From 9766f800070e6573b3db9c235be62f5971e5c2dc Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Tue, 29 Sep 2026 01:48:04 -0400 Subject: [PATCH 1/3] Store a resize when it is released, not a frame later A resize waited one frame after release before storing, so the final size was laid out. A page switch inside that frame could land the resize on the next page's copy of the item, which keeps the same id. Pointer events arrive between frames, and a frame builds and lays out together. So on release the box on screen always matches the size it was last built at. Storing that size with the rendered box makes the commit synchronous and exact. A final move the frame never drew is dropped, so the box stays where the user last saw it. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../image/placed_image_builder.dart | 24 ++++++---- .../text/placed_text_builder.dart | 25 ++++++---- test/placed_box_resize_test.dart | 48 +++++++++++++++++++ 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/lib/widgets/draggable_widgets/image/placed_image_builder.dart b/lib/widgets/draggable_widgets/image/placed_image_builder.dart index e5c9c918..13dfd464 100644 --- a/lib/widgets/draggable_widgets/image/placed_image_builder.dart +++ b/lib/widgets/draggable_widgets/image/placed_image_builder.dart @@ -37,6 +37,11 @@ class PlacedImageBuilder extends StatefulWidget { class _PlacedImageBuilderState extends State { final _boxKey = GlobalKey(); double? localScale; // Make localScale nullable to check if it's initialized + + /// The scale the image was last built, and so laid out, at. Pointer events + /// arrive between frames, so the rendered image always matches this scale + /// even when a drag update since has not been drawn yet. + double? shownScale; bool isPanning = false; bool isDragging = false; Offset? pinnedScreenPosition; @@ -80,16 +85,13 @@ class _PlacedImageBuilderState extends State { pinnedScreenPosition = nextPinned; } - /// Stores the new scale, and the position that keeps the image's top-left - /// where the resize pinned it. On defense the image hangs from its - /// bottom-right corner, so that position moves with the size. + /// Stores the scale the user last saw, and the position that keeps the + /// image's top-left where the resize pinned it. On defense the image hangs + /// from its bottom-right corner, so that position moves with the size. /// - /// Waits for the frame that lays out the last drag update, so the stored - /// position matches the size the user let go at. - Future _finishResize(WidgetRef ref) async { - await WidgetsBinding.instance.endOfFrame; - if (!mounted) return; - + /// Stores [shownScale] with the image laid out at it, so the position + /// matches the size on screen rather than a drag update not drawn yet. + void _finishResize(WidgetRef ref) { final pinned = pinnedScreenPosition; final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; if (pinned != null && renderBox != null) { @@ -103,10 +105,11 @@ class _PlacedImageBuilderState extends State { ); ref .read(placedImageProvider.notifier) - .resize(widget.placedImage.id, scale: localScale!, position: position); + .resize(widget.placedImage.id, scale: shownScale!, position: position); ref.read(strategyProvider.notifier).setUnsaved(); } setState(() { + localScale = shownScale; isPanning = false; pinnedScreenPosition = null; pointerShift = 0; @@ -129,6 +132,7 @@ class _PlacedImageBuilderState extends State { ref.read(placedImageProvider).images[index].scale); } + shownScale = localScale; final coordinateSystem = CoordinateSystem.instance; final attackScreenPosition = coordinateSystem.coordinateToScreen(widget.placedImage.position); diff --git a/lib/widgets/draggable_widgets/text/placed_text_builder.dart b/lib/widgets/draggable_widgets/text/placed_text_builder.dart index ad80fbc1..eaec8e12 100644 --- a/lib/widgets/draggable_widgets/text/placed_text_builder.dart +++ b/lib/widgets/draggable_widgets/text/placed_text_builder.dart @@ -43,6 +43,11 @@ class _PlacedTextBuilderState extends ConsumerState { ]; final _boxKey = GlobalKey(); double? localSize; // Make localScale nullable to check if it's initialized + + /// The width the box was last built, and so laid out, at. Pointer events + /// arrive between frames, so the rendered box always matches this width + /// even when a drag update since has not been drawn yet. + double? shownSize; bool isPanning = false; bool isDragging = false; Offset? pinnedScreenPosition; @@ -85,16 +90,14 @@ class _PlacedTextBuilderState extends ConsumerState { pinnedScreenPosition = nextPinned; } - /// Stores the new width, and the position that keeps the box's top-left - /// where the resize pinned it. On defense the box hangs from its - /// bottom-right corner, so that position moves with the size. + /// Stores the width the user last saw, and the position that keeps the + /// box's top-left where the resize pinned it. On defense the box hangs from + /// its bottom-right corner, so that position moves with the size. /// - /// Waits for the frame that lays out the last drag update, because the - /// text's height, and so its defense position, follows from its width. - Future _finishResize() async { - await WidgetsBinding.instance.endOfFrame; - if (!mounted) return; - + /// The text's height, and so its defense position, follows from its width, + /// so this stores [shownSize] with the box laid out at it rather than a + /// drag update not drawn yet. + void _finishResize() { final pinned = pinnedScreenPosition; final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; if (pinned != null && renderBox != null) { @@ -108,10 +111,11 @@ class _PlacedTextBuilderState extends ConsumerState { ); ref .read(textProvider.notifier) - .resize(widget.placedText.id, size: localSize!, position: position); + .resize(widget.placedText.id, size: shownSize!, position: position); ref.read(strategyProvider.notifier).setUnsaved(); } setState(() { + localSize = shownSize; isPanning = false; isDragging = false; pinnedScreenPosition = null; @@ -137,6 +141,7 @@ class _PlacedTextBuilderState extends ConsumerState { if (texts[index].size != localSize && !isPanning) { localSize = texts[index].size; } + shownSize = localSize; final coordinateSystem = CoordinateSystem.instance; final attackScreenPosition = coordinateSystem.coordinateToScreen(widget.placedText.position); diff --git a/test/placed_box_resize_test.dart b/test/placed_box_resize_test.dart index 6fe61084..2f7712ab 100644 --- a/test/placed_box_resize_test.dart +++ b/test/placed_box_resize_test.dart @@ -227,4 +227,52 @@ void main() { tester.getRect(box).topLeft, offsetMoreOrLessEquals(onDefense.topLeft)); expect(container.read(textProvider).single.size, greaterThan(200)); }); + + testWidgets('release stores what is on screen before the next frame', + (tester) async { + final container = await pumpBox( + tester, + seed: (container) => container.read(textProvider.notifier).fromHive([ + PlacedText( + id: 'text-1', + position: originalPosition, + size: 200, + sizeVersion: PlacedText.currentSizeVersion, + )..text = 'Yo text boxes resize properly on both sides now', + ]), + side: ValueNotifier(false), + builder: (ref, isAttack) { + final placedText = ref.watch(textProvider).single; + return PlacedTextBuilder( + key: ValueKey(placedText.id), + size: placedText.size, + placedText: placedText, + isAttack: isAttack, + onDragEnd: (_) {}, + ); + }, + ); + final box = find.byType(TextScaleController); + final handle = find.byWidgetPredicate( + (widget) => + widget is MouseRegion && + widget.cursor == SystemMouseCursors.resizeLeftRight, + ); + + final gesture = await tester.startGesture(tester.getCenter(handle)); + for (var i = 0; i < 3; i++) { + await gesture.moveBy(const Offset(20, 0)); + await tester.pump(); + } + final shown = tester.getRect(box); + + // A last move and the release arrive before the next frame draws it. + await gesture.moveBy(const Offset(40, 0)); + await gesture.up(); + expect(container.read(textProvider).single.size, greaterThan(200)); + + await tester.pumpAndSettle(); + expect(tester.getRect(box).topLeft, offsetMoreOrLessEquals(shown.topLeft)); + expect(tester.getRect(box).width, closeTo(shown.width, 0.01)); + }); } From e503a658f6a43e3881c90357f6c9985e87e9561e Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Tue, 29 Sep 2026 02:13:28 -0400 Subject: [PATCH 2/3] Keep a quick resize's last movement and write only to its own item Storing the size last drawn at release dropped movement that arrived after the last frame: a quick drag could be lost entirely. Go back to storing the final size one frame after release, when it is laid out. Write it only if the text or image being resized is still loaded. A page switch in that frame loads the next page's copy, which keeps the same id, so the resize can no longer land on it. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../image/placed_image_builder.dart | 33 +++++----- .../text/placed_text_builder.dart | 32 +++++----- test/placed_box_resize_test.dart | 61 +++++++++++++++++-- 3 files changed, 90 insertions(+), 36 deletions(-) diff --git a/lib/widgets/draggable_widgets/image/placed_image_builder.dart b/lib/widgets/draggable_widgets/image/placed_image_builder.dart index 13dfd464..c5f10200 100644 --- a/lib/widgets/draggable_widgets/image/placed_image_builder.dart +++ b/lib/widgets/draggable_widgets/image/placed_image_builder.dart @@ -37,11 +37,6 @@ class PlacedImageBuilder extends StatefulWidget { class _PlacedImageBuilderState extends State { final _boxKey = GlobalKey(); double? localScale; // Make localScale nullable to check if it's initialized - - /// The scale the image was last built, and so laid out, at. Pointer events - /// arrive between frames, so the rendered image always matches this scale - /// even when a drag update since has not been drawn yet. - double? shownScale; bool isPanning = false; bool isDragging = false; Offset? pinnedScreenPosition; @@ -85,16 +80,26 @@ class _PlacedImageBuilderState extends State { pinnedScreenPosition = nextPinned; } - /// Stores the scale the user last saw, and the position that keeps the - /// image's top-left where the resize pinned it. On defense the image hangs - /// from its bottom-right corner, so that position moves with the size. + /// Stores the new scale, and the position that keeps the image's top-left + /// where the resize pinned it. On defense the image hangs from its + /// bottom-right corner, so that position moves with the size. /// - /// Stores [shownScale] with the image laid out at it, so the position - /// matches the size on screen rather than a drag update not drawn yet. - void _finishResize(WidgetRef ref) { + /// Waits for the frame that lays out the last drag update, so the stored + /// position matches the size the user let go at. Writes only if the image + /// it resized is still loaded: a page switch in that frame loads the next + /// page's copy, which keeps the same id. + Future _finishResize(WidgetRef ref) async { + final resizedImage = widget.placedImage; + await WidgetsBinding.instance.endOfFrame; + if (!mounted) return; + final pinned = pinnedScreenPosition; final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; - if (pinned != null && renderBox != null) { + final stillLoaded = ref + .read(placedImageProvider) + .images + .any((image) => identical(image, resizedImage)); + if (pinned != null && renderBox != null && stillLoaded) { final coordinateSystem = CoordinateSystem.instance; final position = coordinateSystem.screenToCoordinate( coordinateSystem.screenPositionFromSide( @@ -105,11 +110,10 @@ class _PlacedImageBuilderState extends State { ); ref .read(placedImageProvider.notifier) - .resize(widget.placedImage.id, scale: shownScale!, position: position); + .resize(widget.placedImage.id, scale: localScale!, position: position); ref.read(strategyProvider.notifier).setUnsaved(); } setState(() { - localScale = shownScale; isPanning = false; pinnedScreenPosition = null; pointerShift = 0; @@ -132,7 +136,6 @@ class _PlacedImageBuilderState extends State { ref.read(placedImageProvider).images[index].scale); } - shownScale = localScale; final coordinateSystem = CoordinateSystem.instance; final attackScreenPosition = coordinateSystem.coordinateToScreen(widget.placedImage.position); diff --git a/lib/widgets/draggable_widgets/text/placed_text_builder.dart b/lib/widgets/draggable_widgets/text/placed_text_builder.dart index eaec8e12..15b164f5 100644 --- a/lib/widgets/draggable_widgets/text/placed_text_builder.dart +++ b/lib/widgets/draggable_widgets/text/placed_text_builder.dart @@ -43,11 +43,6 @@ class _PlacedTextBuilderState extends ConsumerState { ]; final _boxKey = GlobalKey(); double? localSize; // Make localScale nullable to check if it's initialized - - /// The width the box was last built, and so laid out, at. Pointer events - /// arrive between frames, so the rendered box always matches this width - /// even when a drag update since has not been drawn yet. - double? shownSize; bool isPanning = false; bool isDragging = false; Offset? pinnedScreenPosition; @@ -90,17 +85,24 @@ class _PlacedTextBuilderState extends ConsumerState { pinnedScreenPosition = nextPinned; } - /// Stores the width the user last saw, and the position that keeps the - /// box's top-left where the resize pinned it. On defense the box hangs from - /// its bottom-right corner, so that position moves with the size. + /// Stores the new width, and the position that keeps the box's top-left + /// where the resize pinned it. On defense the box hangs from its + /// bottom-right corner, so that position moves with the size. /// - /// The text's height, and so its defense position, follows from its width, - /// so this stores [shownSize] with the box laid out at it rather than a - /// drag update not drawn yet. - void _finishResize() { + /// Waits for the frame that lays out the last drag update, because the + /// text's height, and so its defense position, follows from its width. + /// Writes only if the text it resized is still loaded: a page switch in + /// that frame loads the next page's copy, which keeps the same id. + Future _finishResize() async { + final resizedText = widget.placedText; + await WidgetsBinding.instance.endOfFrame; + if (!mounted) return; + final pinned = pinnedScreenPosition; final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; - if (pinned != null && renderBox != null) { + final stillLoaded = + ref.read(textProvider).any((text) => identical(text, resizedText)); + if (pinned != null && renderBox != null && stillLoaded) { final coordinateSystem = CoordinateSystem.instance; final position = coordinateSystem.screenToCoordinate( coordinateSystem.screenPositionFromSide( @@ -111,11 +113,10 @@ class _PlacedTextBuilderState extends ConsumerState { ); ref .read(textProvider.notifier) - .resize(widget.placedText.id, size: shownSize!, position: position); + .resize(widget.placedText.id, size: localSize!, position: position); ref.read(strategyProvider.notifier).setUnsaved(); } setState(() { - localSize = shownSize; isPanning = false; isDragging = false; pinnedScreenPosition = null; @@ -141,7 +142,6 @@ class _PlacedTextBuilderState extends ConsumerState { if (texts[index].size != localSize && !isPanning) { localSize = texts[index].size; } - shownSize = localSize; final coordinateSystem = CoordinateSystem.instance; final attackScreenPosition = coordinateSystem.coordinateToScreen(widget.placedText.position); diff --git a/test/placed_box_resize_test.dart b/test/placed_box_resize_test.dart index 2f7712ab..6f50736e 100644 --- a/test/placed_box_resize_test.dart +++ b/test/placed_box_resize_test.dart @@ -228,9 +228,9 @@ void main() { expect(container.read(textProvider).single.size, greaterThan(200)); }); - testWidgets('release stores what is on screen before the next frame', + testWidgets('a release before the next frame keeps its last movement', (tester) async { - final container = await pumpBox( + await pumpBox( tester, seed: (container) => container.read(textProvider.notifier).fromHive([ PlacedText( @@ -269,10 +269,61 @@ void main() { // A last move and the release arrive before the next frame draws it. await gesture.moveBy(const Offset(40, 0)); await gesture.up(); - expect(container.read(textProvider).single.size, greaterThan(200)); - await tester.pumpAndSettle(); + expect(tester.getRect(box).topLeft, offsetMoreOrLessEquals(shown.topLeft)); - expect(tester.getRect(box).width, closeTo(shown.width, 0.01)); + expect(tester.getRect(box).width, closeTo(shown.width + 40, 1)); + }); + + testWidgets('a page switch before the resize is stored leaves the copy alone', + (tester) async { + final container = await pumpBox( + tester, + seed: (container) => container.read(textProvider.notifier).fromHive([ + PlacedText( + id: 'text-1', + position: originalPosition, + size: 200, + sizeVersion: PlacedText.currentSizeVersion, + )..text = 'Yo text boxes resize properly on both sides now', + ]), + side: ValueNotifier(false), + builder: (ref, isAttack) { + final placedText = ref.watch(textProvider).single; + return PlacedTextBuilder( + key: ValueKey(placedText.id), + size: placedText.size, + placedText: placedText, + isAttack: isAttack, + onDragEnd: (_) {}, + ); + }, + ); + final handle = find.byWidgetPredicate( + (widget) => + widget is MouseRegion && + widget.cursor == SystemMouseCursors.resizeLeftRight, + ); + + final gesture = await tester.startGesture(tester.getCenter(handle)); + for (var i = 0; i < 3; i++) { + await gesture.moveBy(const Offset(20, 0)); + await tester.pump(); + } + await gesture.up(); + + // Before the frame, the next page loads its copy of the text. + final copy = PlacedText( + id: 'text-1', + position: const Offset(100, 100), + size: 200, + sizeVersion: PlacedText.currentSizeVersion, + )..text = 'Yo text boxes resize properly on both sides now'; + container.read(textProvider.notifier).fromHive([copy]); + await tester.pumpAndSettle(); + + final loaded = container.read(textProvider).single; + expect(loaded.size, 200); + expect(loaded.position, const Offset(100, 100)); }); } From 76219ae29822b045e4a8778f9059bcfe2904b1b4 Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Tue, 29 Sep 2026 02:33:32 -0400 Subject: [PATCH 3/3] Store a resize's size on release and settle its position a frame later With the write deferred a frame, a page save right after release still read the old size, and the page switch then replaced the item, so the resize was lost. Store the size, and a position from the current layout, as soon as the handle is released. Hold the pin one more frame, then store the position again if the final layout moved it, only while the same item is still loaded. Normally that second write finds nothing to change. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../image/placed_image_builder.dart | 54 ++++++++++-------- .../text/placed_text_builder.dart | 55 +++++++++++-------- test/placed_box_resize_test.dart | 7 ++- 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/lib/widgets/draggable_widgets/image/placed_image_builder.dart b/lib/widgets/draggable_widgets/image/placed_image_builder.dart index c5f10200..eba8db3f 100644 --- a/lib/widgets/draggable_widgets/image/placed_image_builder.dart +++ b/lib/widgets/draggable_widgets/image/placed_image_builder.dart @@ -80,39 +80,26 @@ class _PlacedImageBuilderState extends State { pinnedScreenPosition = nextPinned; } - /// Stores the new scale, and the position that keeps the image's top-left - /// where the resize pinned it. On defense the image hangs from its - /// bottom-right corner, so that position moves with the size. + /// Stores the new scale at once, so a page save right after release keeps + /// it, with the position that keeps the image's top-left where the resize + /// pinned it. On defense the image hangs from its bottom-right corner, so + /// that position moves with the size. /// - /// Waits for the frame that lays out the last drag update, so the stored - /// position matches the size the user let go at. Writes only if the image - /// it resized is still loaded: a page switch in that frame loads the next - /// page's copy, which keeps the same id. + /// The last drag update may not be laid out yet, so the pin holds for one + /// more frame, then the position is stored again if it moved, unless a + /// page switch loaded another page's copy of the image, which keeps its id. Future _finishResize(WidgetRef ref) async { final resizedImage = widget.placedImage; + final pinned = pinnedScreenPosition; + if (pinned != null) _storeResize(ref, resizedImage, pinned); await WidgetsBinding.instance.endOfFrame; if (!mounted) return; - final pinned = pinnedScreenPosition; - final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; final stillLoaded = ref .read(placedImageProvider) .images .any((image) => identical(image, resizedImage)); - if (pinned != null && renderBox != null && stillLoaded) { - final coordinateSystem = CoordinateSystem.instance; - final position = coordinateSystem.screenToCoordinate( - coordinateSystem.screenPositionFromSide( - sideScreenPosition: pinned, - reflectionOffset: renderBox.size.bottomRight(Offset.zero), - isAttack: widget.isAttack, - ), - ); - ref - .read(placedImageProvider.notifier) - .resize(widget.placedImage.id, scale: localScale!, position: position); - ref.read(strategyProvider.notifier).setUnsaved(); - } + if (pinned != null && stillLoaded) _storeResize(ref, resizedImage, pinned); setState(() { isPanning = false; pinnedScreenPosition = null; @@ -120,6 +107,27 @@ class _PlacedImageBuilderState extends State { }); } + /// Writes [image]'s new scale and the position that puts its top-left at + /// [pinned] as laid out now, unless both are already stored. + void _storeResize(WidgetRef ref, PlacedImage image, Offset pinned) { + final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; + if (renderBox == null) return; + + final coordinateSystem = CoordinateSystem.instance; + final position = coordinateSystem.screenToCoordinate( + coordinateSystem.screenPositionFromSide( + sideScreenPosition: pinned, + reflectionOffset: renderBox.size.bottomRight(Offset.zero), + isAttack: widget.isAttack, + ), + ); + if (image.scale == localScale && image.position == position) return; + ref + .read(placedImageProvider.notifier) + .resize(image.id, scale: localScale!, position: position); + ref.read(strategyProvider.notifier).setUnsaved(); + } + @override Widget build(BuildContext context) { if (localScale == null) { diff --git a/lib/widgets/draggable_widgets/text/placed_text_builder.dart b/lib/widgets/draggable_widgets/text/placed_text_builder.dart index 15b164f5..4dad542a 100644 --- a/lib/widgets/draggable_widgets/text/placed_text_builder.dart +++ b/lib/widgets/draggable_widgets/text/placed_text_builder.dart @@ -85,37 +85,25 @@ class _PlacedTextBuilderState extends ConsumerState { pinnedScreenPosition = nextPinned; } - /// Stores the new width, and the position that keeps the box's top-left - /// where the resize pinned it. On defense the box hangs from its - /// bottom-right corner, so that position moves with the size. + /// Stores the new width at once, so a page save right after release keeps + /// it, with the position that keeps the box's top-left where the resize + /// pinned it. On defense the box hangs from its bottom-right corner, so + /// that position moves with the size. /// - /// Waits for the frame that lays out the last drag update, because the - /// text's height, and so its defense position, follows from its width. - /// Writes only if the text it resized is still loaded: a page switch in - /// that frame loads the next page's copy, which keeps the same id. + /// The text's height, and so its defense position, follows from its width, + /// and the last drag update may not be laid out yet. So the pin holds for + /// one more frame, then the position is stored again if it moved, unless a + /// page switch loaded another page's copy of the text, which keeps its id. Future _finishResize() async { final resizedText = widget.placedText; + final pinned = pinnedScreenPosition; + if (pinned != null) _storeResize(resizedText, pinned); await WidgetsBinding.instance.endOfFrame; if (!mounted) return; - final pinned = pinnedScreenPosition; - final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; final stillLoaded = ref.read(textProvider).any((text) => identical(text, resizedText)); - if (pinned != null && renderBox != null && stillLoaded) { - final coordinateSystem = CoordinateSystem.instance; - final position = coordinateSystem.screenToCoordinate( - coordinateSystem.screenPositionFromSide( - sideScreenPosition: pinned, - reflectionOffset: renderBox.size.bottomRight(Offset.zero), - isAttack: widget.isAttack, - ), - ); - ref - .read(textProvider.notifier) - .resize(widget.placedText.id, size: localSize!, position: position); - ref.read(strategyProvider.notifier).setUnsaved(); - } + if (pinned != null && stillLoaded) _storeResize(resizedText, pinned); setState(() { isPanning = false; isDragging = false; @@ -124,6 +112,27 @@ class _PlacedTextBuilderState extends ConsumerState { }); } + /// Writes [text]'s new width and the position that puts its top-left at + /// [pinned] as laid out now, unless both are already stored. + void _storeResize(PlacedText text, Offset pinned) { + final renderBox = _boxKey.currentContext?.findRenderObject() as RenderBox?; + if (renderBox == null) return; + + final coordinateSystem = CoordinateSystem.instance; + final position = coordinateSystem.screenToCoordinate( + coordinateSystem.screenPositionFromSide( + sideScreenPosition: pinned, + reflectionOffset: renderBox.size.bottomRight(Offset.zero), + isAttack: widget.isAttack, + ), + ); + if (text.size == localSize && text.position == position) return; + ref + .read(textProvider.notifier) + .resize(text.id, size: localSize!, position: position); + ref.read(strategyProvider.notifier).setUnsaved(); + } + @override Widget build(BuildContext context) { final texts = ref.watch(textProvider); diff --git a/test/placed_box_resize_test.dart b/test/placed_box_resize_test.dart index 6f50736e..02bedea6 100644 --- a/test/placed_box_resize_test.dart +++ b/test/placed_box_resize_test.dart @@ -275,7 +275,7 @@ void main() { expect(tester.getRect(box).width, closeTo(shown.width + 40, 1)); }); - testWidgets('a page switch before the resize is stored leaves the copy alone', + testWidgets('a page switch right after release keeps the resize on its page', (tester) async { final container = await pumpBox( tester, @@ -310,9 +310,12 @@ void main() { await gesture.moveBy(const Offset(20, 0)); await tester.pump(); } + final resized = container.read(textProvider).single; await gesture.up(); - // Before the frame, the next page loads its copy of the text. + // Before the frame, the page is saved and the next page loads its copy + // of the text. The save must already see the new width. + expect(resized.size, greaterThan(200)); final copy = PlacedText( id: 'text-1', position: const Offset(100, 100),