diff --git a/lib/core/commands/command_factory/command_factory.dart b/lib/core/commands/command_factory/command_factory.dart index 5b162543..202d9010 100644 --- a/lib/core/commands/command_factory/command_factory.dart +++ b/lib/core/commands/command_factory/command_factory.dart @@ -1,4 +1,9 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; @@ -55,6 +60,21 @@ class CommandFactory { angle, ); + ClipboardCommand createClipboardCommand( + Paint paint, + Uint8List imageData, + ui.Offset offset, + double scale, + double rotation, + ) => + ClipboardCommand( + paint, + imageData, + offset, + scale, + rotation, + ); + TextCommand createTextCommand( Offset point, String text, @@ -108,4 +128,12 @@ class CommandFactory { SprayCommand createSprayCommand(List points, Paint paint) { return SprayCommand(points, paint); } + + DeleteRegionCommand createDeleteRegionCommand( + ui.Rect region, + ) => + DeleteRegionCommand( + Paint(), + region, + ); } diff --git a/lib/core/commands/command_implementation/command.dart b/lib/core/commands/command_implementation/command.dart index a16e511f..3265bfb8 100644 --- a/lib/core/commands/command_implementation/command.dart +++ b/lib/core/commands/command_implementation/command.dart @@ -1,4 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/ellipse_shape_command.dart'; @@ -13,6 +15,8 @@ abstract class Command with EquatableMixin { Map toJson(); + Future prepareForRuntime() async {} + factory Command.fromJson(Map json) { String type = json['type'] as String; switch (type) { @@ -24,6 +28,10 @@ abstract class Command with EquatableMixin { return SquareShapeCommand.fromJson(json); case SerializerType.ELLIPSE_SHAPE_COMMAND: return EllipseShapeCommand.fromJson(json); + case SerializerType.CLIPBOARD_COMMAND: + return ClipboardCommand.fromJson(json); + case SerializerType.DELETE_REGION_COMMAND: + return DeleteRegionCommand.fromJson(json); case SerializerType.TEXT_COMMAND: return TextCommand.fromJson(json); case SerializerType.HEART_SHAPE_COMMAND: diff --git a/lib/core/commands/command_implementation/graphic/clipboard_command.dart b/lib/core/commands/command_implementation/graphic/clipboard_command.dart new file mode 100644 index 00000000..a3b41ef9 --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/clipboard_command.dart @@ -0,0 +1,122 @@ +// ignore_for_file: must_be_immutable + +import 'dart:ui' as ui; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; +import 'package:paintroid/core/json_serialization/converter/offset_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/uint8list_base64_converter.dart'; +import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; +import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; +import 'package:paintroid/core/models/loggable_mixin.dart'; +import 'package:toast/toast.dart'; + +part 'clipboard_command.g.dart'; + +@JsonSerializable() +class ClipboardCommand extends GraphicCommand with LoggableMixin { + @Uint8ListBase64Converter() + final Uint8List imageData; + @OffsetConverter() + final ui.Offset offset; + final double scale; + final double rotation; + + final int version; + final String type; + + @JsonKey(includeFromJson: false, includeToJson: false) + ui.Image? _runtimeImage; + + ClipboardCommand( + super.paint, + this.imageData, + this.offset, + this.scale, + this.rotation, { + int? version, + this.type = SerializerType.CLIPBOARD_COMMAND, + }) : version = version ?? + VersionStrategyManager.strategy.getClipboardCommandVersion(); + + @override + Future prepareForRuntime() async { + if (_runtimeImage == null && imageData.isNotEmpty) { + try { + final buffer = await ui.ImmutableBuffer.fromUint8List(imageData); + final descriptor = await ui.ImageDescriptor.encoded(buffer); + final codec = await descriptor.instantiateCodec(); + final frameInfo = await codec.getNextFrame(); + _runtimeImage = frameInfo.image; + } catch (e, _) { + Toast.show( + 'Error: $e', + duration: Toast.lengthShort, + gravity: Toast.bottom, + ); + _runtimeImage = null; + } + } + } + + @override + void call(ui.Canvas canvas) { + if (_runtimeImage == null) { + Toast.show( + 'ClipboardCommand.call: _runtimeImage is null. Cannot draw.', + duration: Toast.lengthShort, + gravity: Toast.bottom, + ); + + return; + } + + canvas.save(); + canvas.translate(offset.dx, offset.dy); + canvas.rotate(rotation); + canvas.scale(scale); + + final imageWidth = _runtimeImage!.width.toDouble(); + final imageHeight = _runtimeImage!.height.toDouble(); + + final src = ui.Rect.fromLTWH(0, 0, imageWidth, imageHeight); + final dst = ui.Rect.fromLTWH( + -imageWidth / 2, + -imageHeight / 2, + imageWidth, + imageHeight, + ); + final imagePaint = ui.Paint()..filterQuality = ui.FilterQuality.high; + canvas.drawImageRect(_runtimeImage!, src, dst, imagePaint); + canvas.restore(); + } + + @override + List get props => [ + paint, + imageData, + offset, + scale, + rotation, + version, + type, + ]; + + @override + Map toJson() => _$ClipboardCommandToJson(this); + + factory ClipboardCommand.fromJson(Map json) { + final int version = json['version'] as int? ?? Version.v1; + switch (version) { + case Version.v1: + return _$ClipboardCommandFromJson(json); + case Version.v2: + // For different versions of ClipboardCommand the deserialization + // has to be implemented manually. + // Autogenerated code can only be used for one version + default: + return _$ClipboardCommandFromJson(json); + } + } +} diff --git a/lib/core/commands/command_implementation/graphic/clipboard_command.g.dart b/lib/core/commands/command_implementation/graphic/clipboard_command.g.dart new file mode 100644 index 00000000..3ec5f175 --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/clipboard_command.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'clipboard_command.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ClipboardCommand _$ClipboardCommandFromJson(Map json) => + ClipboardCommand( + const PaintConverter().fromJson(json['paint'] as Map), + const Uint8ListBase64Converter().fromJson(json['imageData'] as String), + const OffsetConverter().fromJson(json['offset'] as Map), + (json['scale'] as num).toDouble(), + (json['rotation'] as num).toDouble(), + version: (json['version'] as num?)?.toInt(), + type: json['type'] as String? ?? SerializerType.CLIPBOARD_COMMAND, + ); + +Map _$ClipboardCommandToJson(ClipboardCommand instance) => + { + 'paint': const PaintConverter().toJson(instance.paint), + 'imageData': const Uint8ListBase64Converter().toJson(instance.imageData), + 'offset': const OffsetConverter().toJson(instance.offset), + 'scale': instance.scale, + 'rotation': instance.rotation, + 'version': instance.version, + 'type': instance.type, + }; diff --git a/lib/core/commands/command_implementation/graphic/delete_region_command.dart b/lib/core/commands/command_implementation/graphic/delete_region_command.dart new file mode 100644 index 00000000..fa70404d --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/delete_region_command.dart @@ -0,0 +1,63 @@ +// ignore_for_file: must_be_immutable + +import 'dart:ui' as ui; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; +import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/rect_converter.dart'; +import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; +import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; + +part 'delete_region_command.g.dart'; + +@JsonSerializable() +class DeleteRegionCommand extends GraphicCommand { + @RectConverter() + final ui.Rect region; + + final int version; + final String type; + + DeleteRegionCommand( + super.paint, + this.region, { + int? version, + this.type = SerializerType.DELETE_REGION_COMMAND, + }) : version = version ?? + VersionStrategyManager.strategy.getDeleteRegionCommandVersion(); + + @override + void call(ui.Canvas canvas) { + final clearPaint = ui.Paint() + ..blendMode = ui.BlendMode.clear + ..style = ui.PaintingStyle.fill; + canvas.drawRect(region, clearPaint); + } + + @override + List get props => [ + paint, + region, + version, + type, + ]; + + @override + Map toJson() => _$DeleteRegionCommandToJson(this); + + factory DeleteRegionCommand.fromJson(Map json) { + final int version = json['version'] as int? ?? Version.v1; + switch (version) { + case Version.v1: + return _$DeleteRegionCommandFromJson(json); + case Version.v2: + // For different versions of DeleteRegionCommand the deserialization + // has to be implemented manually. + // Autogenerated code can only be used for one version + default: + return _$DeleteRegionCommandFromJson(json); + } + + } +} diff --git a/lib/core/commands/command_implementation/graphic/delete_region_command.g.dart b/lib/core/commands/command_implementation/graphic/delete_region_command.g.dart new file mode 100644 index 00000000..640e33fb --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/delete_region_command.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'delete_region_command.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DeleteRegionCommand _$DeleteRegionCommandFromJson(Map json) => + DeleteRegionCommand( + const PaintConverter().fromJson(json['paint'] as Map), + const RectConverter().fromJson(json['region'] as Map), + version: (json['version'] as num?)?.toInt(), + type: json['type'] as String? ?? SerializerType.DELETE_REGION_COMMAND, + ); + +Map _$DeleteRegionCommandToJson( + DeleteRegionCommand instance) => + { + 'paint': const PaintConverter().toJson(instance.paint), + 'region': const RectConverter().toJson(instance.region), + 'version': instance.version, + 'type': instance.type, + }; diff --git a/lib/core/commands/command_manager/command_manager.dart b/lib/core/commands/command_manager/command_manager.dart index e8ca6e25..ffa1ce97 100644 --- a/lib/core/commands/command_manager/command_manager.dart +++ b/lib/core/commands/command_manager/command_manager.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:paintroid/core/commands/command_implementation/command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; @@ -116,6 +117,8 @@ class CommandManager { return ToolData.SHAPES; } else if (command.runtimeType == EllipseShapeCommand) { return ToolData.SHAPES; + } else if (command.runtimeType == ClipboardCommand) { + return ToolData.CLIPBOARD; } else if (command.runtimeType == TextCommand) { return ToolData.TEXT; } else if (command.runtimeType == SprayCommand) { diff --git a/lib/core/commands/command_painter.dart b/lib/core/commands/command_painter.dart index 8b768569..b2dd7ad9 100644 --- a/lib/core/commands/command_painter.dart +++ b/lib/core/commands/command_painter.dart @@ -6,6 +6,7 @@ import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/paint_provider.dart'; import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; +import 'package:paintroid/core/tools/implementation/clipboard_tool.dart'; import 'package:paintroid/core/tools/implementation/shapes_tool.dart'; import 'package:paintroid/core/tools/implementation/text_tool.dart'; import 'package:paintroid/core/tools/implementation/brush_tool.dart'; @@ -28,7 +29,8 @@ class CommandPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { if (currentTool.type != ToolType.SHAPES && - currentTool.type != ToolType.TEXT) { + currentTool.type != ToolType.TEXT && + currentTool.type != ToolType.CLIPBOARD) { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); } switch (currentTool.type) { @@ -40,6 +42,8 @@ class CommandPainter extends CustomPainter { ..drawShape(canvas, ref.read(paintProvider)) ..drawGuides(canvas); break; + case ToolType.CLIPBOARD: + (currentTool as ClipboardTool).paint(canvas, size); case ToolType.TEXT: (currentTool as TextTool).drawGuides(canvas, ref.read(paintProvider)); break; diff --git a/lib/core/json_serialization/converter/rect_converter.dart b/lib/core/json_serialization/converter/rect_converter.dart new file mode 100644 index 00000000..deca32d0 --- /dev/null +++ b/lib/core/json_serialization/converter/rect_converter.dart @@ -0,0 +1,27 @@ +import 'dart:ui'; + +import 'package:json_annotation/json_annotation.dart'; + +class RectConverter implements JsonConverter> { + const RectConverter(); + + @override + Rect fromJson(Map json) { + return Rect.fromLTRB( + (json['left'] as num).toDouble(), + (json['top'] as num).toDouble(), + (json['right'] as num).toDouble(), + (json['bottom'] as num).toDouble(), + ); + } + + @override + Map toJson(Rect rect) { + return { + 'left': rect.left, + 'top': rect.top, + 'right': rect.right, + 'bottom': rect.bottom, + }; + } +} diff --git a/lib/core/json_serialization/converter/uint8list_base64_converter.dart b/lib/core/json_serialization/converter/uint8list_base64_converter.dart new file mode 100644 index 00000000..5b63ccb8 --- /dev/null +++ b/lib/core/json_serialization/converter/uint8list_base64_converter.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:json_annotation/json_annotation.dart'; + +class Uint8ListBase64Converter implements JsonConverter { + const Uint8ListBase64Converter(); + + @override + Uint8List fromJson(String json) { + return base64Decode(json); + } + + @override + String toJson(Uint8List object) { + return base64Encode(object); + } +} diff --git a/lib/core/json_serialization/versioning/serializer_version.dart b/lib/core/json_serialization/versioning/serializer_version.dart index 1d7e8a5b..de4c3048 100644 --- a/lib/core/json_serialization/versioning/serializer_version.dart +++ b/lib/core/json_serialization/versioning/serializer_version.dart @@ -9,6 +9,8 @@ class SerializerVersion { static const int STAR_SHAPE_COMMAND_VERSION = Version.v1; static const int HEART_SHAPE_COMMAND_VERSION = Version.v1; static const int SPRAY_COMMAND_VERSION = Version.v1; + static const int CLIPBOARD_COMMAND_VERSION = Version.v1; + static const int DELETE_REGION_COMMAND_VERSION = Version.v1; } class Version { @@ -29,4 +31,6 @@ class SerializerType { static const String STAR_SHAPE_COMMAND = 'StarShapeCommand'; static const String HEART_SHAPE_COMMAND = 'HeartShapeCommand'; static const String SPRAY_COMMAND = 'SprayCommand'; + static const String CLIPBOARD_COMMAND = 'ClipboardCommand'; + static const String DELETE_REGION_COMMAND = 'DeleteRegionCommand'; } diff --git a/lib/core/json_serialization/versioning/version_strategy.dart b/lib/core/json_serialization/versioning/version_strategy.dart index 36d75330..2c3d843f 100644 --- a/lib/core/json_serialization/versioning/version_strategy.dart +++ b/lib/core/json_serialization/versioning/version_strategy.dart @@ -18,6 +18,10 @@ abstract class IVersionStrategy { int getHeartShapeCommandVersion(); int getSprayCommandVersion(); + + int getClipboardCommandVersion(); + + int getDeleteRegionCommandVersion(); } class ProductionVersionStrategy implements IVersionStrategy { @@ -51,6 +55,14 @@ class ProductionVersionStrategy implements IVersionStrategy { @override int getSprayCommandVersion() => SerializerVersion.SPRAY_COMMAND_VERSION; + + @override + int getClipboardCommandVersion() => + SerializerVersion.CLIPBOARD_COMMAND_VERSION; + + @override + int getDeleteRegionCommandVersion() => + SerializerVersion.DELETE_REGION_COMMAND_VERSION; } class VersionStrategyManager { diff --git a/lib/core/providers/object/clipboard_tool_options_state_provider.dart b/lib/core/providers/object/clipboard_tool_options_state_provider.dart new file mode 100644 index 00000000..9bb2e727 --- /dev/null +++ b/lib/core/providers/object/clipboard_tool_options_state_provider.dart @@ -0,0 +1,71 @@ +import 'dart:ui' as ui; + +import 'package:flutter/painting.dart'; +import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; +import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; +import 'package:paintroid/core/providers/object/canvas_painter_provider.dart'; +import 'package:paintroid/core/providers/object/tools/clipboard_tool_provider.dart'; +import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/providers/state/clipboard_tool_options_state_data.dart'; +import 'package:paintroid/core/tools/implementation/clipboard_tool.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'clipboard_tool_options_state_provider.g.dart'; + +@riverpod +class ClipboardToolOptionsStateProvider + extends _$ClipboardToolOptionsStateProvider { + @override + ClipboardToolOptionsStateData build() { + final clipboardTool = ref.read(clipboardToolProvider); + return ClipboardToolOptionsStateData( + hasCopiedContent: clipboardTool.copiedImageData != null, + ); + } + + ClipboardTool get _clipboardTool => ref.read(clipboardToolProvider); + + Future performCopy(ui.Image canvasImage) async { + await _clipboardTool.copy(canvasImage); + state = state.copyWith( + hasCopiedContent: _clipboardTool.copiedImageData != null); + _notifyUpdates(); + } + + Future performPaste(Paint paint) async { + if (_clipboardTool.copiedImageData != null) { + await _clipboardTool.paste(paint); + _notifyUpdates(updateCache: true); + } + } + + Future performCut(ui.Image canvasImage) async { + await _clipboardTool.copy(canvasImage); + final bool didCopy = _clipboardTool.copiedImageData != null; + state = state.copyWith(hasCopiedContent: didCopy); + + if (didCopy) { + final commandFactory = ref.read(commandFactoryProvider); + final commandManager = ref.read(commandManagerProvider); + final deleteCommand = commandFactory.createDeleteRegionCommand( + _clipboardTool.boundingBox.rect, + ); + commandManager.addGraphicCommand(deleteCommand); + } + + _notifyUpdates(updateCache: true); + } + + void clearClipboard() { + _clipboardTool.clearClipboard(); + state = state.copyWith(hasCopiedContent: false); + _notifyUpdates(); + } + + void _notifyUpdates({bool updateCache = false}) { + ref.read(canvasPainterProvider.notifier).repaint(); + if (updateCache) { + ref.read(canvasStateProvider.notifier).updateCachedImage(); + } + } +} diff --git a/lib/core/providers/object/clipboard_tool_options_state_provider.g.dart b/lib/core/providers/object/clipboard_tool_options_state_provider.g.dart new file mode 100644 index 00000000..55d6356a --- /dev/null +++ b/lib/core/providers/object/clipboard_tool_options_state_provider.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'clipboard_tool_options_state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$clipboardToolOptionsStateProviderHash() => + r'b90c7edc4d6236db516f945e10a927f3a5c5e128'; + +/// See also [ClipboardToolOptionsStateProvider]. +@ProviderFor(ClipboardToolOptionsStateProvider) +final clipboardToolOptionsStateProvider = AutoDisposeNotifierProvider< + ClipboardToolOptionsStateProvider, ClipboardToolOptionsStateData>.internal( + ClipboardToolOptionsStateProvider.new, + name: r'clipboardToolOptionsStateProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$clipboardToolOptionsStateProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ClipboardToolOptionsStateProvider + = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/core/providers/object/tools/clipboard_tool_provider.dart b/lib/core/providers/object/tools/clipboard_tool_provider.dart new file mode 100644 index 00000000..7222f3d3 --- /dev/null +++ b/lib/core/providers/object/tools/clipboard_tool_provider.dart @@ -0,0 +1,29 @@ +import 'dart:ui'; + +import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/tools/bounding_box.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; +import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; +import 'package:paintroid/core/tools/implementation/clipboard_tool.dart'; + +part 'clipboard_tool_provider.g.dart'; + +@Riverpod(keepAlive: true) +class ClipboardToolProvider extends _$ClipboardToolProvider { + @override + ClipboardTool build() { + Rect initialBoundingBox = Rect.fromCenter( + center: ref.read(canvasStateProvider).size.center(Offset.zero), + width: 300, + height: 300, + ); + return ClipboardTool( + commandManager: ref.watch(commandManagerProvider), + commandFactory: ref.watch(commandFactoryProvider), + boundingBox: BoundingBox.fromRect(initialBoundingBox), + type: ToolType.CLIPBOARD, + ); + } +} diff --git a/lib/core/providers/object/tools/clipboard_tool_provider.g.dart b/lib/core/providers/object/tools/clipboard_tool_provider.g.dart new file mode 100644 index 00000000..08eeb566 --- /dev/null +++ b/lib/core/providers/object/tools/clipboard_tool_provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'clipboard_tool_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$clipboardToolProviderHash() => + r'f2ad4919b409cdbba8aea66d24e5b04327a83603'; + +/// See also [ClipboardToolProvider]. +@ProviderFor(ClipboardToolProvider) +final clipboardToolProvider = + NotifierProvider.internal( + ClipboardToolProvider.new, + name: r'clipboardToolProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$clipboardToolProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ClipboardToolProvider = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/core/providers/state/canvas_state_provider.dart b/lib/core/providers/state/canvas_state_provider.dart index 19741e96..b0a8f31e 100644 --- a/lib/core/providers/state/canvas_state_provider.dart +++ b/lib/core/providers/state/canvas_state_provider.dart @@ -5,14 +5,16 @@ import 'package:flutter/widgets.dart' as widgets; import 'package:paintroid/core/commands/command_implementation/command.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; +import 'package:paintroid/core/models/loggable_mixin.dart'; import 'package:paintroid/core/providers/object/device_service.dart'; import 'package:paintroid/core/providers/state/canvas_state_data.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:toast/toast.dart'; part 'canvas_state_provider.g.dart'; @Riverpod(keepAlive: true) -class CanvasStateProvider extends _$CanvasStateProvider { +class CanvasStateProvider extends _$CanvasStateProvider with LoggableMixin { Size initialCanvasSize = Size.zero; @override @@ -64,8 +66,26 @@ class CanvasStateProvider extends _$CanvasStateProvider { } Future resetCanvasWithNewCommands(Iterable commands) async { + final List preparedCommands = []; + for (final command in commands) { + try { + await command.prepareForRuntime(); + preparedCommands.add(command); + } catch (e) { + Toast.show( + 'Error preparing command ${command.runtimeType} during resetCanvasWithNewCommands: $e', + duration: Toast.lengthShort, + gravity: Toast.bottom, + ); + } + } + state.commandManager.clearRedoStack(); - state.commandManager.clearUndoStack(newCommands: commands); + + state.commandManager.clearUndoStack(newCommands: preparedCommands); + if (preparedCommands.isEmpty) { + state = state.copyWith(cachedImage: null); + } if (commands.isEmpty) { state = state.copyWith(cachedImage: null, isCachingCommand: false); } else { diff --git a/lib/core/providers/state/canvas_state_provider.g.dart b/lib/core/providers/state/canvas_state_provider.g.dart index 7c6225f4..a4909b02 100644 --- a/lib/core/providers/state/canvas_state_provider.g.dart +++ b/lib/core/providers/state/canvas_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'canvas_state_provider.dart'; // ************************************************************************** String _$canvasStateProviderHash() => - r'd378ae0f1a05759a0c31ccfd77ce42649e15c6a1'; + r'18b157df0a616edc9cf9a0946ebe80a7119a9f38'; /// See also [CanvasStateProvider]. @ProviderFor(CanvasStateProvider) diff --git a/lib/core/providers/state/clipboard_tool_options_state_data.dart b/lib/core/providers/state/clipboard_tool_options_state_data.dart new file mode 100644 index 00000000..e851d836 --- /dev/null +++ b/lib/core/providers/state/clipboard_tool_options_state_data.dart @@ -0,0 +1,10 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'clipboard_tool_options_state_data.freezed.dart'; + +@freezed +class ClipboardToolOptionsStateData with _$ClipboardToolOptionsStateData { + const factory ClipboardToolOptionsStateData({ + @Default(false) bool hasCopiedContent, + }) = _ClipboardToolOptionsStateData; +} diff --git a/lib/core/providers/state/clipboard_tool_options_state_data.freezed.dart b/lib/core/providers/state/clipboard_tool_options_state_data.freezed.dart new file mode 100644 index 00000000..450d7ab8 --- /dev/null +++ b/lib/core/providers/state/clipboard_tool_options_state_data.freezed.dart @@ -0,0 +1,157 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'clipboard_tool_options_state_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ClipboardToolOptionsStateData { + bool get hasCopiedContent => throw _privateConstructorUsedError; + + /// Create a copy of ClipboardToolOptionsStateData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ClipboardToolOptionsStateDataCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ClipboardToolOptionsStateDataCopyWith<$Res> { + factory $ClipboardToolOptionsStateDataCopyWith( + ClipboardToolOptionsStateData value, + $Res Function(ClipboardToolOptionsStateData) then) = + _$ClipboardToolOptionsStateDataCopyWithImpl<$Res, + ClipboardToolOptionsStateData>; + @useResult + $Res call({bool hasCopiedContent}); +} + +/// @nodoc +class _$ClipboardToolOptionsStateDataCopyWithImpl<$Res, + $Val extends ClipboardToolOptionsStateData> + implements $ClipboardToolOptionsStateDataCopyWith<$Res> { + _$ClipboardToolOptionsStateDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ClipboardToolOptionsStateData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hasCopiedContent = null, + }) { + return _then(_value.copyWith( + hasCopiedContent: null == hasCopiedContent + ? _value.hasCopiedContent + : hasCopiedContent // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ClipboardToolOptionsStateDataImplCopyWith<$Res> + implements $ClipboardToolOptionsStateDataCopyWith<$Res> { + factory _$$ClipboardToolOptionsStateDataImplCopyWith( + _$ClipboardToolOptionsStateDataImpl value, + $Res Function(_$ClipboardToolOptionsStateDataImpl) then) = + __$$ClipboardToolOptionsStateDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({bool hasCopiedContent}); +} + +/// @nodoc +class __$$ClipboardToolOptionsStateDataImplCopyWithImpl<$Res> + extends _$ClipboardToolOptionsStateDataCopyWithImpl<$Res, + _$ClipboardToolOptionsStateDataImpl> + implements _$$ClipboardToolOptionsStateDataImplCopyWith<$Res> { + __$$ClipboardToolOptionsStateDataImplCopyWithImpl( + _$ClipboardToolOptionsStateDataImpl _value, + $Res Function(_$ClipboardToolOptionsStateDataImpl) _then) + : super(_value, _then); + + /// Create a copy of ClipboardToolOptionsStateData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hasCopiedContent = null, + }) { + return _then(_$ClipboardToolOptionsStateDataImpl( + hasCopiedContent: null == hasCopiedContent + ? _value.hasCopiedContent + : hasCopiedContent // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$ClipboardToolOptionsStateDataImpl + implements _ClipboardToolOptionsStateData { + const _$ClipboardToolOptionsStateDataImpl({this.hasCopiedContent = false}); + + @override + @JsonKey() + final bool hasCopiedContent; + + @override + String toString() { + return 'ClipboardToolOptionsStateData(hasCopiedContent: $hasCopiedContent)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ClipboardToolOptionsStateDataImpl && + (identical(other.hasCopiedContent, hasCopiedContent) || + other.hasCopiedContent == hasCopiedContent)); + } + + @override + int get hashCode => Object.hash(runtimeType, hasCopiedContent); + + /// Create a copy of ClipboardToolOptionsStateData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ClipboardToolOptionsStateDataImplCopyWith< + _$ClipboardToolOptionsStateDataImpl> + get copyWith => __$$ClipboardToolOptionsStateDataImplCopyWithImpl< + _$ClipboardToolOptionsStateDataImpl>(this, _$identity); +} + +abstract class _ClipboardToolOptionsStateData + implements ClipboardToolOptionsStateData { + const factory _ClipboardToolOptionsStateData({final bool hasCopiedContent}) = + _$ClipboardToolOptionsStateDataImpl; + + @override + bool get hasCopiedContent; + + /// Create a copy of ClipboardToolOptionsStateData + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ClipboardToolOptionsStateDataImplCopyWith< + _$ClipboardToolOptionsStateDataImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/core/providers/state/toolbox_state_provider.dart b/lib/core/providers/state/toolbox_state_provider.dart index bd4f0dd6..e051305a 100644 --- a/lib/core/providers/state/toolbox_state_provider.dart +++ b/lib/core/providers/state/toolbox_state_provider.dart @@ -4,6 +4,7 @@ import 'package:paintroid/core/commands/command_manager/command_manager_provider import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/object/canvas_painter_provider.dart'; import 'package:paintroid/core/providers/object/tools/brush_tool_provider.dart'; +import 'package:paintroid/core/providers/object/tools/clipboard_tool_provider.dart'; import 'package:paintroid/core/providers/object/tools/eraser_tool_provider.dart'; import 'package:paintroid/core/providers/object/tools/hand_tool_provider.dart'; import 'package:paintroid/core/providers/object/tools/line_tool_provider.dart'; @@ -85,6 +86,10 @@ class ToolBoxStateProvider extends _$ToolBoxStateProvider { (state.currentTool as SprayTool).updateSprayRadius(currentStrokeWidth); ref.read(paintProvider.notifier).updateStrokeWidth(SPRAY_TOOL_RADIUS); break; + case ToolType.CLIPBOARD: + state = state.copyWith(currentTool: ref.read(clipboardToolProvider)); + ref.read(canvasPainterProvider.notifier).repaint(); + break; default: state = state.copyWith(currentTool: ref.read(brushToolProvider)); break; diff --git a/lib/core/providers/state/toolbox_state_provider.g.dart b/lib/core/providers/state/toolbox_state_provider.g.dart index 4ad281ca..d75b380b 100644 --- a/lib/core/providers/state/toolbox_state_provider.g.dart +++ b/lib/core/providers/state/toolbox_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'toolbox_state_provider.dart'; // ************************************************************************** String _$toolBoxStateProviderHash() => - r'287ea0cf348a191887971afc63b94e77ffd3d24b'; + r'206acf9ae4e9afd5aead49ae432264b37e7c63ab'; /// See also [ToolBoxStateProvider]. @ProviderFor(ToolBoxStateProvider) diff --git a/lib/core/tools/implementation/clipboard_tool.dart b/lib/core/tools/implementation/clipboard_tool.dart new file mode 100644 index 00000000..89741f64 --- /dev/null +++ b/lib/core/tools/implementation/clipboard_tool.dart @@ -0,0 +1,156 @@ +import 'dart:ui' as ui; +import 'dart:math' as dart_math; +import 'package:flutter/material.dart'; +import 'package:paintroid/core/enums/bounding_box_action.dart'; +import 'package:paintroid/core/tools/bounding_box.dart'; +import 'package:paintroid/core/tools/tool.dart'; + +class ClipboardTool extends Tool { + final BoundingBox boundingBox; + ui.Image? copiedImageData; + bool _isInteracting = false; + + ClipboardTool({ + required super.commandManager, + required super.commandFactory, + required this.boundingBox, + required super.type, + super.hasAddFunctionality = false, + super.hasFinalizeFunctionality = false, + }); + + Future copy(ui.Image fullCanvasImage) async { + copiedImageData = null; + final rect = boundingBox.rect; + if (rect.width <= 0 || rect.height <= 0) return; + final recorder = ui.PictureRecorder(); + final canvas = Canvas(recorder); + canvas.translate(-rect.left, -rect.top); + canvas.drawImage(fullCanvasImage, ui.Offset.zero, Paint()); + final rawImage = await recorder.endRecording().toImage( + rect.width.round(), + rect.height.round(), + ); + copiedImageData = rawImage; + } + + Future paste(Paint paint) async { + final image = copiedImageData; + if (image == null) return; + final byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) return; + final bytes = byteData.buffer.asUint8List(); + + final pasteOffset = boundingBox.rect.center; + final pasteRotation = boundingBox.angle; + + double pasteScale = 1.0; + if (image.width > 0 && + image.height > 0 && + boundingBox.rect.width > 0 && + boundingBox.rect.height > 0) { + final double widthScale = boundingBox.rect.width / image.width.toDouble(); + final double heightScale = + boundingBox.rect.height / image.height.toDouble(); + pasteScale = dart_math.min(widthScale, heightScale); + } else if (image.width > 0 && boundingBox.rect.width > 0) { + pasteScale = boundingBox.rect.width / image.width.toDouble(); + } + + final command = commandFactory.createClipboardCommand( + paint, + bytes, + pasteOffset, + pasteScale, + pasteRotation, + ); + await command.prepareForRuntime(); + commandManager.addGraphicCommand(command); + } + + void clearClipboard() { + copiedImageData = null; + } + + @override + void onDown(ui.Offset point, Paint paint) { + boundingBox.determineAction(point); + _isInteracting = boundingBox.currentAction != BoundingBoxAction.none; + } + + @override + void onDrag(ui.Offset point, Paint paint) { + if (_isInteracting) boundingBox.updateDrag(point); + } + + @override + void onUp(ui.Offset point, Paint paint) { + if (_isInteracting) { + boundingBox.endDrag(); + _isInteracting = false; + } + } + + @override + void onCancel() { + if (_isInteracting) { + boundingBox.endDrag(); + _isInteracting = false; + } + } + + void paint(Canvas canvas, Size size) { + boundingBox.drawGuides(canvas); + final image = copiedImageData; + if (image == null) return; + if (image.width == 0 || image.height == 0) return; + + final rect = boundingBox.rect; + if (rect.width <= 0 || rect.height <= 0) return; + + double previewScale = 1.0; + if (image.width > 0 && + image.height > 0 && + rect.width > 0 && + rect.height > 0) { + final double widthScale = rect.width / image.width.toDouble(); + final double heightScale = rect.height / image.height.toDouble(); + previewScale = dart_math.min(widthScale, heightScale); + } + + final double scaledWidth = image.width.toDouble() * previewScale; + final double scaledHeight = image.height.toDouble() * previewScale; + + final src = ui.Rect.fromLTWH( + 0, + 0, + image.width.toDouble(), + image.height.toDouble(), + ); + final dst = ui.Rect.fromLTWH( + -scaledWidth / 2, + -scaledHeight / 2, + scaledWidth, + scaledHeight, + ); + + canvas.save(); + canvas.translate(rect.center.dx, rect.center.dy); + canvas.rotate(boundingBox.angle); + final paintImage = Paint()..filterQuality = FilterQuality.high; + canvas.drawImageRect(image, src, dst, paintImage); + canvas.restore(); + } + + @override + void onCheckmark(Paint paint) {} + + @override + void onPlus() {} + + @override + void onUndo() => commandManager.undo(); + + @override + void onRedo() => commandManager.redo(); +} diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/clipboard_tool_options.dart b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/clipboard_tool_options.dart new file mode 100644 index 00000000..947f0144 --- /dev/null +++ b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/clipboard_tool_options.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:paintroid/core/providers/object/clipboard_tool_options_state_provider.dart'; +import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/providers/state/paint_provider.dart'; +import 'package:paintroid/ui/shared/custom_action_chip.dart'; +import 'package:paintroid/ui/theme/data/paintroid_theme.dart'; +import 'package:paintroid/ui/utils/toast_utils.dart'; + +class ClipboardToolOptions extends ConsumerWidget { + const ClipboardToolOptions({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final clipboardOptionsState = ref.watch(clipboardToolOptionsStateProvider); + final clipboardOptionsNotifier = + ref.read(clipboardToolOptionsStateProvider.notifier); + + final canvasImage = + ref.watch(canvasStateProvider.select((s) => s.cachedImage)); + final paint = ref.watch(paintProvider); + final shadowColor = PaintroidTheme.of(context).shadowColor; + + return Column( + children: [ + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomActionChip( + chipIcon: Icon(Icons.copy, color: shadowColor), + hint: 'Copy selection', + chipBackgroundColor: Colors.white, + onPressed: () async { + if (canvasImage != null) { + await clipboardOptionsNotifier.performCopy(canvasImage); + } + }, + ), + const SizedBox(width: 16), + CustomActionChip( + chipIcon: Icon(Icons.content_cut, color: shadowColor), + hint: 'Cut selection', + chipBackgroundColor: Colors.white, + onPressed: () async { + if (canvasImage != null) { + await clipboardOptionsNotifier.performCut(canvasImage); + } + }, + ), + const SizedBox(width: 16), + CustomActionChip( + chipIcon: Icon(Icons.paste, color: shadowColor), + hint: 'Paste clipboard', + chipBackgroundColor: Colors.white, + onPressed: clipboardOptionsState.hasCopiedContent + ? () async { + await clipboardOptionsNotifier.performPaste(paint); + } + : () { + ToastUtils.showShortToast(message: 'Nothing to paste!'); + }, + ), + ], + ), + ], + ); + } +} diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart index ad8b99f2..d1343033 100644 --- a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart +++ b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/state/tool_options_visibility_state_provider.dart'; import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; +import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/clipboard_tool_options.dart'; import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/shapes_tool_options.dart'; import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/spray_tool_options.dart'; import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/stroke_tool_options.dart'; @@ -36,6 +37,7 @@ class ToolOptions extends ConsumerWidget { ToolType.LINE => const StrokeToolOptions(), ToolType.SHAPES => const ShapesToolOptions(), ToolType.SPRAY => const SprayToolOptions(), + ToolType.CLIPBOARD => const ClipboardToolOptions(), ToolType.TEXT => const TextToolOptions(), _ => Container(), }, diff --git a/test/integration/clipboard_tool_test.dart b/test/integration/clipboard_tool_test.dart new file mode 100644 index 00000000..7f5bc44e --- /dev/null +++ b/test/integration/clipboard_tool_test.dart @@ -0,0 +1,249 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:paintroid/core/tools/tool_data.dart'; +import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/clipboard_tool_options.dart'; +import 'package:paintroid/ui/shared/custom_action_chip.dart'; + +import '../utils/ui_interaction.dart'; +import '../utils/clipboard_tool_util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + const String testIDStr = String.fromEnvironment('id', defaultValue: '-1'); + final testID = int.tryParse(testIDStr) ?? testIDStr; + + group('Clipboard Tool Integration Tests', () { + if (testID == -1 || testID == 0) { + testWidgets( + '[CLIPBOARD_TOOL_TEST_ID_0]: Copy and Paste operations work correctly', + (WidgetTester tester) async { + await ClipboardIntegrationTestUtils.launchAppAndInit(tester); + + ui.Image? imageBeforeDraw = + await ClipboardIntegrationTestUtils.getCanvasImage(tester); + + await ClipboardIntegrationTestUtils.drawSomethingOnCanvas(tester); + ui.Image? imageAfterDraw = + await ClipboardIntegrationTestUtils.getCanvasImage(tester); + expect(imageAfterDraw, isNotNull, + reason: 'Canvas image should be available after draw'); + expect( + imageAfterDraw.hashCode, isNot(equals(imageBeforeDraw?.hashCode)), + reason: 'Canvas should change after drawing a shape'); + + await ClipboardIntegrationTestUtils.selectTool( + tester, ToolData.CLIPBOARD.name); + expect(find.byType(ClipboardToolOptions), findsOneWidget, + reason: 'Clipboard options should be visible'); + + final copyButton = find.widgetWithIcon(CustomActionChip, Icons.copy); + expect(copyButton, findsOneWidget, + reason: 'Copy button should be present'); + await tester.tap(copyButton); + await tester.pumpAndSettle(); + + expect( + ClipboardIntegrationTestUtils.getHasCopiedContent(tester), isTrue, + reason: 'hasCopiedContent should be true after copy'); + + final pasteButton = find.widgetWithIcon(CustomActionChip, Icons.paste); + expect(pasteButton, findsOneWidget, + reason: 'Paste button should be present'); + await tester.tap(pasteButton); + await tester.pumpAndSettle(); + + ui.Image? imageAfterPaste = + await ClipboardIntegrationTestUtils.getCanvasImage(tester); + expect(imageAfterPaste, isNotNull, + reason: 'Canvas image should be available after paste'); + expect(imageAfterPaste.hashCode, isNot(equals(imageAfterDraw.hashCode)), + reason: 'Canvas should change after paste'); + }); + } + + if (testID == -1 || testID == 1) { + testWidgets( + '[CLIPBOARD_TOOL_TEST_ID_1]: Cut and Paste operations work correctly', + (WidgetTester tester) async { + await ClipboardIntegrationTestUtils.launchAppAndInit(tester); + + ui.Image? imageBeforeDraw = + await ClipboardIntegrationTestUtils.getCanvasImage(tester); + + await ClipboardIntegrationTestUtils.drawSomethingOnCanvas(tester); + ui.Image? imageAfterDraw = + await ClipboardIntegrationTestUtils.getCanvasImage(tester); + expect(imageAfterDraw, isNotNull, + reason: 'Canvas image should be available after draw'); + expect( + imageAfterDraw.hashCode, isNot(equals(imageBeforeDraw?.hashCode)), + reason: 'Canvas should change after drawing a shape'); + + await ClipboardIntegrationTestUtils.selectTool( + tester, ToolData.CLIPBOARD.name); + expect(find.byType(ClipboardToolOptions), findsOneWidget, + reason: 'Clipboard options should be visible'); + + final cutButton = + find.widgetWithIcon(CustomActionChip, Icons.content_cut); + expect(cutButton, findsOneWidget, + reason: 'Cut button should be present'); + await tester.tap(cutButton); + await tester.pumpAndSettle(); + + expect( + ClipboardIntegrationTestUtils.getHasCopiedContent(tester), isTrue, + reason: 'hasCopiedContent should be true after cut'); + + ui.Image? imageAfterCut = + await ClipboardIntegrationTestUtils.getCanvasImage(tester); + expect(imageAfterCut, isNotNull, + reason: 'Canvas image should be available after cut'); + expect(imageAfterCut.hashCode, isNot(equals(imageAfterDraw.hashCode)), + reason: 'Canvas should change after cut (shape removed)'); + + final pasteButton = find.widgetWithIcon(CustomActionChip, Icons.paste); + expect(pasteButton, findsOneWidget, + reason: 'Paste button should be present'); + await tester.tap(pasteButton); + await tester.pumpAndSettle(); + + ui.Image? imageAfterPaste = + await ClipboardIntegrationTestUtils.getCanvasImage(tester); + expect(imageAfterPaste, isNotNull, + reason: 'Canvas image should be available after paste'); + expect(imageAfterPaste.hashCode, isNot(equals(imageAfterCut.hashCode)), + reason: 'Canvas should change after paste (shape re-added)'); + }); + } + + if (testID == -1 || testID == 2) { + testWidgets( + '[CLIPBOARD_TOOL_TEST_ID_2]: Paste with initially empty clipboard does nothing', + (WidgetTester tester) async { + await ClipboardIntegrationTestUtils.launchAppAndInit(tester); + ui.Image? imageBeforeAction = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: true); + int initialUndoStackLength = UIInteraction.getUndoStackLength(); + + await ClipboardIntegrationTestUtils.selectTool( + tester, ToolData.CLIPBOARD.name); + expect(find.byType(ClipboardToolOptions), findsOneWidget); + expect( + ClipboardIntegrationTestUtils.getHasCopiedContent(tester), isFalse, + reason: 'Initially, clipboard should be empty'); + + final pasteButton = find.widgetWithIcon(CustomActionChip, Icons.paste); + expect(pasteButton, findsOneWidget); + await tester.tap(pasteButton); + await tester.pumpAndSettle(); + + ui.Image? imageAfterPasteAttempt = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: false); + int finalUndoStackLength = UIInteraction.getUndoStackLength(); + + expect( + ClipboardIntegrationTestUtils.getHasCopiedContent(tester), isFalse, + reason: + 'Clipboard should still be empty after attempting to paste nothing'); + expect(imageAfterPasteAttempt?.hashCode, + equals(imageBeforeAction?.hashCode), + reason: + 'Canvas image instance should not change after a no-op paste attempt on an empty clipboard'); + expect(finalUndoStackLength, equals(initialUndoStackLength), + reason: + 'Undo stack should not change after attempting to paste nothing'); + }); + } + + if (testID == -1 || testID == 3) { + testWidgets( + '[CLIPBOARD_TOOL_TEST_ID_3]: Multiple Paste operations of the same content are idempotent on canvas', + (WidgetTester tester) async { + await ClipboardIntegrationTestUtils.launchAppAndInit(tester); + await ClipboardIntegrationTestUtils.drawSomethingOnCanvas(tester); + ui.Image? imageAfterDraw = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: true); + + await ClipboardIntegrationTestUtils.selectTool( + tester, ToolData.CLIPBOARD.name); + final copyButton = find.widgetWithIcon(CustomActionChip, Icons.copy); + await tester.tap(copyButton); + await tester.pumpAndSettle(); + expect( + ClipboardIntegrationTestUtils.getHasCopiedContent(tester), isTrue); + + final pasteButton = find.widgetWithIcon(CustomActionChip, Icons.paste); + await tester.tap(pasteButton); + await tester.pumpAndSettle(); + ui.Image? imageAfterFirstPaste = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: true); + expect(imageAfterFirstPaste?.hashCode, + isNot(equals(imageAfterDraw?.hashCode)), + reason: 'Canvas should change after first paste'); + + await tester.tap(pasteButton); + await tester.pumpAndSettle(); + ui.Image? imageAfterSecondPaste = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: true); + + expect(imageAfterSecondPaste?.hashCode, + isNot(equals(imageAfterFirstPaste?.hashCode)), + reason: + 'Canvas should change after pasting another instance of the same content (new command is added)'); + }); + } + + if (testID == -1 || testID == 4) { + testWidgets( + '[CLIPBOARD_TOOL_TEST_ID_4]: Pasting after copy and further drawing uses original copied content', + (WidgetTester tester) async { + await ClipboardIntegrationTestUtils.launchAppAndInit(tester); + + await ClipboardIntegrationTestUtils.drawSomethingOnCanvas(tester); + ui.Image? imageAfterDrawingA = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: true); + + await ClipboardIntegrationTestUtils.selectTool( + tester, ToolData.CLIPBOARD.name); + final copyButton = find.widgetWithIcon(CustomActionChip, Icons.copy); + await tester.tap(copyButton); + await tester.pumpAndSettle(); + expect( + ClipboardIntegrationTestUtils.getHasCopiedContent(tester), isTrue); + + await ClipboardIntegrationTestUtils.drawSomethingElseOnCanvas(tester); + ui.Image? imageAfterDrawingB = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: true); + expect(imageAfterDrawingB?.hashCode, + isNot(equals(imageAfterDrawingA?.hashCode)), + reason: 'Canvas should change after drawing item B'); + + await ClipboardIntegrationTestUtils.selectTool( + tester, ToolData.CLIPBOARD.name); + final pasteButton = find.widgetWithIcon(CustomActionChip, Icons.paste); + await tester.tap(pasteButton); + await tester.pumpAndSettle(); + + ui.Image? imageAfterPaste = + await ClipboardIntegrationTestUtils.getCanvasImage(tester, + forceUpdate: true); + expect(imageAfterPaste?.hashCode, + isNot(equals(imageAfterDrawingB?.hashCode)), + reason: + 'Canvas should change after pasting, and it should be different from just having A and B'); + }); + } + }); +} diff --git a/test/integration/shapes_tool_test.dart b/test/integration/shapes_tool_test.dart index d3b2fb86..2a28c339 100644 --- a/test/integration/shapes_tool_test.dart +++ b/test/integration/shapes_tool_test.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:paintroid/app.dart'; import 'package:paintroid/core/tools/tool_data.dart'; +import 'package:paintroid/core/utils/color_utils.dart'; + import '../utils/test_utils.dart'; void main() { @@ -49,7 +50,8 @@ void main() { } if (testID == -1 || testID == 1) { - testWidgets('[SHAPES_TOOL]: test ellipse shape', (WidgetTester tester) async { + testWidgets('[SHAPES_TOOL]: test ellipse shape', + (WidgetTester tester) async { UIInteraction.initialize(tester); await tester.pumpWidget(sut); await UIInteraction.createNewImage(); @@ -264,4 +266,37 @@ void main() { centerColor.toARGB32(), UIInteraction.getCurrentColor().toARGB32()); }); } + + if (testID == -1 || testID == 2) { + testWidgets('[SHAPES_TOOL]: test ellipse shape', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + await UIInteraction.selectTool(ToolData.SHAPES.name); + await UIInteraction.selectShapesToolShapeType( + WidgetFinder.ellipseShapeTypeChip); + + final (left, top, right, bottom) = + await UIInteraction.getEllipseShapeColors(); + + expect(left.toValue(), Colors.transparent.toValue()); + expect(top.toValue(), Colors.transparent.toValue()); + expect(right.toValue(), Colors.transparent.toValue()); + expect(bottom.toValue(), Colors.transparent.toValue()); + + await UIInteraction.tapAt(CanvasPosition.center); + await UIInteraction.clickCheckmark(); + + final (leftAfter, topAfter, rightAfter, bottomAfter) = + await UIInteraction.getEllipseShapeColors(); + + final currentColor = UIInteraction.getCurrentColor(); + + expect(leftAfter.toValue(), currentColor.toValue()); + expect(topAfter.toValue(), currentColor.toValue()); + expect(rightAfter.toValue(), currentColor.toValue()); + expect(bottomAfter.toValue(), currentColor.toValue()); + }); + } } diff --git a/test/unit/serialization/utils/dummy_version_strategy.dart b/test/unit/serialization/utils/dummy_version_strategy.dart index 2c5246b6..e1a78232 100644 --- a/test/unit/serialization/utils/dummy_version_strategy.dart +++ b/test/unit/serialization/utils/dummy_version_strategy.dart @@ -10,6 +10,8 @@ class DummyVersionStrategy implements IVersionStrategy { final int starShapeCommandVersion; final int heartShapeCommandVersion; final int sprayCommandVersion; + final int clipboardCommandVersion; + final int deleteRegionCommandVersion; final int textCommandVersion; DummyVersionStrategy({ @@ -24,6 +26,9 @@ class DummyVersionStrategy implements IVersionStrategy { this.heartShapeCommandVersion = SerializerVersion.HEART_SHAPE_COMMAND_VERSION, this.sprayCommandVersion = SerializerVersion.SPRAY_COMMAND_VERSION, + this.clipboardCommandVersion = SerializerVersion.CLIPBOARD_COMMAND_VERSION, + this.deleteRegionCommandVersion = + SerializerVersion.DELETE_REGION_COMMAND_VERSION, this.textCommandVersion = SerializerVersion.TEXT_COMMAND_VERSION, }); @@ -51,6 +56,12 @@ class DummyVersionStrategy implements IVersionStrategy { @override int getSprayCommandVersion() => sprayCommandVersion; + @override + int getClipboardCommandVersion() => clipboardCommandVersion; + + @override + int getDeleteRegionCommandVersion() => deleteRegionCommandVersion; + @override int getTextCommandVersion() => textCommandVersion; } diff --git a/test/unit/tools/bounding_box_test.dart b/test/unit/tools/bounding_box_test.dart index 13eceaa7..615c4d7b 100644 --- a/test/unit/tools/bounding_box_test.dart +++ b/test/unit/tools/bounding_box_test.dart @@ -1,333 +1,3 @@ -// import 'dart:ui'; -// import 'dart:math' as math; -// -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:paintroid/core/enums/bounding_box_corners.dart'; -// import 'package:paintroid/core/extensions/offset_extension.dart'; -// import 'package:paintroid/core/tools/bounding_box.dart'; -// -// void main() { -// double epsilon = 0.000000001; -// late BoundingBox boundingBox; -// -// const Offset topLeftInitial = Offset(0, 0); -// const Offset topRightInitial = Offset(200, 0); -// const Offset bottomLeftInitial = Offset(0, 200); -// const Offset bottomRightInitial = Offset(200, 200); -// -// setUp(() { -// boundingBox = BoundingBox( -// topLeftInitial, topRightInitial, bottomLeftInitial, bottomRightInitial); -// }); -// -// group('resetToDefaultsAroundCenter', () { -// test( -// 'should reset to default dimensions and zero angle around a new center', -// () { -// const newCenter = Offset(300, 300); -// const defaultWidth = 150.0; -// const defaultHeight = 150.0; -// boundingBox.resetToDefaultsAroundCenter(newCenter: newCenter); -// -// expect(boundingBox.center.dx, closeTo(newCenter.dx, epsilon)); -// expect(boundingBox.center.dy, closeTo(newCenter.dy, epsilon)); -// expect(boundingBox.width, closeTo(defaultWidth, epsilon)); -// expect(boundingBox.height, closeTo(defaultHeight, epsilon)); -// expect(boundingBox.angle, closeTo(0.0, epsilon)); -// expect(boundingBox.activeCorner, BoundingBoxCorner.none); -// -// final expectedTopLeft = Offset( -// newCenter.dx - defaultWidth / 2, newCenter.dy - defaultHeight / 2); -// expect(boundingBox.topLeft.dx, closeTo(expectedTopLeft.dx, epsilon)); -// expect(boundingBox.topLeft.dy, closeTo(expectedTopLeft.dy, epsilon)); -// }); -// -// test('should reset to specified dimensions and zero angle', () { -// const newCenter = Offset(50, 50); -// const newWidth = 100.0; -// const newHeight = 300.0; -// boundingBox.resetToDefaultsAroundCenter( -// newCenter: newCenter, -// defaultWidth: newWidth, -// defaultHeight: newHeight); -// -// expect(boundingBox.center.dx, closeTo(newCenter.dx, epsilon)); -// expect(boundingBox.center.dy, closeTo(newCenter.dy, epsilon)); -// expect(boundingBox.width, closeTo(newWidth, epsilon)); -// expect(boundingBox.height, closeTo(newHeight, epsilon)); -// expect(boundingBox.angle, closeTo(0.0, epsilon)); -// expect(boundingBox.activeCorner, BoundingBoxCorner.none); -// }); -// -// test('should reset to specified dimensions and non-zero angle', () { -// const newCenter = Offset(10, 20); -// const newWidth = 200.0; -// const newHeight = 100.0; -// const newAngle = math.pi / 4; -// boundingBox.resetToDefaultsAroundCenter( -// newCenter: newCenter, -// defaultWidth: newWidth, -// defaultHeight: newHeight, -// defaultAngle: newAngle, -// ); -// -// expect(boundingBox.center.dx, closeTo(newCenter.dx, epsilon)); -// expect(boundingBox.center.dy, closeTo(newCenter.dy, epsilon)); -// expect(boundingBox.angle, closeTo(newAngle, epsilon)); -// expect(boundingBox.width, closeTo(newWidth, epsilon)); -// expect(boundingBox.height, closeTo(newHeight, epsilon)); -// expect(boundingBox.activeCorner, BoundingBoxCorner.none); -// -// final halfWidth = newWidth / 2; -// final halfHeight = newHeight / 2; -// final baseTopLeft = -// Offset(newCenter.dx - halfWidth, newCenter.dy - halfHeight); -// final vector = baseTopLeft - newCenter; -// final rotatedTopLeft = newCenter + -// Offset( -// vector.dx * math.cos(newAngle) - vector.dy * math.sin(newAngle), -// vector.dx * math.sin(newAngle) + vector.dy * math.cos(newAngle), -// ); -// expect(boundingBox.topLeft.dx, closeTo(rotatedTopLeft.dx, epsilon)); -// expect(boundingBox.topLeft.dy, closeTo(rotatedTopLeft.dy, epsilon)); -// }); -// -// test('activeCorner should be none after reset', () { -// boundingBox.activeCorner = BoundingBoxCorner.topLeft; -// boundingBox.resetToDefaultsAroundCenter( -// newCenter: const Offset(100, 100)); -// expect(boundingBox.activeCorner, BoundingBoxCorner.none); -// }); -// }); -// -// group('update (delegated operations)', () { -// test( -// 'should rotate correctly when activeCorner is topLeftRotationArc and update is called', -// () { -// boundingBox.activeCorner = BoundingBoxCorner.topLeftRotationArc; -// boundingBox.lastPoint = boundingBox.topLeftRotationArcCenter; -// final centerBefore = boundingBox.center; -// final initialTopLeftDistanceToCenter = -// boundingBox.topLeft.distanceTo(centerBefore); -// const Offset newPoint = Offset(0, 50); -// boundingBox.update(newPoint); -// expect(boundingBox.center.dx, closeTo(centerBefore.dx, epsilon)); -// expect(boundingBox.center.dy, closeTo(centerBefore.dy, epsilon)); -// expect(boundingBox.topLeft.distanceTo(centerBefore), -// closeTo(initialTopLeftDistanceToCenter, epsilon)); -// expect( -// boundingBox.topLeft.dx, isNot(closeTo(topLeftInitial.dx, epsilon))); -// }); -// -// test( -// 'should transform edge correctly when activeCorner is topEdge and update is called', -// () { -// boundingBox.activeCorner = BoundingBoxCorner.topEdge; -// final centerBefore = boundingBox.center; -// const Offset newPoint = Offset(100, 10); -// boundingBox.lastPoint = const Offset(100, 0); -// boundingBox.update(newPoint); -// expect(boundingBox.topLeft.dy, closeTo(newPoint.dy, epsilon)); -// expect(boundingBox.topRight.dy, closeTo(newPoint.dy, epsilon)); -// expect(boundingBox.topLeft.dx, closeTo(topLeftInitial.dx, epsilon)); -// expect(boundingBox.topRight.dx, closeTo(topRightInitial.dx, epsilon)); -// expect(boundingBox.bottomLeft, bottomLeftInitial); -// expect(boundingBox.bottomRight, bottomRightInitial); -// expect(boundingBox.center, isNot(centerBefore)); -// }); -// -// test('should move center when activeCorner is none and update is called', -// () { -// boundingBox.activeCorner = BoundingBoxCorner.none; -// final centerBefore = boundingBox.center; -// const Offset dragVector = Offset(50, 50); -// boundingBox.lastPoint = centerBefore; -// final Offset newPoint = centerBefore + dragVector; -// boundingBox.update(newPoint); -// expect(boundingBox.center.dx, closeTo(newPoint.dx, epsilon)); -// expect(boundingBox.center.dy, closeTo(newPoint.dy, epsilon)); -// expect(boundingBox.topLeft, topLeftInitial + dragVector); -// expect(boundingBox.topRight, topRightInitial + dragVector); -// expect(boundingBox.bottomLeft, bottomLeftInitial + dragVector); -// expect(boundingBox.bottomRight, bottomRightInitial + dragVector); -// }); -// }); -// -// group('transform (direct edge manipulation)', () { -// test( -// 'should scale correctly when a corner handle is active (simulating old transform)', -// () { -// boundingBox.activeCorner = BoundingBoxCorner.bottomRight; -// boundingBox.lastPoint = boundingBox.bottomRight; -// const Offset newPoint = Offset(250, 250); -// boundingBox.update(newPoint); -// -// expect(boundingBox.bottomRight.dx, closeTo(newPoint.dx, epsilon)); -// expect(boundingBox.bottomRight.dy, closeTo(newPoint.dy, epsilon)); -// expect(boundingBox.topLeft.dx, closeTo(topLeftInitial.dx, epsilon)); -// expect(boundingBox.topLeft.dy, closeTo(topLeftInitial.dy, epsilon)); -// }); -// }); -// -// group('rotate (direct rotation)', () { -// test( -// 'should rotate corners around center when activeCorner is a rotation arc', -// () { -// boundingBox.activeCorner = BoundingBoxCorner.topLeftRotationArc; -// boundingBox.lastPoint = boundingBox.topLeftRotationArcCenter; -// final centerBefore = boundingBox.center; -// final initialDistance = boundingBox.topLeft.distanceTo(centerBefore); -// const Offset newPoint = Offset(0, 50); -// boundingBox.rotate(newPoint); -// -// expect(boundingBox.center.dx, closeTo(centerBefore.dx, epsilon)); -// expect(boundingBox.center.dy, closeTo(centerBefore.dy, epsilon)); -// expect(boundingBox.topLeft.distanceTo(centerBefore), -// closeTo(initialDistance, epsilon)); -// expect( -// boundingBox.topLeft.dx, isNot(closeTo(topLeftInitial.dx, epsilon))); -// }); -// -// test('should not rotate when activeCorner is none and rotate is called', -// () { -// boundingBox.activeCorner = BoundingBoxCorner.none; -// final beforeRotation = BoundingBox( -// boundingBox.topLeft, -// boundingBox.topRight, -// boundingBox.bottomLeft, -// boundingBox.bottomRight); -// const Offset newPoint = Offset(50, -50); -// boundingBox.lastPoint = Offset.zero; -// boundingBox.rotate(newPoint); -// expect(boundingBox.topLeft, beforeRotation.topLeft); -// expect(boundingBox.topRight, beforeRotation.topRight); -// expect(boundingBox.bottomLeft, beforeRotation.bottomLeft); -// expect(boundingBox.bottomRight, beforeRotation.bottomRight); -// }); -// -// test( -// 'should NOT rotate if activeCorner is a corner handle and rotate() is called directly', -// () { -// boundingBox.activeCorner = BoundingBoxCorner.topLeft; -// final originalTopLeft = boundingBox.topLeft; -// const Offset rotatePoint = Offset(100, 100); -// boundingBox.lastPoint = boundingBox.topLeft; -// boundingBox.rotate(rotatePoint); -// expect(boundingBox.topLeft.dx, closeTo(originalTopLeft.dx, epsilon)); -// expect(boundingBox.topLeft.dy, closeTo(originalTopLeft.dy, epsilon)); -// }); -// }); -// -// group('moveCenter (direct)', () { -// test('should move center and update all corners', () { -// const Offset newCenter = Offset(150, 150); -// final initialTopLeft = boundingBox.topLeft; -// final initialTopRight = boundingBox.topRight; -// final initialBottomLeft = boundingBox.bottomLeft; -// final initialBottomRight = boundingBox.bottomRight; -// final offsetDiff = newCenter - boundingBox.center; -// -// boundingBox.moveCenter(newCenter); -// -// expect(boundingBox.center.dx, closeTo(newCenter.dx, epsilon)); -// expect(boundingBox.center.dy, closeTo(newCenter.dy, epsilon)); -// expect(boundingBox.topLeft, initialTopLeft + offsetDiff); -// expect(boundingBox.topRight, initialTopRight + offsetDiff); -// expect(boundingBox.bottomLeft, initialBottomLeft + offsetDiff); -// expect(boundingBox.bottomRight, initialBottomRight + offsetDiff); -// }); -// }); -// -// group('setActiveCorner', () { -// test('should set active corner based on corner handles', () { -// boundingBox.setActiveCorner(topLeftInitial); -// expect(boundingBox.activeCorner, BoundingBoxCorner.topLeft); -// boundingBox.setActiveCorner(topRightInitial); -// expect(boundingBox.activeCorner, BoundingBoxCorner.topRight); -// boundingBox.setActiveCorner(bottomLeftInitial); -// expect(boundingBox.activeCorner, BoundingBoxCorner.bottomLeft); -// boundingBox.setActiveCorner(bottomRightInitial); -// expect(boundingBox.activeCorner, BoundingBoxCorner.bottomRight); -// }); -// -// test('should set active corner based on rotation arc handles', () { -// boundingBox.resetToDefaultsAroundCenter( -// newCenter: const Offset(100, 100), -// defaultWidth: 200, -// defaultHeight: 200); -// -// final Offset calculatedTopLeftRotationArcCenter = -// boundingBox.topLeftRotationArcCenter; -// boundingBox.setActiveCorner(calculatedTopLeftRotationArcCenter); -// expect(boundingBox.activeCorner, BoundingBoxCorner.topLeftRotationArc); -// -// final Offset calculatedTopRightRotationArcCenter = -// boundingBox.topRightRotationArcCenter; -// boundingBox.setActiveCorner(calculatedTopRightRotationArcCenter); -// expect(boundingBox.activeCorner, BoundingBoxCorner.topRightRotationArc); -// -// final Offset calculatedBottomLeftRotationArcCenter = -// boundingBox.bottomLeftRotationArcCenter; -// boundingBox.setActiveCorner(calculatedBottomLeftRotationArcCenter); -// expect(boundingBox.activeCorner, BoundingBoxCorner.bottomLeftRotationArc); -// -// final Offset calculatedBottomRightRotationArcCenter = -// boundingBox.bottomRightRotationArcCenter; -// boundingBox.setActiveCorner(calculatedBottomRightRotationArcCenter); -// expect( -// boundingBox.activeCorner, BoundingBoxCorner.bottomRightRotationArc); -// }); -// -// test('should set active corner to none if point is far from handles', () { -// boundingBox.setActiveCorner(const Offset(5000, 5000)); -// expect(boundingBox.activeCorner, BoundingBoxCorner.none); -// }); -// -// test('should set active corner for top edge', () { -// final topEdgePoint = (boundingBox.topLeft + boundingBox.topRight) / 2; -// boundingBox.setActiveCorner(topEdgePoint); -// expect(boundingBox.activeCorner, BoundingBoxCorner.topEdge); -// }); -// }); -// -// group('getPath', () { -// test('getPath should return correct path from corners for unrotated box', -// () { -// const Offset topLeft = Offset(10, 20); -// const Offset topRight = Offset(110, 20); -// const Offset bottomLeft = Offset(10, 120); -// const Offset bottomRight = Offset(110, 120); -// final unrotatedBox = -// BoundingBox(topLeft, topRight, bottomLeft, bottomRight, angle: 0.0); -// final Path path = unrotatedBox.getPath(); -// final Rect bounds = path.getBounds(); -// -// expect(bounds.topLeft.dx, closeTo(topLeft.dx, epsilon)); -// expect(bounds.topLeft.dy, closeTo(topLeft.dy, epsilon)); -// expect(bounds.bottomRight.dx, closeTo(bottomRight.dx, epsilon)); -// expect(bounds.bottomRight.dy, closeTo(bottomRight.dy, epsilon)); -// }); -// -// test('getPath should return correct path for rotated box', () { -// final center = const Offset(100, 100); -// final rotatedBox = BoundingBox( -// Offset(50, 50), Offset(150, 50), Offset(50, 150), Offset(150, 150), -// angle: math.pi / 4); -// rotatedBox.resetToDefaultsAroundCenter( -// newCenter: center, -// defaultWidth: 100, -// defaultHeight: 100, -// defaultAngle: math.pi / 4); -// -// final Path path = rotatedBox.getPath(); -// final Rect bounds = path.getBounds(); -// -// expect(bounds.center.dx, closeTo(center.dx, epsilon)); -// expect(bounds.center.dy, closeTo(center.dy, epsilon)); -// }); -// }); -// } - import 'package:flutter_test/flutter_test.dart'; import 'package:paintroid/core/enums/bounding_box_action.dart'; import 'package:paintroid/core/enums/bounding_box_resize_action.dart'; diff --git a/test/unit/tools/clipboard_tool_test.dart b/test/unit/tools/clipboard_tool_test.dart new file mode 100644 index 00000000..ec007f99 --- /dev/null +++ b/test/unit/tools/clipboard_tool_test.dart @@ -0,0 +1,206 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'package:paintroid/core/enums/bounding_box_action.dart'; +import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/tools/bounding_box.dart'; +import 'package:paintroid/core/tools/implementation/clipboard_tool.dart'; +import 'package:paintroid/core/commands/command_factory/command_factory.dart'; +import 'package:paintroid/core/commands/command_manager/command_manager.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart'; + +import '../../utils/clipboard_tool_util.dart'; +import 'clipboard_tool_test.mocks.dart'; + +@GenerateMocks( + [BoundingBox, CommandManager, CommandFactory, ClipboardCommand, ui.Image]) +void main() { + late ClipboardTool sut; + late MockBoundingBox boundingBox; + late MockCommandManager commandManager; + late MockCommandFactory commandFactory; + late MockImage mockCopiedImage; + + const ui.Offset pointA = ui.Offset(50, 50); + final ui.Paint paint = ui.Paint(); + + setUp(() { + boundingBox = MockBoundingBox(); + commandManager = MockCommandManager(); + commandFactory = MockCommandFactory(); + mockCopiedImage = MockImage(); + + when(boundingBox.rect).thenReturn(const ui.Rect.fromLTWH(0, 0, 100, 100)); + when(boundingBox.angle).thenReturn(0.0); + when(boundingBox.currentAction).thenReturn(BoundingBoxAction.none); + + sut = ClipboardTool( + commandManager: commandManager, + commandFactory: commandFactory, + boundingBox: boundingBox, + type: ToolType.CLIPBOARD, + ); + }); + + group('Basic behavior', () { + test('clearClipboard sets copiedImageData to null', () async { + final image = await ClipboardIntegrationTestUtils.createTestImage(10, 10); + sut.copiedImageData = image; + sut.clearClipboard(); + expect(sut.copiedImageData, isNull); + }); + + test('onDown sets _isInteracting when boundingBox action != none', () { + when(boundingBox.determineAction(any)).thenAnswer((_) { + when(boundingBox.currentAction).thenReturn(BoundingBoxAction.move); + }); + + sut.onDown(pointA, paint); + + verify(boundingBox.determineAction(pointA)).called(1); + sut.onDrag(ui.Offset.zero, paint); + verify(boundingBox.updateDrag(any)).called(1); + }); + + test('onDown does NOT set _isInteracting when boundingBox action is none', + () { + when(boundingBox.determineAction(any)).thenAnswer((_) { + when(boundingBox.currentAction).thenReturn(BoundingBoxAction.none); + }); + + sut.onDown(pointA, paint); + + verify(boundingBox.determineAction(pointA)).called(1); + sut.onDrag(ui.Offset.zero, paint); + verifyNever(boundingBox.updateDrag(any)); + }); + + test('onDrag calls boundingBox.updateDrag when interacting', () { + when(boundingBox.currentAction).thenReturn(BoundingBoxAction.move); + sut.onDown(pointA, paint); + + sut.onDrag(const ui.Offset(10, 10), paint); + verify(boundingBox.updateDrag(const ui.Offset(10, 10))).called(1); + }); + + test('onDrag does NOT call boundingBox.updateDrag when not interacting', + () { + sut.onDrag(const ui.Offset(10, 10), paint); + verifyNever(boundingBox.updateDrag(any)); + }); + + test('onUp and onCancel end drag and reset interaction when interacting', + () { + when(boundingBox.currentAction).thenReturn(BoundingBoxAction.move); + sut.onDown(pointA, paint); + + sut.onUp(pointA, paint); + verify(boundingBox.endDrag()).called(1); + sut.onDrag(ui.Offset.zero, paint); + verifyNever(boundingBox.updateDrag(any)); + + when(boundingBox.currentAction).thenReturn(BoundingBoxAction.move); + sut.onDown(pointA, paint); + + sut.onCancel(); + verify(boundingBox.endDrag()).called(1); + + sut.onDrag(ui.Offset.zero, paint); + verifyNever(boundingBox.updateDrag(any)); + }); + + test('onUp does NOT call boundingBox.endDrag when not interacting', () { + sut.onUp(pointA, paint); + verifyNever(boundingBox.endDrag()); + }); + + test('onCancel does NOT call boundingBox.endDrag when not interacting', () { + sut.onCancel(); + verifyNever(boundingBox.endDrag()); + }); + }); + + group('copy()', () { + test('does nothing if rect has zero size', () async { + when(boundingBox.rect).thenReturn(ui.Rect.zero); + final img = await ClipboardIntegrationTestUtils.createTestImage(20, 20); + await sut.copy(img); + expect(sut.copiedImageData, isNull); + }); + + test('captures image subsection for valid rect', () async { + final img = await ClipboardIntegrationTestUtils.createTestImage(100, 100); + await sut.copy(img); + expect(sut.copiedImageData, isNotNull); + expect(sut.copiedImageData!.width, 100); + expect(sut.copiedImageData!.height, 100); + }); + }); + + group('paste()', () { + test('does nothing if no image copied', () async { + sut.copiedImageData = null; + await sut.paste(paint); + verifyNever( + commandFactory.createClipboardCommand(any, any, any, any, any)); + verifyNever(commandManager.addGraphicCommand(any)); + }); + + test('does nothing if copiedImageData.toByteData returns null', () async { + sut.copiedImageData = mockCopiedImage; + when(mockCopiedImage.width).thenReturn(50); + when(mockCopiedImage.height).thenReturn(50); + when(mockCopiedImage.toByteData(format: ui.ImageByteFormat.png)) + .thenAnswer((_) async => null); + + await sut.paste(paint); + + verify(mockCopiedImage.toByteData(format: ui.ImageByteFormat.png)) + .called(1); + verifyNever( + commandFactory.createClipboardCommand(any, any, any, any, any)); + verifyNever(commandManager.addGraphicCommand(any)); + }); + + test( + 'creates, prepares, and adds command when image exists and toByteData is valid', + () async { + final img = await ClipboardIntegrationTestUtils.createTestImage(50, 50); + final ByteData? testByteData = + await img.toByteData(format: ui.ImageByteFormat.png); + expect(testByteData, isNotNull); + final bytes = testByteData!.buffer.asUint8List(); + + sut.copiedImageData = img; + + final mockCmd = MockClipboardCommand(); + when(commandFactory.createClipboardCommand(any, any, any, any, any)) + .thenReturn(mockCmd); + when(mockCmd.prepareForRuntime()).thenAnswer((_) async {}); + + await sut.paste(paint); + + final expectedCenter = const ui.Offset(50.0, 50.0); + const expectedScale = 2.0; + const expectedAngle = 0.0; + + verify(commandFactory.createClipboardCommand( + paint, + bytes, + argThat(equals(expectedCenter)), + argThat(equals(expectedScale)), + argThat(equals(expectedAngle)), + )).called(1); + verify(mockCmd.prepareForRuntime()).called(1); + verify(commandManager.addGraphicCommand(mockCmd)).called(1); + }); + }); + + test('type getter returns ToolType.CLIPBOARD', () { + expect(sut.type, ToolType.CLIPBOARD); + }); +} diff --git a/test/unit/tools/clipboard_tool_test.mocks.dart b/test/unit/tools/clipboard_tool_test.mocks.dart new file mode 100644 index 00000000..f5c42016 --- /dev/null +++ b/test/unit/tools/clipboard_tool_test.mocks.dart @@ -0,0 +1,1181 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in paintroid/test/unit/tools/clipboard_tool_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i28; +import 'dart:typed_data' as _i26; +import 'dart:ui' as _i2; + +import 'package:flutter/material.dart' as _i27; +import 'package:logging/logging.dart' as _i15; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i19; +import 'package:paintroid/core/commands/command_factory/command_factory.dart' + as _i23; +import 'package:paintroid/core/commands/command_implementation/command.dart' + as _i3; +import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart' + as _i9; +import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart' + as _i14; +import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart' + as _i21; +import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart' + as _i6; +import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart' + as _i5; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/ellipse_shape_command.dart' + as _i8; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart' + as _i12; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart' + as _i7; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart' + as _i11; +import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart' + as _i13; +import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart' + as _i10; +import 'package:paintroid/core/commands/command_manager/command_manager.dart' + as _i20; +import 'package:paintroid/core/commands/path_with_action_history.dart' as _i24; +import 'package:paintroid/core/enums/bounding_box_action.dart' as _i17; +import 'package:paintroid/core/enums/bounding_box_resize_action.dart' as _i18; +import 'package:paintroid/core/enums/shape_style.dart' as _i25; +import 'package:paintroid/core/tools/bounding_box.dart' as _i16; +import 'package:paintroid/core/tools/line_tool/vertex_stack.dart' as _i22; +import 'package:paintroid/core/tools/tool_data.dart' as _i4; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeOffset_0 extends _i1.SmartFake implements _i2.Offset { + _FakeOffset_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRect_1 extends _i1.SmartFake implements _i2.Rect { + _FakeRect_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCommand_2 extends _i1.SmartFake implements _i3.Command { + _FakeCommand_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeToolData_3 extends _i1.SmartFake implements _i4.ToolData { + _FakeToolData_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePathCommand_4 extends _i1.SmartFake implements _i5.PathCommand { + _FakePathCommand_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLineCommand_5 extends _i1.SmartFake implements _i6.LineCommand { + _FakeLineCommand_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSquareShapeCommand_6 extends _i1.SmartFake + implements _i7.SquareShapeCommand { + _FakeSquareShapeCommand_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEllipseShapeCommand_7 extends _i1.SmartFake + implements _i8.EllipseShapeCommand { + _FakeEllipseShapeCommand_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeClipboardCommand_8 extends _i1.SmartFake + implements _i9.ClipboardCommand { + _FakeClipboardCommand_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeTextCommand_9 extends _i1.SmartFake implements _i10.TextCommand { + _FakeTextCommand_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeStarShapeCommand_10 extends _i1.SmartFake + implements _i11.StarShapeCommand { + _FakeStarShapeCommand_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeHeartShapeCommand_11 extends _i1.SmartFake + implements _i12.HeartShapeCommand { + _FakeHeartShapeCommand_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSprayCommand_12 extends _i1.SmartFake implements _i13.SprayCommand { + _FakeSprayCommand_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDeleteRegionCommand_13 extends _i1.SmartFake + implements _i14.DeleteRegionCommand { + _FakeDeleteRegionCommand_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLogger_14 extends _i1.SmartFake implements _i15.Logger { + _FakeLogger_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeImage_15 extends _i1.SmartFake implements _i2.Image { + _FakeImage_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [BoundingBox]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBoundingBox extends _i1.Mock implements _i16.BoundingBox { + MockBoundingBox() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Offset get center => (super.noSuchMethod( + Invocation.getter(#center), + returnValue: _FakeOffset_0( + this, + Invocation.getter(#center), + ), + ) as _i2.Offset); + + @override + set center(_i2.Offset? _center) => super.noSuchMethod( + Invocation.setter( + #center, + _center, + ), + returnValueForMissingStub: null, + ); + + @override + double get width => (super.noSuchMethod( + Invocation.getter(#width), + returnValue: 0.0, + ) as double); + + @override + set width(double? _width) => super.noSuchMethod( + Invocation.setter( + #width, + _width, + ), + returnValueForMissingStub: null, + ); + + @override + double get height => (super.noSuchMethod( + Invocation.getter(#height), + returnValue: 0.0, + ) as double); + + @override + set height(double? _height) => super.noSuchMethod( + Invocation.setter( + #height, + _height, + ), + returnValueForMissingStub: null, + ); + + @override + double get angle => (super.noSuchMethod( + Invocation.getter(#angle), + returnValue: 0.0, + ) as double); + + @override + set angle(double? _angle) => super.noSuchMethod( + Invocation.setter( + #angle, + _angle, + ), + returnValueForMissingStub: null, + ); + + @override + _i17.BoundingBoxAction get currentAction => (super.noSuchMethod( + Invocation.getter(#currentAction), + returnValue: _i17.BoundingBoxAction.none, + ) as _i17.BoundingBoxAction); + + @override + set currentAction(_i17.BoundingBoxAction? _currentAction) => + super.noSuchMethod( + Invocation.setter( + #currentAction, + _currentAction, + ), + returnValueForMissingStub: null, + ); + + @override + _i18.BoundingBoxResizeAction get currentBoundingBoxResizeAction => + (super.noSuchMethod( + Invocation.getter(#currentBoundingBoxResizeAction), + returnValue: _i18.BoundingBoxResizeAction.none, + ) as _i18.BoundingBoxResizeAction); + + @override + set currentBoundingBoxResizeAction( + _i18.BoundingBoxResizeAction? _currentBoundingBoxResizeAction) => + super.noSuchMethod( + Invocation.setter( + #currentBoundingBoxResizeAction, + _currentBoundingBoxResizeAction, + ), + returnValueForMissingStub: null, + ); + + @override + set lastDragGlobalPosition(_i2.Offset? _lastDragGlobalPosition) => + super.noSuchMethod( + Invocation.setter( + #lastDragGlobalPosition, + _lastDragGlobalPosition, + ), + returnValueForMissingStub: null, + ); + + @override + set dragStartLocalPosition(_i2.Offset? _dragStartLocalPosition) => + super.noSuchMethod( + Invocation.setter( + #dragStartLocalPosition, + _dragStartLocalPosition, + ), + returnValueForMissingStub: null, + ); + + @override + int get activeRotationArcIndex => (super.noSuchMethod( + Invocation.getter(#activeRotationArcIndex), + returnValue: 0, + ) as int); + + @override + set activeRotationArcIndex(int? _activeRotationArcIndex) => + super.noSuchMethod( + Invocation.setter( + #activeRotationArcIndex, + _activeRotationArcIndex, + ), + returnValueForMissingStub: null, + ); + + @override + _i2.Paint get boxPaint => (super.noSuchMethod( + Invocation.getter(#boxPaint), + returnValue: _i19.dummyValue<_i2.Paint>( + this, + Invocation.getter(#boxPaint), + ), + ) as _i2.Paint); + + @override + set boxPaint(_i2.Paint? _boxPaint) => super.noSuchMethod( + Invocation.setter( + #boxPaint, + _boxPaint, + ), + returnValueForMissingStub: null, + ); + + @override + _i2.Paint get handlePaint => (super.noSuchMethod( + Invocation.getter(#handlePaint), + returnValue: _i19.dummyValue<_i2.Paint>( + this, + Invocation.getter(#handlePaint), + ), + ) as _i2.Paint); + + @override + set handlePaint(_i2.Paint? _handlePaint) => super.noSuchMethod( + Invocation.setter( + #handlePaint, + _handlePaint, + ), + returnValueForMissingStub: null, + ); + + @override + _i2.Paint get rotationHandlePaint => (super.noSuchMethod( + Invocation.getter(#rotationHandlePaint), + returnValue: _i19.dummyValue<_i2.Paint>( + this, + Invocation.getter(#rotationHandlePaint), + ), + ) as _i2.Paint); + + @override + set rotationHandlePaint(_i2.Paint? _rotationHandlePaint) => + super.noSuchMethod( + Invocation.setter( + #rotationHandlePaint, + _rotationHandlePaint, + ), + returnValueForMissingStub: null, + ); + + @override + bool get isAspectRatioLocked => (super.noSuchMethod( + Invocation.getter(#isAspectRatioLocked), + returnValue: false, + ) as bool); + + @override + set isAspectRatioLocked(bool? _isAspectRatioLocked) => super.noSuchMethod( + Invocation.setter( + #isAspectRatioLocked, + _isAspectRatioLocked, + ), + returnValueForMissingStub: null, + ); + + @override + _i2.Rect get rect => (super.noSuchMethod( + Invocation.getter(#rect), + returnValue: _FakeRect_1( + this, + Invocation.getter(#rect), + ), + ) as _i2.Rect); + + @override + List<_i2.Offset> getCorners() => (super.noSuchMethod( + Invocation.method( + #getCorners, + [], + ), + returnValue: <_i2.Offset>[], + ) as List<_i2.Offset>); + + @override + void determineAction(_i2.Offset? globalPoint) => super.noSuchMethod( + Invocation.method( + #determineAction, + [globalPoint], + ), + returnValueForMissingStub: null, + ); + + @override + void updateDrag(_i2.Offset? globalPoint) => super.noSuchMethod( + Invocation.method( + #updateDrag, + [globalPoint], + ), + returnValueForMissingStub: null, + ); + + @override + void endDrag() => super.noSuchMethod( + Invocation.method( + #endDrag, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void drawGuides(_i2.Canvas? canvas) => super.noSuchMethod( + Invocation.method( + #drawGuides, + [canvas], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [CommandManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCommandManager extends _i1.Mock implements _i20.CommandManager { + MockCommandManager() { + _i1.throwOnMissingStub(this); + } + + @override + List<_i3.Command> get redoStack => (super.noSuchMethod( + Invocation.getter(#redoStack), + returnValue: <_i3.Command>[], + ) as List<_i3.Command>); + + @override + List<_i3.Command> get undoStack => (super.noSuchMethod( + Invocation.getter(#undoStack), + returnValue: <_i3.Command>[], + ) as List<_i3.Command>); + + @override + void addGraphicCommand(_i21.GraphicCommand? command) => super.noSuchMethod( + Invocation.method( + #addGraphicCommand, + [command], + ), + returnValueForMissingStub: null, + ); + + @override + void setUndoStack(List<_i3.Command>? commands) => super.noSuchMethod( + Invocation.method( + #setUndoStack, + [commands], + ), + returnValueForMissingStub: null, + ); + + @override + void executeLastCommand(_i2.Canvas? canvas) => super.noSuchMethod( + Invocation.method( + #executeLastCommand, + [canvas], + ), + returnValueForMissingStub: null, + ); + + @override + void executeAllCommands(_i2.Canvas? canvas) => super.noSuchMethod( + Invocation.method( + #executeAllCommands, + [canvas], + ), + returnValueForMissingStub: null, + ); + + @override + void discardLastCommand() => super.noSuchMethod( + Invocation.method( + #discardLastCommand, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void clearUndoStack({Iterable<_i3.Command>? newCommands}) => + super.noSuchMethod( + Invocation.method( + #clearUndoStack, + [], + {#newCommands: newCommands}, + ), + returnValueForMissingStub: null, + ); + + @override + void clearRedoStack() => super.noSuchMethod( + Invocation.method( + #clearRedoStack, + [], + ), + returnValueForMissingStub: null, + ); + + @override + void drawLineToolGhostPaths( + _i2.Canvas? canvas, + _i6.LineCommand? ingoingGhostPathCommand, + _i6.LineCommand? outgoingGhostPathCommand, + ) => + super.noSuchMethod( + Invocation.method( + #drawLineToolGhostPaths, + [ + canvas, + ingoingGhostPathCommand, + outgoingGhostPathCommand, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void drawLineToolVertices( + _i2.Canvas? canvas, + _i22.VertexStack? vertexStack, + ) => + super.noSuchMethod( + Invocation.method( + #drawLineToolVertices, + [ + canvas, + vertexStack, + ], + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Command redo() => (super.noSuchMethod( + Invocation.method( + #redo, + [], + ), + returnValue: _FakeCommand_2( + this, + Invocation.method( + #redo, + [], + ), + ), + ) as _i3.Command); + + @override + void undo() => super.noSuchMethod( + Invocation.method( + #undo, + [], + ), + returnValueForMissingStub: null, + ); + + @override + _i4.ToolData getNextTool(_i20.ActionType? actionType) => (super.noSuchMethod( + Invocation.method( + #getNextTool, + [actionType], + ), + returnValue: _FakeToolData_3( + this, + Invocation.method( + #getNextTool, + [actionType], + ), + ), + ) as _i4.ToolData); + + @override + List<_i6.LineCommand> getTopLineCommandSequence() => (super.noSuchMethod( + Invocation.method( + #getTopLineCommandSequence, + [], + ), + returnValue: <_i6.LineCommand>[], + ) as List<_i6.LineCommand>); +} + +/// A class which mocks [CommandFactory]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCommandFactory extends _i1.Mock implements _i23.CommandFactory { + MockCommandFactory() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.PathCommand createPathCommand( + _i24.PathWithActionHistory? path, + _i2.Paint? paint, + ) => + (super.noSuchMethod( + Invocation.method( + #createPathCommand, + [ + path, + paint, + ], + ), + returnValue: _FakePathCommand_4( + this, + Invocation.method( + #createPathCommand, + [ + path, + paint, + ], + ), + ), + ) as _i5.PathCommand); + + @override + _i6.LineCommand createLineCommand( + _i24.PathWithActionHistory? path, + _i2.Paint? paint, + _i2.Offset? startPoint, + _i2.Offset? endPoint, + ) => + (super.noSuchMethod( + Invocation.method( + #createLineCommand, + [ + path, + paint, + startPoint, + endPoint, + ], + ), + returnValue: _FakeLineCommand_5( + this, + Invocation.method( + #createLineCommand, + [ + path, + paint, + startPoint, + endPoint, + ], + ), + ), + ) as _i6.LineCommand); + + @override + _i7.SquareShapeCommand createSquareShapeCommand( + _i2.Paint? paint, + _i2.Offset? topLeft, + _i2.Offset? topRight, + _i2.Offset? bottomLeft, + _i2.Offset? bottomRight, + _i25.ShapeStyle? style, + ) => + (super.noSuchMethod( + Invocation.method( + #createSquareShapeCommand, + [ + paint, + topLeft, + topRight, + bottomLeft, + bottomRight, + style, + ], + ), + returnValue: _FakeSquareShapeCommand_6( + this, + Invocation.method( + #createSquareShapeCommand, + [ + paint, + topLeft, + topRight, + bottomLeft, + bottomRight, + style, + ], + ), + ), + ) as _i7.SquareShapeCommand); + + @override + _i8.EllipseShapeCommand createEllipseShapeCommand( + _i2.Paint? paint, + double? radiusX, + double? radiusY, + _i2.Offset? center, + _i25.ShapeStyle? style, + double? angle, + ) => + (super.noSuchMethod( + Invocation.method( + #createEllipseShapeCommand, + [ + paint, + radiusX, + radiusY, + center, + style, + angle, + ], + ), + returnValue: _FakeEllipseShapeCommand_7( + this, + Invocation.method( + #createEllipseShapeCommand, + [ + paint, + radiusX, + radiusY, + center, + style, + angle, + ], + ), + ), + ) as _i8.EllipseShapeCommand); + + @override + _i9.ClipboardCommand createClipboardCommand( + _i2.Paint? paint, + _i26.Uint8List? imageData, + _i2.Offset? offset, + double? scale, + double? rotation, + ) => + (super.noSuchMethod( + Invocation.method( + #createClipboardCommand, + [ + paint, + imageData, + offset, + scale, + rotation, + ], + ), + returnValue: _FakeClipboardCommand_8( + this, + Invocation.method( + #createClipboardCommand, + [ + paint, + imageData, + offset, + scale, + rotation, + ], + ), + ), + ) as _i9.ClipboardCommand); + + @override + _i10.TextCommand createTextCommand( + _i2.Offset? point, + String? text, + _i27.TextStyle? style, + double? fontSize, + _i2.Paint? paint, + double? rotationAngle, { + double? scaleX = 1.0, + double? scaleY = 1.0, + }) => + (super.noSuchMethod( + Invocation.method( + #createTextCommand, + [ + point, + text, + style, + fontSize, + paint, + rotationAngle, + ], + { + #scaleX: scaleX, + #scaleY: scaleY, + }, + ), + returnValue: _FakeTextCommand_9( + this, + Invocation.method( + #createTextCommand, + [ + point, + text, + style, + fontSize, + paint, + rotationAngle, + ], + { + #scaleX: scaleX, + #scaleY: scaleY, + }, + ), + ), + ) as _i10.TextCommand); + + @override + _i11.StarShapeCommand createStarShapeCommand( + _i2.Paint? paint, + int? numPoints, + double? angle, + _i2.Offset? center, + _i25.ShapeStyle? style, + double? radiusX, + double? radiusY, + ) => + (super.noSuchMethod( + Invocation.method( + #createStarShapeCommand, + [ + paint, + numPoints, + angle, + center, + style, + radiusX, + radiusY, + ], + ), + returnValue: _FakeStarShapeCommand_10( + this, + Invocation.method( + #createStarShapeCommand, + [ + paint, + numPoints, + angle, + center, + style, + radiusX, + radiusY, + ], + ), + ), + ) as _i11.StarShapeCommand); + + @override + _i12.HeartShapeCommand createHeartShapeCommand( + _i2.Paint? paint, + double? width, + double? height, + double? angle, + _i2.Offset? center, + _i25.ShapeStyle? style, + ) => + (super.noSuchMethod( + Invocation.method( + #createHeartShapeCommand, + [ + paint, + width, + height, + angle, + center, + style, + ], + ), + returnValue: _FakeHeartShapeCommand_11( + this, + Invocation.method( + #createHeartShapeCommand, + [ + paint, + width, + height, + angle, + center, + style, + ], + ), + ), + ) as _i12.HeartShapeCommand); + + @override + _i13.SprayCommand createSprayCommand( + List<_i2.Offset>? points, + _i2.Paint? paint, + ) => + (super.noSuchMethod( + Invocation.method( + #createSprayCommand, + [ + points, + paint, + ], + ), + returnValue: _FakeSprayCommand_12( + this, + Invocation.method( + #createSprayCommand, + [ + points, + paint, + ], + ), + ), + ) as _i13.SprayCommand); + + @override + _i14.DeleteRegionCommand createDeleteRegionCommand(_i2.Rect? region) => + (super.noSuchMethod( + Invocation.method( + #createDeleteRegionCommand, + [region], + ), + returnValue: _FakeDeleteRegionCommand_13( + this, + Invocation.method( + #createDeleteRegionCommand, + [region], + ), + ), + ) as _i14.DeleteRegionCommand); +} + +/// A class which mocks [ClipboardCommand]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClipboardCommand extends _i1.Mock implements _i9.ClipboardCommand { + MockClipboardCommand() { + _i1.throwOnMissingStub(this); + } + + @override + _i26.Uint8List get imageData => (super.noSuchMethod( + Invocation.getter(#imageData), + returnValue: _i26.Uint8List(0), + ) as _i26.Uint8List); + + @override + _i2.Offset get offset => (super.noSuchMethod( + Invocation.getter(#offset), + returnValue: _FakeOffset_0( + this, + Invocation.getter(#offset), + ), + ) as _i2.Offset); + + @override + double get scale => (super.noSuchMethod( + Invocation.getter(#scale), + returnValue: 0.0, + ) as double); + + @override + double get rotation => (super.noSuchMethod( + Invocation.getter(#rotation), + returnValue: 0.0, + ) as double); + + @override + int get version => (super.noSuchMethod( + Invocation.getter(#version), + returnValue: 0, + ) as int); + + @override + String get type => (super.noSuchMethod( + Invocation.getter(#type), + returnValue: _i19.dummyValue( + this, + Invocation.getter(#type), + ), + ) as String); + + @override + List get props => (super.noSuchMethod( + Invocation.getter(#props), + returnValue: [], + ) as List); + + @override + _i2.Paint get paint => (super.noSuchMethod( + Invocation.getter(#paint), + returnValue: _i19.dummyValue<_i2.Paint>( + this, + Invocation.getter(#paint), + ), + ) as _i2.Paint); + + @override + _i15.Logger get logger => (super.noSuchMethod( + Invocation.getter(#logger), + returnValue: _FakeLogger_14( + this, + Invocation.getter(#logger), + ), + ) as _i15.Logger); + + @override + _i28.Future prepareForRuntime() => (super.noSuchMethod( + Invocation.method( + #prepareForRuntime, + [], + ), + returnValue: _i28.Future.value(), + returnValueForMissingStub: _i28.Future.value(), + ) as _i28.Future); + + @override + void call(_i2.Canvas? canvas) => super.noSuchMethod( + Invocation.method( + #call, + [canvas], + ), + returnValueForMissingStub: null, + ); + + @override + Map toJson() => (super.noSuchMethod( + Invocation.method( + #toJson, + [], + ), + returnValue: {}, + ) as Map); +} + +/// A class which mocks [Image]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockImage extends _i1.Mock implements _i2.Image { + MockImage() { + _i1.throwOnMissingStub(this); + } + + @override + int get width => (super.noSuchMethod( + Invocation.getter(#width), + returnValue: 0, + ) as int); + + @override + int get height => (super.noSuchMethod( + Invocation.getter(#height), + returnValue: 0, + ) as int); + + @override + bool get debugDisposed => (super.noSuchMethod( + Invocation.getter(#debugDisposed), + returnValue: false, + ) as bool); + + @override + _i2.ColorSpace get colorSpace => (super.noSuchMethod( + Invocation.getter(#colorSpace), + returnValue: _i2.ColorSpace.sRGB, + ) as _i2.ColorSpace); + + @override + void dispose() => super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValueForMissingStub: null, + ); + + @override + _i28.Future<_i26.ByteData?> toByteData( + {_i2.ImageByteFormat? format = _i2.ImageByteFormat.rawRgba}) => + (super.noSuchMethod( + Invocation.method( + #toByteData, + [], + {#format: format}, + ), + returnValue: _i28.Future<_i26.ByteData?>.value(), + ) as _i28.Future<_i26.ByteData?>); + + @override + _i2.Image clone() => (super.noSuchMethod( + Invocation.method( + #clone, + [], + ), + returnValue: _FakeImage_15( + this, + Invocation.method( + #clone, + [], + ), + ), + ) as _i2.Image); + + @override + bool isCloneOf(_i2.Image? other) => (super.noSuchMethod( + Invocation.method( + #isCloneOf, + [other], + ), + returnValue: false, + ) as bool); +} diff --git a/test/unit/tools/shapes_tool_test.dart b/test/unit/tools/shapes_tool_test.dart index 56589b76..7cb2fcbf 100644 --- a/test/unit/tools/shapes_tool_test.dart +++ b/test/unit/tools/shapes_tool_test.dart @@ -1,3 +1,4 @@ + import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; diff --git a/test/unit/tools/text_tool_test.mocks.dart b/test/unit/tools/text_tool_test.mocks.dart index aec80aba..cfa1831b 100644 --- a/test/unit/tools/text_tool_test.mocks.dart +++ b/test/unit/tools/text_tool_test.mocks.dart @@ -3,18 +3,23 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:typed_data' as _i22; -import 'dart:ui' as _i13; +import 'dart:async' as _i25; +import 'dart:typed_data' as _i23; +import 'dart:ui' as _i15; -import 'package:flutter/material.dart' as _i14; +import 'package:flutter/material.dart' as _i16; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i21; +import 'package:mockito/src/dummies.dart' as _i24; import 'package:paintroid/core/commands/command_factory/command_factory.dart' - as _i19; + as _i21; import 'package:paintroid/core/commands/command_implementation/command.dart' as _i2; +import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart' + as _i8; +import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart' + as _i13; import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart' - as _i17; + as _i19; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart' as _i5; import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart' @@ -22,27 +27,27 @@ import 'package:paintroid/core/commands/command_implementation/graphic/path_comm import 'package:paintroid/core/commands/command_implementation/graphic/shape/ellipse_shape_command.dart' as _i7; import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart' - as _i10; + as _i11; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart' as _i6; import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart' - as _i9; + as _i10; import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart' - as _i11; + as _i12; import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart' - as _i8; + as _i9; import 'package:paintroid/core/commands/command_manager/command_manager.dart' - as _i16; + as _i18; import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart' - as _i23; -import 'package:paintroid/core/commands/path_with_action_history.dart' as _i15; -import 'package:paintroid/core/enums/bounding_box_action.dart' as _i25; -import 'package:paintroid/core/enums/bounding_box_resize_action.dart' as _i26; -import 'package:paintroid/core/enums/shape_style.dart' as _i20; -import 'package:paintroid/core/tools/bounding_box.dart' as _i24; -import 'package:paintroid/core/tools/line_tool/vertex_stack.dart' as _i18; + as _i26; +import 'package:paintroid/core/commands/path_with_action_history.dart' as _i17; +import 'package:paintroid/core/enums/bounding_box_action.dart' as _i28; +import 'package:paintroid/core/enums/bounding_box_resize_action.dart' as _i29; +import 'package:paintroid/core/enums/shape_style.dart' as _i22; +import 'package:paintroid/core/tools/bounding_box.dart' as _i27; +import 'package:paintroid/core/tools/line_tool/vertex_stack.dart' as _i20; import 'package:paintroid/core/tools/tool_data.dart' as _i3; -import 'package:riverpod/src/internals.dart' as _i12; +import 'package:riverpod/src/internals.dart' as _i14; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -120,8 +125,9 @@ class _FakeEllipseShapeCommand_5 extends _i1.SmartFake ); } -class _FakeTextCommand_6 extends _i1.SmartFake implements _i8.TextCommand { - _FakeTextCommand_6( +class _FakeClipboardCommand_6 extends _i1.SmartFake + implements _i8.ClipboardCommand { + _FakeClipboardCommand_6( Object parent, Invocation parentInvocation, ) : super( @@ -130,9 +136,8 @@ class _FakeTextCommand_6 extends _i1.SmartFake implements _i8.TextCommand { ); } -class _FakeStarShapeCommand_7 extends _i1.SmartFake - implements _i9.StarShapeCommand { - _FakeStarShapeCommand_7( +class _FakeTextCommand_7 extends _i1.SmartFake implements _i9.TextCommand { + _FakeTextCommand_7( Object parent, Invocation parentInvocation, ) : super( @@ -141,9 +146,9 @@ class _FakeStarShapeCommand_7 extends _i1.SmartFake ); } -class _FakeHeartShapeCommand_8 extends _i1.SmartFake - implements _i10.HeartShapeCommand { - _FakeHeartShapeCommand_8( +class _FakeStarShapeCommand_8 extends _i1.SmartFake + implements _i10.StarShapeCommand { + _FakeStarShapeCommand_8( Object parent, Invocation parentInvocation, ) : super( @@ -152,8 +157,9 @@ class _FakeHeartShapeCommand_8 extends _i1.SmartFake ); } -class _FakeSprayCommand_9 extends _i1.SmartFake implements _i11.SprayCommand { - _FakeSprayCommand_9( +class _FakeHeartShapeCommand_9 extends _i1.SmartFake + implements _i11.HeartShapeCommand { + _FakeHeartShapeCommand_9( Object parent, Invocation parentInvocation, ) : super( @@ -162,9 +168,8 @@ class _FakeSprayCommand_9 extends _i1.SmartFake implements _i11.SprayCommand { ); } -class _FakeProviderContainer_10 extends _i1.SmartFake - implements _i12.ProviderContainer { - _FakeProviderContainer_10( +class _FakeSprayCommand_10 extends _i1.SmartFake implements _i12.SprayCommand { + _FakeSprayCommand_10( Object parent, Invocation parentInvocation, ) : super( @@ -173,9 +178,9 @@ class _FakeProviderContainer_10 extends _i1.SmartFake ); } -class _FakeKeepAliveLink_11 extends _i1.SmartFake - implements _i12.KeepAliveLink { - _FakeKeepAliveLink_11( +class _FakeDeleteRegionCommand_11 extends _i1.SmartFake + implements _i13.DeleteRegionCommand { + _FakeDeleteRegionCommand_11( Object parent, Invocation parentInvocation, ) : super( @@ -184,9 +189,9 @@ class _FakeKeepAliveLink_11 extends _i1.SmartFake ); } -class _FakeProviderSubscription_12 extends _i1.SmartFake - implements _i12.ProviderSubscription { - _FakeProviderSubscription_12( +class _FakeProviderContainer_12 extends _i1.SmartFake + implements _i14.ProviderContainer { + _FakeProviderContainer_12( Object parent, Invocation parentInvocation, ) : super( @@ -195,8 +200,9 @@ class _FakeProviderSubscription_12 extends _i1.SmartFake ); } -class _FakeRect_13 extends _i1.SmartFake implements _i13.Rect { - _FakeRect_13( +class _FakeKeepAliveLink_13 extends _i1.SmartFake + implements _i14.KeepAliveLink { + _FakeKeepAliveLink_13( Object parent, Invocation parentInvocation, ) : super( @@ -205,8 +211,9 @@ class _FakeRect_13 extends _i1.SmartFake implements _i13.Rect { ); } -class _FakeOffset_14 extends _i1.SmartFake implements _i13.Offset { - _FakeOffset_14( +class _FakeProviderSubscription_14 extends _i1.SmartFake + implements _i14.ProviderSubscription { + _FakeProviderSubscription_14( Object parent, Invocation parentInvocation, ) : super( @@ -215,8 +222,28 @@ class _FakeOffset_14 extends _i1.SmartFake implements _i13.Offset { ); } -class _FakeTextStyle_15 extends _i1.SmartFake implements _i14.TextStyle { - _FakeTextStyle_15( +class _FakeRect_15 extends _i1.SmartFake implements _i15.Rect { + _FakeRect_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOffset_16 extends _i1.SmartFake implements _i15.Offset { + _FakeOffset_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeTextStyle_17 extends _i1.SmartFake implements _i16.TextStyle { + _FakeTextStyle_17( Object parent, Invocation parentInvocation, ) : super( @@ -226,13 +253,13 @@ class _FakeTextStyle_15 extends _i1.SmartFake implements _i14.TextStyle { @override String toString( - {_i14.DiagnosticLevel? minLevel = _i14.DiagnosticLevel.info}) => + {_i16.DiagnosticLevel? minLevel = _i16.DiagnosticLevel.info}) => super.toString(); } -class _FakePathWithActionHistory_16 extends _i1.SmartFake - implements _i15.PathWithActionHistory { - _FakePathWithActionHistory_16( +class _FakePathWithActionHistory_18 extends _i1.SmartFake + implements _i17.PathWithActionHistory { + _FakePathWithActionHistory_18( Object parent, Invocation parentInvocation, ) : super( @@ -241,9 +268,9 @@ class _FakePathWithActionHistory_16 extends _i1.SmartFake ); } -class _FakePictureRecorder_17 extends _i1.SmartFake - implements _i13.PictureRecorder { - _FakePictureRecorder_17( +class _FakePictureRecorder_19 extends _i1.SmartFake + implements _i15.PictureRecorder { + _FakePictureRecorder_19( Object parent, Invocation parentInvocation, ) : super( @@ -252,8 +279,8 @@ class _FakePictureRecorder_17 extends _i1.SmartFake ); } -class _FakeCanvas_18 extends _i1.SmartFake implements _i13.Canvas { - _FakeCanvas_18( +class _FakeCanvas_20 extends _i1.SmartFake implements _i15.Canvas { + _FakeCanvas_20( Object parent, Invocation parentInvocation, ) : super( @@ -265,7 +292,7 @@ class _FakeCanvas_18 extends _i1.SmartFake implements _i13.Canvas { /// A class which mocks [CommandManager]. /// /// See the documentation for Mockito's code generation for more information. -class MockCommandManager extends _i1.Mock implements _i16.CommandManager { +class MockCommandManager extends _i1.Mock implements _i18.CommandManager { MockCommandManager() { _i1.throwOnMissingStub(this); } @@ -283,7 +310,7 @@ class MockCommandManager extends _i1.Mock implements _i16.CommandManager { ) as List<_i2.Command>); @override - void addGraphicCommand(_i17.GraphicCommand? command) => super.noSuchMethod( + void addGraphicCommand(_i19.GraphicCommand? command) => super.noSuchMethod( Invocation.method( #addGraphicCommand, [command], @@ -301,7 +328,7 @@ class MockCommandManager extends _i1.Mock implements _i16.CommandManager { ); @override - void executeLastCommand(_i13.Canvas? canvas) => super.noSuchMethod( + void executeLastCommand(_i15.Canvas? canvas) => super.noSuchMethod( Invocation.method( #executeLastCommand, [canvas], @@ -310,7 +337,7 @@ class MockCommandManager extends _i1.Mock implements _i16.CommandManager { ); @override - void executeAllCommands(_i13.Canvas? canvas) => super.noSuchMethod( + void executeAllCommands(_i15.Canvas? canvas) => super.noSuchMethod( Invocation.method( #executeAllCommands, [canvas], @@ -349,7 +376,7 @@ class MockCommandManager extends _i1.Mock implements _i16.CommandManager { @override void drawLineToolGhostPaths( - _i13.Canvas? canvas, + _i15.Canvas? canvas, _i5.LineCommand? ingoingGhostPathCommand, _i5.LineCommand? outgoingGhostPathCommand, ) => @@ -367,8 +394,8 @@ class MockCommandManager extends _i1.Mock implements _i16.CommandManager { @override void drawLineToolVertices( - _i13.Canvas? canvas, - _i18.VertexStack? vertexStack, + _i15.Canvas? canvas, + _i20.VertexStack? vertexStack, ) => super.noSuchMethod( Invocation.method( @@ -406,7 +433,7 @@ class MockCommandManager extends _i1.Mock implements _i16.CommandManager { ); @override - _i3.ToolData getNextTool(_i16.ActionType? actionType) => (super.noSuchMethod( + _i3.ToolData getNextTool(_i18.ActionType? actionType) => (super.noSuchMethod( Invocation.method( #getNextTool, [actionType], @@ -433,15 +460,15 @@ class MockCommandManager extends _i1.Mock implements _i16.CommandManager { /// A class which mocks [CommandFactory]. /// /// See the documentation for Mockito's code generation for more information. -class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { +class MockCommandFactory extends _i1.Mock implements _i21.CommandFactory { MockCommandFactory() { _i1.throwOnMissingStub(this); } @override _i4.PathCommand createPathCommand( - _i15.PathWithActionHistory? path, - _i13.Paint? paint, + _i17.PathWithActionHistory? path, + _i15.Paint? paint, ) => (super.noSuchMethod( Invocation.method( @@ -465,10 +492,10 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { @override _i5.LineCommand createLineCommand( - _i15.PathWithActionHistory? path, - _i13.Paint? paint, - _i13.Offset? startPoint, - _i13.Offset? endPoint, + _i17.PathWithActionHistory? path, + _i15.Paint? paint, + _i15.Offset? startPoint, + _i15.Offset? endPoint, ) => (super.noSuchMethod( Invocation.method( @@ -496,12 +523,12 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { @override _i6.SquareShapeCommand createSquareShapeCommand( - _i13.Paint? paint, - _i13.Offset? topLeft, - _i13.Offset? topRight, - _i13.Offset? bottomLeft, - _i13.Offset? bottomRight, - _i20.ShapeStyle? style, + _i15.Paint? paint, + _i15.Offset? topLeft, + _i15.Offset? topRight, + _i15.Offset? bottomLeft, + _i15.Offset? bottomRight, + _i22.ShapeStyle? style, ) => (super.noSuchMethod( Invocation.method( @@ -533,11 +560,11 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { @override _i7.EllipseShapeCommand createEllipseShapeCommand( - _i13.Paint? paint, + _i15.Paint? paint, double? radiusX, double? radiusY, - _i13.Offset? center, - _i20.ShapeStyle? style, + _i15.Offset? center, + _i22.ShapeStyle? style, double? angle, ) => (super.noSuchMethod( @@ -569,12 +596,46 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { ) as _i7.EllipseShapeCommand); @override - _i8.TextCommand createTextCommand( - _i13.Offset? point, + _i8.ClipboardCommand createClipboardCommand( + _i15.Paint? paint, + _i23.Uint8List? imageData, + _i15.Offset? offset, + double? scale, + double? rotation, + ) => + (super.noSuchMethod( + Invocation.method( + #createClipboardCommand, + [ + paint, + imageData, + offset, + scale, + rotation, + ], + ), + returnValue: _FakeClipboardCommand_6( + this, + Invocation.method( + #createClipboardCommand, + [ + paint, + imageData, + offset, + scale, + rotation, + ], + ), + ), + ) as _i8.ClipboardCommand); + + @override + _i9.TextCommand createTextCommand( + _i15.Offset? point, String? text, - _i14.TextStyle? style, + _i16.TextStyle? style, double? fontSize, - _i13.Paint? paint, + _i15.Paint? paint, double? rotationAngle, { double? scaleX = 1.0, double? scaleY = 1.0, @@ -595,7 +656,7 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { #scaleY: scaleY, }, ), - returnValue: _FakeTextCommand_6( + returnValue: _FakeTextCommand_7( this, Invocation.method( #createTextCommand, @@ -613,15 +674,15 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { }, ), ), - ) as _i8.TextCommand); + ) as _i9.TextCommand); @override - _i9.StarShapeCommand createStarShapeCommand( - _i13.Paint? paint, + _i10.StarShapeCommand createStarShapeCommand( + _i15.Paint? paint, int? numPoints, double? angle, - _i13.Offset? center, - _i20.ShapeStyle? style, + _i15.Offset? center, + _i22.ShapeStyle? style, double? radiusX, double? radiusY, ) => @@ -638,7 +699,7 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { radiusY, ], ), - returnValue: _FakeStarShapeCommand_7( + returnValue: _FakeStarShapeCommand_8( this, Invocation.method( #createStarShapeCommand, @@ -653,16 +714,16 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { ], ), ), - ) as _i9.StarShapeCommand); + ) as _i10.StarShapeCommand); @override - _i10.HeartShapeCommand createHeartShapeCommand( - _i13.Paint? paint, + _i11.HeartShapeCommand createHeartShapeCommand( + _i15.Paint? paint, double? width, double? height, double? angle, - _i13.Offset? center, - _i20.ShapeStyle? style, + _i15.Offset? center, + _i22.ShapeStyle? style, ) => (super.noSuchMethod( Invocation.method( @@ -676,7 +737,7 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { style, ], ), - returnValue: _FakeHeartShapeCommand_8( + returnValue: _FakeHeartShapeCommand_9( this, Invocation.method( #createHeartShapeCommand, @@ -690,12 +751,12 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { ], ), ), - ) as _i10.HeartShapeCommand); + ) as _i11.HeartShapeCommand); @override - _i11.SprayCommand createSprayCommand( - List<_i13.Offset>? points, - _i13.Paint? paint, + _i12.SprayCommand createSprayCommand( + List<_i15.Offset>? points, + _i15.Paint? paint, ) => (super.noSuchMethod( Invocation.method( @@ -705,7 +766,7 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { paint, ], ), - returnValue: _FakeSprayCommand_9( + returnValue: _FakeSprayCommand_10( this, Invocation.method( #createSprayCommand, @@ -715,34 +776,50 @@ class MockCommandFactory extends _i1.Mock implements _i19.CommandFactory { ], ), ), - ) as _i11.SprayCommand); + ) as _i12.SprayCommand); + + @override + _i13.DeleteRegionCommand createDeleteRegionCommand(_i15.Rect? region) => + (super.noSuchMethod( + Invocation.method( + #createDeleteRegionCommand, + [region], + ), + returnValue: _FakeDeleteRegionCommand_11( + this, + Invocation.method( + #createDeleteRegionCommand, + [region], + ), + ), + ) as _i13.DeleteRegionCommand); } /// A class which mocks [Ref]. /// /// See the documentation for Mockito's code generation for more information. class MockRef extends _i1.Mock - implements _i12.Ref { + implements _i14.Ref { MockRef() { _i1.throwOnMissingStub(this); } @override - _i12.ProviderContainer get container => (super.noSuchMethod( + _i14.ProviderContainer get container => (super.noSuchMethod( Invocation.getter(#container), - returnValue: _FakeProviderContainer_10( + returnValue: _FakeProviderContainer_12( this, Invocation.getter(#container), ), - ) as _i12.ProviderContainer); + ) as _i14.ProviderContainer); @override - T refresh(_i12.Refreshable? provider) => (super.noSuchMethod( + T refresh(_i14.Refreshable? provider) => (super.noSuchMethod( Invocation.method( #refresh, [provider], ), - returnValue: _i21.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #refresh, @@ -752,7 +829,7 @@ class MockRef extends _i1.Mock ) as T); @override - void invalidate(_i12.ProviderOrFamily? provider) => super.noSuchMethod( + void invalidate(_i14.ProviderOrFamily? provider) => super.noSuchMethod( Invocation.method( #invalidate, [provider], @@ -844,12 +921,12 @@ class MockRef extends _i1.Mock ); @override - T read(_i12.ProviderListenable? provider) => (super.noSuchMethod( + T read(_i14.ProviderListenable? provider) => (super.noSuchMethod( Invocation.method( #read, [provider], ), - returnValue: _i21.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #read, @@ -859,7 +936,7 @@ class MockRef extends _i1.Mock ) as T); @override - bool exists(_i12.ProviderBase? provider) => (super.noSuchMethod( + bool exists(_i14.ProviderBase? provider) => (super.noSuchMethod( Invocation.method( #exists, [provider], @@ -868,12 +945,12 @@ class MockRef extends _i1.Mock ) as bool); @override - T watch(_i12.ProviderListenable? provider) => (super.noSuchMethod( + T watch(_i14.ProviderListenable? provider) => (super.noSuchMethod( Invocation.method( #watch, [provider], ), - returnValue: _i21.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.method( #watch, @@ -883,23 +960,23 @@ class MockRef extends _i1.Mock ) as T); @override - _i12.KeepAliveLink keepAlive() => (super.noSuchMethod( + _i14.KeepAliveLink keepAlive() => (super.noSuchMethod( Invocation.method( #keepAlive, [], ), - returnValue: _FakeKeepAliveLink_11( + returnValue: _FakeKeepAliveLink_13( this, Invocation.method( #keepAlive, [], ), ), - ) as _i12.KeepAliveLink); + ) as _i14.KeepAliveLink); @override - _i12.ProviderSubscription listen( - _i12.ProviderListenable? provider, + _i14.ProviderSubscription listen( + _i14.ProviderListenable? provider, void Function( T?, T, @@ -922,7 +999,7 @@ class MockRef extends _i1.Mock #fireImmediately: fireImmediately, }, ), - returnValue: _FakeProviderSubscription_12( + returnValue: _FakeProviderSubscription_14( this, Invocation.method( #listen, @@ -936,13 +1013,13 @@ class MockRef extends _i1.Mock }, ), ), - ) as _i12.ProviderSubscription); + ) as _i14.ProviderSubscription); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. -class MockCanvas extends _i1.Mock implements _i13.Canvas { +class MockCanvas extends _i1.Mock implements _i15.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @@ -958,8 +1035,8 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void saveLayer( - _i13.Rect? bounds, - _i13.Paint? paint, + _i15.Rect? bounds, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1057,7 +1134,7 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { ); @override - void transform(_i22.Float64List? matrix4) => super.noSuchMethod( + void transform(_i23.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], @@ -1066,18 +1143,18 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { ); @override - _i22.Float64List getTransform() => (super.noSuchMethod( + _i23.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), - returnValue: _i22.Float64List(0), - ) as _i22.Float64List); + returnValue: _i23.Float64List(0), + ) as _i23.Float64List); @override void clipRect( - _i13.Rect? rect, { - _i13.ClipOp? clipOp = _i13.ClipOp.intersect, + _i15.Rect? rect, { + _i15.ClipOp? clipOp = _i15.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( @@ -1094,7 +1171,7 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void clipRRect( - _i13.RRect? rrect, { + _i15.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( @@ -1108,7 +1185,7 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void clipPath( - _i13.Path? path, { + _i15.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( @@ -1121,39 +1198,39 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { ); @override - _i13.Rect getLocalClipBounds() => (super.noSuchMethod( + _i15.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), - returnValue: _FakeRect_13( + returnValue: _FakeRect_15( this, Invocation.method( #getLocalClipBounds, [], ), ), - ) as _i13.Rect); + ) as _i15.Rect); @override - _i13.Rect getDestinationClipBounds() => (super.noSuchMethod( + _i15.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), - returnValue: _FakeRect_13( + returnValue: _FakeRect_15( this, Invocation.method( #getDestinationClipBounds, [], ), ), - ) as _i13.Rect); + ) as _i15.Rect); @override void drawColor( - _i13.Color? color, - _i13.BlendMode? blendMode, + _i15.Color? color, + _i15.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( @@ -1168,9 +1245,9 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawLine( - _i13.Offset? p1, - _i13.Offset? p2, - _i13.Paint? paint, + _i15.Offset? p1, + _i15.Offset? p2, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1185,7 +1262,7 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { ); @override - void drawPaint(_i13.Paint? paint) => super.noSuchMethod( + void drawPaint(_i15.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], @@ -1195,8 +1272,8 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawRect( - _i13.Rect? rect, - _i13.Paint? paint, + _i15.Rect? rect, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1211,8 +1288,8 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawRRect( - _i13.RRect? rrect, - _i13.Paint? paint, + _i15.RRect? rrect, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1227,9 +1304,9 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawDRRect( - _i13.RRect? outer, - _i13.RRect? inner, - _i13.Paint? paint, + _i15.RRect? outer, + _i15.RRect? inner, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1245,8 +1322,8 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawOval( - _i13.Rect? rect, - _i13.Paint? paint, + _i15.Rect? rect, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1261,9 +1338,9 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawCircle( - _i13.Offset? c, + _i15.Offset? c, double? radius, - _i13.Paint? paint, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1279,11 +1356,11 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawArc( - _i13.Rect? rect, + _i15.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, - _i13.Paint? paint, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1301,8 +1378,8 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawPath( - _i13.Path? path, - _i13.Paint? paint, + _i15.Path? path, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1317,9 +1394,9 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawImage( - _i13.Image? image, - _i13.Offset? offset, - _i13.Paint? paint, + _i15.Image? image, + _i15.Offset? offset, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1335,10 +1412,10 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawImageRect( - _i13.Image? image, - _i13.Rect? src, - _i13.Rect? dst, - _i13.Paint? paint, + _i15.Image? image, + _i15.Rect? src, + _i15.Rect? dst, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1355,10 +1432,10 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawImageNine( - _i13.Image? image, - _i13.Rect? center, - _i13.Rect? dst, - _i13.Paint? paint, + _i15.Image? image, + _i15.Rect? center, + _i15.Rect? dst, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1374,7 +1451,7 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { ); @override - void drawPicture(_i13.Picture? picture) => super.noSuchMethod( + void drawPicture(_i15.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], @@ -1384,8 +1461,8 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawParagraph( - _i13.Paragraph? paragraph, - _i13.Offset? offset, + _i15.Paragraph? paragraph, + _i15.Offset? offset, ) => super.noSuchMethod( Invocation.method( @@ -1400,9 +1477,9 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawPoints( - _i13.PointMode? pointMode, - List<_i13.Offset>? points, - _i13.Paint? paint, + _i15.PointMode? pointMode, + List<_i15.Offset>? points, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1418,9 +1495,9 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawRawPoints( - _i13.PointMode? pointMode, - _i22.Float32List? points, - _i13.Paint? paint, + _i15.PointMode? pointMode, + _i23.Float32List? points, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1436,9 +1513,9 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawVertices( - _i13.Vertices? vertices, - _i13.BlendMode? blendMode, - _i13.Paint? paint, + _i15.Vertices? vertices, + _i15.BlendMode? blendMode, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1454,13 +1531,13 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawAtlas( - _i13.Image? atlas, - List<_i13.RSTransform>? transforms, - List<_i13.Rect>? rects, - List<_i13.Color>? colors, - _i13.BlendMode? blendMode, - _i13.Rect? cullRect, - _i13.Paint? paint, + _i15.Image? atlas, + List<_i15.RSTransform>? transforms, + List<_i15.Rect>? rects, + List<_i15.Color>? colors, + _i15.BlendMode? blendMode, + _i15.Rect? cullRect, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1480,13 +1557,13 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawRawAtlas( - _i13.Image? atlas, - _i22.Float32List? rstTransforms, - _i22.Float32List? rects, - _i22.Int32List? colors, - _i13.BlendMode? blendMode, - _i13.Rect? cullRect, - _i13.Paint? paint, + _i15.Image? atlas, + _i23.Float32List? rstTransforms, + _i23.Float32List? rects, + _i23.Int32List? colors, + _i15.BlendMode? blendMode, + _i15.Rect? cullRect, + _i15.Paint? paint, ) => super.noSuchMethod( Invocation.method( @@ -1506,8 +1583,8 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { @override void drawShadow( - _i13.Path? path, - _i13.Color? color, + _i15.Path? path, + _i15.Color? color, double? elevation, bool? transparentOccluder, ) => @@ -1528,22 +1605,22 @@ class MockCanvas extends _i1.Mock implements _i13.Canvas { /// A class which mocks [TextCommand]. /// /// See the documentation for Mockito's code generation for more information. -class MockTextCommand extends _i1.Mock implements _i8.TextCommand { +class MockTextCommand extends _i1.Mock implements _i9.TextCommand { MockTextCommand() { _i1.throwOnMissingStub(this); } @override - _i13.Offset get point => (super.noSuchMethod( + _i15.Offset get point => (super.noSuchMethod( Invocation.getter(#point), - returnValue: _FakeOffset_14( + returnValue: _FakeOffset_16( this, Invocation.getter(#point), ), - ) as _i13.Offset); + ) as _i15.Offset); @override - set point(_i13.Offset? _point) => super.noSuchMethod( + set point(_i15.Offset? _point) => super.noSuchMethod( Invocation.setter( #point, _point, @@ -1552,18 +1629,18 @@ class MockTextCommand extends _i1.Mock implements _i8.TextCommand { ); @override - _i14.TextStyle get style => (super.noSuchMethod( + _i16.TextStyle get style => (super.noSuchMethod( Invocation.getter(#style), - returnValue: _FakeTextStyle_15( + returnValue: _FakeTextStyle_17( this, Invocation.getter(#style), ), - ) as _i14.TextStyle); + ) as _i16.TextStyle); @override String get text => (super.noSuchMethod( Invocation.getter(#text), - returnValue: _i21.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.getter(#text), ), @@ -1584,7 +1661,7 @@ class MockTextCommand extends _i1.Mock implements _i8.TextCommand { @override String get type => (super.noSuchMethod( Invocation.getter(#type), - returnValue: _i21.dummyValue( + returnValue: _i24.dummyValue( this, Invocation.getter(#type), ), @@ -1621,16 +1698,16 @@ class MockTextCommand extends _i1.Mock implements _i8.TextCommand { ) as bool); @override - _i13.Paint get paint => (super.noSuchMethod( + _i15.Paint get paint => (super.noSuchMethod( Invocation.getter(#paint), - returnValue: _i21.dummyValue<_i13.Paint>( + returnValue: _i24.dummyValue<_i15.Paint>( this, Invocation.getter(#paint), ), - ) as _i13.Paint); + ) as _i15.Paint); @override - void call(_i13.Canvas? canvas) => super.noSuchMethod( + void call(_i15.Canvas? canvas) => super.noSuchMethod( Invocation.method( #call, [canvas], @@ -1646,81 +1723,91 @@ class MockTextCommand extends _i1.Mock implements _i8.TextCommand { ), returnValue: {}, ) as Map); + + @override + _i25.Future prepareForRuntime() => (super.noSuchMethod( + Invocation.method( + #prepareForRuntime, + [], + ), + returnValue: _i25.Future.value(), + returnValueForMissingStub: _i25.Future.value(), + ) as _i25.Future); } /// A class which mocks [GraphicFactory]. /// /// See the documentation for Mockito's code generation for more information. -class MockGraphicFactory extends _i1.Mock implements _i23.GraphicFactory { +class MockGraphicFactory extends _i1.Mock implements _i26.GraphicFactory { MockGraphicFactory() { _i1.throwOnMissingStub(this); } @override - _i13.Paint createPaint() => (super.noSuchMethod( + _i15.Paint createPaint() => (super.noSuchMethod( Invocation.method( #createPaint, [], ), - returnValue: _i21.dummyValue<_i13.Paint>( + returnValue: _i24.dummyValue<_i15.Paint>( this, Invocation.method( #createPaint, [], ), ), - ) as _i13.Paint); + ) as _i15.Paint); @override - _i15.PathWithActionHistory createPathWithActionHistory() => + _i17.PathWithActionHistory createPathWithActionHistory() => (super.noSuchMethod( Invocation.method( #createPathWithActionHistory, [], ), - returnValue: _FakePathWithActionHistory_16( + returnValue: _FakePathWithActionHistory_18( this, Invocation.method( #createPathWithActionHistory, [], ), ), - ) as _i15.PathWithActionHistory); + ) as _i17.PathWithActionHistory); @override - _i13.PictureRecorder createPictureRecorder() => (super.noSuchMethod( + _i15.PictureRecorder createPictureRecorder() => (super.noSuchMethod( Invocation.method( #createPictureRecorder, [], ), - returnValue: _FakePictureRecorder_17( + returnValue: _FakePictureRecorder_19( this, Invocation.method( #createPictureRecorder, [], ), ), - ) as _i13.PictureRecorder); + ) as _i15.PictureRecorder); @override - _i13.Canvas createCanvasWithRecorder(_i13.PictureRecorder? recorder) => + _i15.Canvas createCanvasWithRecorder(_i15.PictureRecorder? recorder) => (super.noSuchMethod( Invocation.method( #createCanvasWithRecorder, [recorder], ), - returnValue: _FakeCanvas_18( + returnValue: _FakeCanvas_20( this, Invocation.method( #createCanvasWithRecorder, [recorder], ), ), - ) as _i13.Canvas); + ) as _i15.Canvas); @override - _i13.Paint createWatercolorPaint( - _i13.Paint? originalPaint, + _i15.Paint createWatercolorPaint( + _i15.Paint? originalPaint, double? blurSigma, ) => (super.noSuchMethod( @@ -1731,7 +1818,7 @@ class MockGraphicFactory extends _i1.Mock implements _i23.GraphicFactory { blurSigma, ], ), - returnValue: _i21.dummyValue<_i13.Paint>( + returnValue: _i24.dummyValue<_i15.Paint>( this, Invocation.method( #createWatercolorPaint, @@ -1741,43 +1828,43 @@ class MockGraphicFactory extends _i1.Mock implements _i23.GraphicFactory { ], ), ), - ) as _i13.Paint); + ) as _i15.Paint); @override - _i13.Paint copyPaint(_i13.Paint? original) => (super.noSuchMethod( + _i15.Paint copyPaint(_i15.Paint? original) => (super.noSuchMethod( Invocation.method( #copyPaint, [original], ), - returnValue: _i21.dummyValue<_i13.Paint>( + returnValue: _i24.dummyValue<_i15.Paint>( this, Invocation.method( #copyPaint, [original], ), ), - ) as _i13.Paint); + ) as _i15.Paint); } /// A class which mocks [BoundingBox]. /// /// See the documentation for Mockito's code generation for more information. -class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { +class MockBoundingBox extends _i1.Mock implements _i27.BoundingBox { MockBoundingBox() { _i1.throwOnMissingStub(this); } @override - _i13.Offset get center => (super.noSuchMethod( + _i15.Offset get center => (super.noSuchMethod( Invocation.getter(#center), - returnValue: _FakeOffset_14( + returnValue: _FakeOffset_16( this, Invocation.getter(#center), ), - ) as _i13.Offset); + ) as _i15.Offset); @override - set center(_i13.Offset? _center) => super.noSuchMethod( + set center(_i15.Offset? _center) => super.noSuchMethod( Invocation.setter( #center, _center, @@ -1831,13 +1918,13 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - _i25.BoundingBoxAction get currentAction => (super.noSuchMethod( + _i28.BoundingBoxAction get currentAction => (super.noSuchMethod( Invocation.getter(#currentAction), - returnValue: _i25.BoundingBoxAction.none, - ) as _i25.BoundingBoxAction); + returnValue: _i28.BoundingBoxAction.none, + ) as _i28.BoundingBoxAction); @override - set currentAction(_i25.BoundingBoxAction? _currentAction) => + set currentAction(_i28.BoundingBoxAction? _currentAction) => super.noSuchMethod( Invocation.setter( #currentAction, @@ -1847,15 +1934,15 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - _i26.BoundingBoxResizeAction get currentBoundingBoxResizeAction => + _i29.BoundingBoxResizeAction get currentBoundingBoxResizeAction => (super.noSuchMethod( Invocation.getter(#currentBoundingBoxResizeAction), - returnValue: _i26.BoundingBoxResizeAction.none, - ) as _i26.BoundingBoxResizeAction); + returnValue: _i29.BoundingBoxResizeAction.none, + ) as _i29.BoundingBoxResizeAction); @override set currentBoundingBoxResizeAction( - _i26.BoundingBoxResizeAction? _currentBoundingBoxResizeAction) => + _i29.BoundingBoxResizeAction? _currentBoundingBoxResizeAction) => super.noSuchMethod( Invocation.setter( #currentBoundingBoxResizeAction, @@ -1865,7 +1952,7 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - set lastDragGlobalPosition(_i13.Offset? _lastDragGlobalPosition) => + set lastDragGlobalPosition(_i15.Offset? _lastDragGlobalPosition) => super.noSuchMethod( Invocation.setter( #lastDragGlobalPosition, @@ -1875,7 +1962,7 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - set dragStartLocalPosition(_i13.Offset? _dragStartLocalPosition) => + set dragStartLocalPosition(_i15.Offset? _dragStartLocalPosition) => super.noSuchMethod( Invocation.setter( #dragStartLocalPosition, @@ -1901,16 +1988,16 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - _i13.Paint get boxPaint => (super.noSuchMethod( + _i15.Paint get boxPaint => (super.noSuchMethod( Invocation.getter(#boxPaint), - returnValue: _i21.dummyValue<_i13.Paint>( + returnValue: _i24.dummyValue<_i15.Paint>( this, Invocation.getter(#boxPaint), ), - ) as _i13.Paint); + ) as _i15.Paint); @override - set boxPaint(_i13.Paint? _boxPaint) => super.noSuchMethod( + set boxPaint(_i15.Paint? _boxPaint) => super.noSuchMethod( Invocation.setter( #boxPaint, _boxPaint, @@ -1919,16 +2006,16 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - _i13.Paint get handlePaint => (super.noSuchMethod( + _i15.Paint get handlePaint => (super.noSuchMethod( Invocation.getter(#handlePaint), - returnValue: _i21.dummyValue<_i13.Paint>( + returnValue: _i24.dummyValue<_i15.Paint>( this, Invocation.getter(#handlePaint), ), - ) as _i13.Paint); + ) as _i15.Paint); @override - set handlePaint(_i13.Paint? _handlePaint) => super.noSuchMethod( + set handlePaint(_i15.Paint? _handlePaint) => super.noSuchMethod( Invocation.setter( #handlePaint, _handlePaint, @@ -1937,16 +2024,16 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - _i13.Paint get rotationHandlePaint => (super.noSuchMethod( + _i15.Paint get rotationHandlePaint => (super.noSuchMethod( Invocation.getter(#rotationHandlePaint), - returnValue: _i21.dummyValue<_i13.Paint>( + returnValue: _i24.dummyValue<_i15.Paint>( this, Invocation.getter(#rotationHandlePaint), ), - ) as _i13.Paint); + ) as _i15.Paint); @override - set rotationHandlePaint(_i13.Paint? _rotationHandlePaint) => + set rotationHandlePaint(_i15.Paint? _rotationHandlePaint) => super.noSuchMethod( Invocation.setter( #rotationHandlePaint, @@ -1971,25 +2058,25 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - _i13.Rect get rect => (super.noSuchMethod( + _i15.Rect get rect => (super.noSuchMethod( Invocation.getter(#rect), - returnValue: _FakeRect_13( + returnValue: _FakeRect_15( this, Invocation.getter(#rect), ), - ) as _i13.Rect); + ) as _i15.Rect); @override - List<_i13.Offset> getCorners() => (super.noSuchMethod( + List<_i15.Offset> getCorners() => (super.noSuchMethod( Invocation.method( #getCorners, [], ), - returnValue: <_i13.Offset>[], - ) as List<_i13.Offset>); + returnValue: <_i15.Offset>[], + ) as List<_i15.Offset>); @override - void determineAction(_i13.Offset? globalPoint) => super.noSuchMethod( + void determineAction(_i15.Offset? globalPoint) => super.noSuchMethod( Invocation.method( #determineAction, [globalPoint], @@ -1998,7 +2085,7 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - void updateDrag(_i13.Offset? globalPoint) => super.noSuchMethod( + void updateDrag(_i15.Offset? globalPoint) => super.noSuchMethod( Invocation.method( #updateDrag, [globalPoint], @@ -2016,7 +2103,7 @@ class MockBoundingBox extends _i1.Mock implements _i24.BoundingBox { ); @override - void drawGuides(_i13.Canvas? canvas) => super.noSuchMethod( + void drawGuides(_i15.Canvas? canvas) => super.noSuchMethod( Invocation.method( #drawGuides, [canvas], diff --git a/test/utils/clipboard_tool_util.dart b/test/utils/clipboard_tool_util.dart new file mode 100644 index 00000000..25455075 --- /dev/null +++ b/test/utils/clipboard_tool_util.dart @@ -0,0 +1,70 @@ +import 'dart:ui' as ui; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:paintroid/app.dart'; +import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/providers/object/clipboard_tool_options_state_provider.dart'; +import 'package:paintroid/core/tools/tool_data.dart'; + +import 'ui_interaction.dart'; +import 'canvas_positions.dart'; + +class ClipboardIntegrationTestUtils { + static Future launchAppAndInit(WidgetTester tester) async { + await tester.pumpWidget( + ProviderScope( + child: App(showOnboardingPage: false), + ), + ); + UIInteraction.initialize(tester); + await UIInteraction.createNewImage(); + await tester.pumpAndSettle(); + } + + static Future selectTool(WidgetTester tester, String toolName) async { + await UIInteraction.selectTool(toolName); + await tester.pumpAndSettle(); + } + + static Future createTestImage(int w, int h) async { + final recorder = ui.PictureRecorder(); + final canvas = ui.Canvas(recorder); + final paint = ui.Paint()..color = const ui.Color(0xFF00FF00); + canvas.drawRect(ui.Rect.fromLTWH(0, 0, w.toDouble(), h.toDouble()), paint); + final picture = recorder.endRecording(); + return picture.toImage(w, h); + } + + static Future drawSomethingOnCanvas(WidgetTester tester) async { + await selectTool(tester, ToolData.SHAPES.name); + await UIInteraction.tapAt(CanvasPosition.center); + await tester.pumpAndSettle(); + await UIInteraction.clickCheckmark(); + await tester.pumpAndSettle(); + } + + static Future drawSomethingElseOnCanvas(WidgetTester tester) async { + await selectTool(tester, ToolData.SHAPES.name); + await UIInteraction.tapAt(CanvasPosition.topLeft); + await tester.pumpAndSettle(); + await UIInteraction.clickCheckmark(); + await tester.pumpAndSettle(); + } + + static Future getCanvasImage(WidgetTester tester, + {bool forceUpdate = true}) async { + final container = + ProviderScope.containerOf(tester.element(find.byType(App))); + if (forceUpdate) { + await container.read(canvasStateProvider.notifier).updateCachedImage(); + } + return container.read(canvasStateProvider).cachedImage; + } + + static bool getHasCopiedContent(WidgetTester tester) { + final container = + ProviderScope.containerOf(tester.element(find.byType(App))); + return container.read(clipboardToolOptionsStateProvider).hasCopiedContent; + } +} diff --git a/test/widget/workspace_page/clipboard_tool_test.dart b/test/widget/workspace_page/clipboard_tool_test.dart new file mode 100644 index 00000000..af37e352 --- /dev/null +++ b/test/widget/workspace_page/clipboard_tool_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:paintroid/app.dart'; +import 'package:paintroid/core/tools/tool_data.dart'; +import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/clipboard_tool_options.dart'; +import 'package:paintroid/ui/shared/custom_action_chip.dart'; + +import '../../utils/test_utils.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + late Widget sut; + + setUp(() async { + sut = ProviderScope(child: App(showOnboardingPage: false)); + }); + + Future selectClipboardTool(WidgetTester tester) async { + await UIInteraction.selectTool(ToolData.CLIPBOARD.name); + await tester.pumpAndSettle(); + expect(find.byType(ClipboardToolOptions), findsOneWidget); + } + + testWidgets( + '[CLIPBOARD_TOOL_OPTIONS]: initial state displays all action buttons', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + await selectClipboardTool(tester); + + expect(find.widgetWithIcon(CustomActionChip, Icons.copy), findsOneWidget); + expect(find.widgetWithIcon(CustomActionChip, Icons.content_cut), + findsOneWidget); + expect(find.widgetWithIcon(CustomActionChip, Icons.paste), findsOneWidget); + }); + + testWidgets( + '[CLIPBOARD_TOOL]: selecting clipboard tool does not show plus or checkmark', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + + expect(WidgetFinder.plusButton, findsNothing); + expect(WidgetFinder.checkMark, findsNothing); + + await UIInteraction.selectTool(ToolData.CLIPBOARD.name); + await tester.pumpAndSettle(); + + expect(WidgetFinder.plusButton, findsNothing); + expect(WidgetFinder.checkMark, findsNothing); + }); +}