feat: support customizing the mobile magnifiter (#625)
* feat: support customizing the mobile magnifiter * chore: remove unused test * feat: expaned the padding between cursor and toolbar * feat: support long pressing to update selection * chore: revert code sign
This commit is contained in:
parent
e6b1336041
commit
82f3baf3f1
14 changed files with 209 additions and 111 deletions
|
|
@ -358,7 +358,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = KD48NC6JW7;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
|
@ -487,7 +487,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = KD48NC6JW7;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
|
@ -510,7 +510,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = KD48NC6JW7;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ class _MobileEditorState extends State<MobileEditor> {
|
|||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
magnifierSize: const Size(144, 96),
|
||||
mobileDragHandleBallSize: const Size(12, 12),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/block_component/base_component/selection/selection_area_painter.dart';
|
||||
import 'package:appflowy_editor/src/editor/editor_component/service/selection/mobile_selection_service.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/cursor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
@ -123,10 +124,15 @@ class _BlockSelectionAreaState extends State<BlockSelectionArea> {
|
|||
prevCursorRect == null) {
|
||||
return sizedBox;
|
||||
}
|
||||
final editorState = context.read<EditorState>();
|
||||
final dragMode =
|
||||
editorState.selectionExtraInfo?[selectionDragModeKey];
|
||||
final shouldBlink = widget.delegate.shouldCursorBlink &&
|
||||
dragMode != MobileSelectionDragMode.cursor;
|
||||
final cursor = Cursor(
|
||||
key: cursorKey,
|
||||
rect: prevCursorRect!,
|
||||
shouldBlink: widget.delegate.shouldCursorBlink,
|
||||
shouldBlink: shouldBlink,
|
||||
cursorStyle: widget.delegate.cursorStyle,
|
||||
color: widget.cursorColor,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/editor_component/service/scroll/desktop_scroll_service.dart';
|
||||
import 'package:appflowy_editor/src/editor/editor_component/service/scroll/mobile_scroll_service.dart';
|
||||
import 'package:appflowy_editor/src/editor/toolbar/mobile/utils/keyboard_height_observer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
|
@ -100,10 +101,14 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>
|
|||
if (PlatformExtension.isMobile) {
|
||||
// soft keyboard
|
||||
// workaround: wait for the soft keyboard to show up
|
||||
return Future.delayed(const Duration(milliseconds: 300), () {
|
||||
return Future.delayed(
|
||||
Duration(
|
||||
milliseconds:
|
||||
KeyboardHeightObserver.currentKeyboardHeight == 0 ? 250 : 0,
|
||||
), () {
|
||||
startAutoScroll(
|
||||
endTouchPoint,
|
||||
edgeOffset: 100,
|
||||
edgeOffset: 150,
|
||||
duration: Duration.zero,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,17 +12,55 @@ class MobileMagnifier extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// the magnifier will blink if the center is the same as the offset.
|
||||
final magicOffset = Offset(0, size.height - 22);
|
||||
return Positioned.fromRect(
|
||||
rect: Rect.fromCenter(
|
||||
center: offset.translate(0, -size.height),
|
||||
center: offset - magicOffset,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
),
|
||||
child: IgnorePointer(
|
||||
child: Magnifier(
|
||||
child: _CustomMagnifier(
|
||||
size: size,
|
||||
additionalFocalPointOffset: magicOffset,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomMagnifier extends StatelessWidget {
|
||||
const _CustomMagnifier({
|
||||
this.additionalFocalPointOffset = Offset.zero,
|
||||
required this.size,
|
||||
});
|
||||
|
||||
final Size size;
|
||||
final Offset additionalFocalPointOffset;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawMagnifier(
|
||||
decoration: const MagnifierDecoration(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(40)),
|
||||
),
|
||||
shadows: <BoxShadow>[
|
||||
BoxShadow(
|
||||
blurRadius: 1.5,
|
||||
offset: Offset(0, 2),
|
||||
spreadRadius: 0.75,
|
||||
color: Color.fromARGB(25, 0, 0, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
magnificationScale: 1.25,
|
||||
focalPointOffset: additionalFocalPointOffset,
|
||||
size: size,
|
||||
child: const ColoredBox(
|
||||
color: Color.fromARGB(8, 158, 158, 158),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,16 @@ enum MobileSelectionHandlerType {
|
|||
cursorHandler,
|
||||
}
|
||||
|
||||
// the value type is MobileSelectionDragMode
|
||||
const String selectionDragModeKey = 'selection_drag_mode';
|
||||
|
||||
class MobileSelectionServiceWidget extends StatefulWidget {
|
||||
const MobileSelectionServiceWidget({
|
||||
super.key,
|
||||
this.cursorColor = const Color(0xFF00BCF0),
|
||||
this.selectionColor = const Color.fromARGB(53, 111, 201, 231),
|
||||
this.showMagnifier = true,
|
||||
this.magnifierSize = const Size(72, 48),
|
||||
required this.child,
|
||||
});
|
||||
|
||||
|
|
@ -37,6 +41,8 @@ class MobileSelectionServiceWidget extends StatefulWidget {
|
|||
/// only works on iOS or Android.
|
||||
final bool showMagnifier;
|
||||
|
||||
final Size magnifierSize;
|
||||
|
||||
@override
|
||||
State<MobileSelectionServiceWidget> createState() =>
|
||||
_MobileSelectionServiceWidgetState();
|
||||
|
|
@ -98,6 +104,9 @@ class _MobileSelectionServiceWidgetState
|
|||
onTapUp: _onTapUp,
|
||||
onDoubleTapUp: _onDoubleTapUp,
|
||||
onTripleTapUp: _onTripleTapUp,
|
||||
onLongPressStart: _onLongPressStart,
|
||||
onLongPressMoveUpdate: _onLongPressMoveUpdate,
|
||||
onLongPressEnd: _onLongPressEnd,
|
||||
child: Stack(
|
||||
children: [
|
||||
widget.child,
|
||||
|
|
@ -117,7 +126,7 @@ class _MobileSelectionServiceWidgetState
|
|||
final renderBox = context.findRenderObject() as RenderBox;
|
||||
final local = renderBox.globalToLocal(offset);
|
||||
return MobileMagnifier(
|
||||
size: const Size(72, 48),
|
||||
size: widget.magnifierSize,
|
||||
offset: local,
|
||||
);
|
||||
},
|
||||
|
|
@ -145,6 +154,9 @@ class _MobileSelectionServiceWidgetState
|
|||
editorState.updateSelectionWithReason(
|
||||
selection,
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
extraInfo: {
|
||||
selectionDragModeKey: dragMode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +278,10 @@ class _MobileSelectionServiceWidgetState
|
|||
return;
|
||||
}
|
||||
|
||||
editorState.selection = Selection.collapsed(position);
|
||||
editorState.updateSelectionWithReason(
|
||||
Selection.collapsed(position),
|
||||
extraInfo: null,
|
||||
);
|
||||
}
|
||||
|
||||
void _onDoubleTapUp(TapUpDetails details) {
|
||||
|
|
@ -375,6 +390,58 @@ class _MobileSelectionServiceWidgetState
|
|||
void _onPanEnd(DragEndDetails details) {
|
||||
// do nothing
|
||||
_lastPanOffset.value = null;
|
||||
|
||||
// clear the status
|
||||
dragMode = MobileSelectionDragMode.none;
|
||||
editorState.updateSelectionWithReason(
|
||||
editorState.selection,
|
||||
extraInfo: null,
|
||||
);
|
||||
}
|
||||
|
||||
void _onLongPressStart(LongPressStartDetails details) {
|
||||
clearSelection();
|
||||
|
||||
// clear old state.
|
||||
_panStartOffset = null;
|
||||
|
||||
final position = getPositionInOffset(details.globalPosition);
|
||||
if (position == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastPanOffset.value = details.globalPosition;
|
||||
|
||||
dragMode = MobileSelectionDragMode.cursor;
|
||||
updateSelection(
|
||||
Selection.collapsed(position),
|
||||
);
|
||||
}
|
||||
|
||||
void _onLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
|
||||
final panEndOffset = details.globalPosition;
|
||||
final position = getNodeInOffset(panEndOffset)
|
||||
?.selectable
|
||||
?.getPositionInOffset(panEndOffset);
|
||||
|
||||
if (position == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastPanOffset.value = panEndOffset;
|
||||
updateSelection(
|
||||
Selection.collapsed(position),
|
||||
);
|
||||
}
|
||||
|
||||
void _onLongPressEnd(LongPressEndDetails details) {
|
||||
_lastPanOffset.value = null;
|
||||
|
||||
dragMode = MobileSelectionDragMode.none;
|
||||
editorState.updateSelectionWithReason(
|
||||
editorState.selection,
|
||||
extraInfo: null,
|
||||
);
|
||||
}
|
||||
|
||||
void _updateSelectionAreas(Selection selection) {
|
||||
|
|
@ -444,6 +511,9 @@ class _MobileSelectionServiceWidgetState
|
|||
showLeftHandler: showLeftHandler,
|
||||
showRightHandler: showRightHandler,
|
||||
handlerColor: editorState.editorStyle.cursorColor,
|
||||
handlerWidth: editorState.editorStyle.mobileDragHandleWidth,
|
||||
handlerBallWidth:
|
||||
editorState.editorStyle.mobileDragHandleBallSize.width,
|
||||
),
|
||||
);
|
||||
_selectionAreas.add(overlay);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
|||
import 'package:appflowy_editor/src/editor/editor_component/service/selection/desktop_selection_service.dart';
|
||||
import 'package:appflowy_editor/src/editor/editor_component/service/selection/mobile_selection_service.dart';
|
||||
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SelectionServiceWidget extends StatefulWidget {
|
||||
const SelectionServiceWidget({
|
||||
|
|
@ -48,11 +49,13 @@ class _SelectionServiceWidgetState extends State<SelectionServiceWidget>
|
|||
);
|
||||
}
|
||||
|
||||
final editorState = context.read<EditorState>();
|
||||
return MobileSelectionServiceWidget(
|
||||
key: forwardKey,
|
||||
cursorColor: widget.cursorColor,
|
||||
selectionColor: widget.selectionColor,
|
||||
showMagnifier: widget.showMagnifier,
|
||||
magnifierSize: editorState.editorStyle.magnifierSize,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// The style of the editor.
|
||||
|
|
@ -14,6 +13,9 @@ class EditorStyle {
|
|||
required this.selectionColor,
|
||||
required this.textStyleConfiguration,
|
||||
required this.textSpanDecorator,
|
||||
this.magnifierSize = const Size(72, 48),
|
||||
this.mobileDragHandleBallSize = const Size(8, 8),
|
||||
this.mobileDragHandleWidth = 2.0,
|
||||
this.defaultTextDirection,
|
||||
});
|
||||
|
||||
|
|
@ -43,6 +45,16 @@ class EditorStyle {
|
|||
|
||||
final String? defaultTextDirection;
|
||||
|
||||
/// The size of the magnifier.
|
||||
/// Only works on mobile.
|
||||
final Size magnifierSize;
|
||||
|
||||
/// mobile drag handler size.
|
||||
/// /// Only works on mobile.
|
||||
final Size mobileDragHandleBallSize;
|
||||
|
||||
final double mobileDragHandleWidth;
|
||||
|
||||
const EditorStyle.desktop({
|
||||
EdgeInsets? padding,
|
||||
Color? cursorColor,
|
||||
|
|
@ -59,7 +71,10 @@ class EditorStyle {
|
|||
text: TextStyle(fontSize: 16, color: Colors.black),
|
||||
),
|
||||
textSpanDecorator =
|
||||
textSpanDecorator ?? defaultTextSpanDecoratorForAttribute;
|
||||
textSpanDecorator ?? defaultTextSpanDecoratorForAttribute,
|
||||
magnifierSize = Size.zero,
|
||||
mobileDragHandleBallSize = Size.zero,
|
||||
mobileDragHandleWidth = 0.0;
|
||||
|
||||
const EditorStyle.mobile({
|
||||
EdgeInsets? padding,
|
||||
|
|
@ -68,6 +83,9 @@ class EditorStyle {
|
|||
TextStyleConfiguration? textStyleConfiguration,
|
||||
TextSpanDecoratorForAttribute? textSpanDecorator,
|
||||
this.defaultTextDirection,
|
||||
this.magnifierSize = const Size(72, 48),
|
||||
this.mobileDragHandleBallSize = const Size(8, 8),
|
||||
this.mobileDragHandleWidth = 2.0,
|
||||
}) : padding = padding ?? const EdgeInsets.symmetric(horizontal: 20),
|
||||
cursorColor = cursorColor ?? const Color(0xFF00BCF0),
|
||||
selectionColor =
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/editor_component/service/selection/mobile_selection_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileFloatingToolbarItem {
|
||||
|
|
@ -104,8 +105,15 @@ class _MobileFloatingToolbarState extends State<MobileFloatingToolbar>
|
|||
}
|
||||
prevSelection = selection;
|
||||
} else {
|
||||
// uses debounce to avoid the computing the rects too frequently.
|
||||
_clear();
|
||||
final dragMode = editorState.selectionExtraInfo?[selectionDragModeKey];
|
||||
if ([
|
||||
MobileSelectionDragMode.leftSelectionHandler,
|
||||
MobileSelectionDragMode.rightSelectionHandler,
|
||||
].contains(dragMode)) {
|
||||
return;
|
||||
}
|
||||
// uses debounce to avoid the computing the rects too frequently.
|
||||
_showAfterDelay(const Duration(milliseconds: 400));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ typedef KeyboardHeightCallback = void Function(double height);
|
|||
class KeyboardHeightObserver {
|
||||
KeyboardHeightObserver._() {
|
||||
_keyboardHeightPlugin.onKeyboardHeightChanged((height) {
|
||||
currentKeyboardHeight = height;
|
||||
notify(height);
|
||||
});
|
||||
}
|
||||
|
||||
static final KeyboardHeightObserver instance = KeyboardHeightObserver._();
|
||||
static double currentKeyboardHeight = 0;
|
||||
|
||||
final List<KeyboardHeightCallback> _listeners = [];
|
||||
final KeyboardHeightPlugin _keyboardHeightPlugin = KeyboardHeightPlugin();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ class MobileSelectionWidget extends StatelessWidget {
|
|||
this.showLeftHandler = false,
|
||||
this.showRightHandler = false,
|
||||
this.handlerColor = Colors.black,
|
||||
this.handlerBallWidth = 6.0,
|
||||
this.handlerWidth = 2.0,
|
||||
});
|
||||
|
||||
final Color color;
|
||||
|
|
@ -19,11 +21,11 @@ class MobileSelectionWidget extends StatelessWidget {
|
|||
final bool showLeftHandler;
|
||||
final bool showRightHandler;
|
||||
final Color handlerColor;
|
||||
final double handlerWidth;
|
||||
final double handlerBallWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const handlerWidth = 2.0;
|
||||
const handlerBallWidth = 6.0;
|
||||
// to avoid row overflow
|
||||
const threshold = 0.25;
|
||||
// left and right add 2px to avoid the selection area from being too narrow
|
||||
|
|
@ -91,11 +93,13 @@ class MobileSelectionWithHandler extends StatelessWidget {
|
|||
decoration: decoration,
|
||||
);
|
||||
if (showLeftHandler || showRightHandler) {
|
||||
child = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
child = Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
if (showLeftHandler)
|
||||
_DragHandler(
|
||||
Positioned(
|
||||
left: -handlerWidth,
|
||||
child: _DragHandler(
|
||||
handlerColor: handlerColor,
|
||||
handlerWidth: handlerWidth,
|
||||
handlerBallWidth: handlerBallWidth,
|
||||
|
|
@ -103,9 +107,12 @@ class MobileSelectionWithHandler extends StatelessWidget {
|
|||
showLeftHandler: true,
|
||||
showRightHandler: false,
|
||||
),
|
||||
Expanded(child: child),
|
||||
),
|
||||
child,
|
||||
if (showRightHandler)
|
||||
_DragHandler(
|
||||
Positioned(
|
||||
right: -handlerWidth,
|
||||
child: _DragHandler(
|
||||
handlerColor: handlerColor,
|
||||
handlerWidth: handlerWidth,
|
||||
handlerBallWidth: handlerBallWidth,
|
||||
|
|
@ -113,6 +120,7 @@ class MobileSelectionWithHandler extends StatelessWidget {
|
|||
showRightHandler: true,
|
||||
showLeftHandler: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ class MobileSelectionGestureDetector extends StatefulWidget {
|
|||
this.onPanStart,
|
||||
this.onPanUpdate,
|
||||
this.onPanEnd,
|
||||
this.onLongPressStart,
|
||||
this.onLongPressEnd,
|
||||
this.onLongPressMoveUpdate,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -31,6 +34,9 @@ class MobileSelectionGestureDetector extends StatefulWidget {
|
|||
final GestureDragStartCallback? onPanStart;
|
||||
final GestureDragUpdateCallback? onPanUpdate;
|
||||
final GestureDragEndCallback? onPanEnd;
|
||||
final GestureLongPressStartCallback? onLongPressStart;
|
||||
final GestureLongPressEndCallback? onLongPressEnd;
|
||||
final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
|
||||
}
|
||||
|
||||
class MobileSelectionGestureDetectorState
|
||||
|
|
@ -67,6 +73,16 @@ class MobileSelectionGestureDetectorState
|
|||
..onEnd = widget.onPanEnd;
|
||||
},
|
||||
),
|
||||
LongPressGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
||||
() => LongPressGestureRecognizer(),
|
||||
(recognizer) {
|
||||
recognizer
|
||||
..onLongPressStart = widget.onLongPressStart
|
||||
..onLongPressEnd = widget.onLongPressEnd
|
||||
..onLongPressMoveUpdate = widget.onLongPressMoveUpdate;
|
||||
},
|
||||
),
|
||||
TapGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
() => TapGestureRecognizer(),
|
||||
|
|
|
|||
|
|
@ -49,15 +49,13 @@ dependencies:
|
|||
keyboard_height_plugin: ^0.0.4
|
||||
numerus: ^2.1.2
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.1
|
||||
network_image_mock: ^2.1.1
|
||||
mockito: ^5.4.1
|
||||
leak_tracker_flutter_testing: ^1.0.10
|
||||
leak_tracker: '>=9.0.0 <11.0.0'
|
||||
leak_tracker: 9.0.16
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:leak_tracker/leak_tracker.dart';
|
||||
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||
|
||||
void main() async {
|
||||
testWidgetsWithLeakTracking(
|
||||
'memory leak',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(const _NoMemoryLeaks());
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
leakTesting: LeakTesting.settings.copyWith(
|
||||
ignore: false,
|
||||
leakDiagnosticConfig: const LeakDiagnosticConfig(
|
||||
collectStackTraceOnStart: true,
|
||||
collectRetainingPathForNotGCed: true,
|
||||
),
|
||||
onLeaks: (leaks) {
|
||||
// dump the leaks
|
||||
for (final leak in leaks.all) {
|
||||
final stack =
|
||||
leak.context![ContextKeys.startCallstack]! as StackTrace;
|
||||
final stackInEditor = stack
|
||||
.toString()
|
||||
.split('\n')
|
||||
.where((stack) => stack.contains('package:appflowy_editor'))
|
||||
.join('\n');
|
||||
debugPrint('''
|
||||
${leak.type} => ${leak.trackedClass}
|
||||
$stackInEditor
|
||||
-------------------------------------------------------------------------------
|
||||
''');
|
||||
}
|
||||
expect(leaks.all, isEmpty);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _NoMemoryLeaks extends StatefulWidget {
|
||||
const _NoMemoryLeaks();
|
||||
|
||||
@override
|
||||
State<_NoMemoryLeaks> createState() => __NoMemoryLeaksState();
|
||||
}
|
||||
|
||||
class __NoMemoryLeaksState extends State<_NoMemoryLeaks> {
|
||||
final editorState = EditorState.blank();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
editorState.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: SafeArea(
|
||||
child: Container(
|
||||
width: 500,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.blue),
|
||||
),
|
||||
child: AppFlowyEditor(
|
||||
editorState: editorState,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue