diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7cef8453..e670faaf 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -121,17 +121,17 @@ SPEC CHECKSUMS: file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 launch_review: 75d5a956ba8eaa493e9c9d4bf4c05e505e8d5ed0 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 PODFILE CHECKSUM: 303789365c3a8d7bc562e5e65d7e8e15218ec5c6 diff --git a/lib/core/commands/command_factory/command_factory.dart b/lib/core/commands/command_factory/command_factory.dart index 3d9aaab1..69ad9e24 100644 --- a/lib/core/commands/command_factory/command_factory.dart +++ b/lib/core/commands/command_factory/command_factory.dart @@ -3,7 +3,9 @@ import 'dart:ui'; 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/circle_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; class CommandFactory { @@ -38,4 +40,28 @@ class CommandFactory { Offset center, ) => CircleShapeCommand(paint, radius, center); + + StarShapeCommand createStarShapeCommand( + Paint paint, + int numPoints, + double radius, + double angle, + Offset center, + ) => + StarShapeCommand( + paint, + numPoints, + radius, + angle, + center, + ); + + HeartShapeCommand createHeartShapeCommand( + Paint paint, + double width, + double height, + double angle, + Offset center, + ) => + HeartShapeCommand(paint, width, height, angle, center); } diff --git a/lib/core/commands/command_implementation/command.dart b/lib/core/commands/command_implementation/command.dart index 714e39c2..0f9ca429 100644 --- a/lib/core/commands/command_implementation/command.dart +++ b/lib/core/commands/command_implementation/command.dart @@ -3,6 +3,7 @@ import 'package:paintroid/core/commands/command_implementation/graphic/line_comm import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; abstract class Command with EquatableMixin { @@ -21,6 +22,8 @@ abstract class Command with EquatableMixin { return SquareShapeCommand.fromJson(json); case SerializerType.CIRCLE_SHAPE_COMMAND: return CircleShapeCommand.fromJson(json); + case SerializerType.STAR_SHAPE_COMMAND: + return StarShapeCommand.fromJson(json); default: return PathCommand.fromJson(json); } diff --git a/lib/core/commands/command_implementation/graphic/shape/heart_shape_command.dart b/lib/core/commands/command_implementation/graphic/shape/heart_shape_command.dart new file mode 100644 index 00000000..67741761 --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/shape/heart_shape_command.dart @@ -0,0 +1,94 @@ +import 'dart:math'; + +import 'package:flutter/widgets.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/shape_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/versioning/serializer_version.dart'; +import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; + +part 'heart_shape_command.g.dart'; + +@JsonSerializable() +class HeartShapeCommand extends ShapeCommand { + final double width; + final double height; + final double angle; + @OffsetConverter() + final Offset center; + + final int version; + final String type; + + HeartShapeCommand( + super.paint, + this.width, + this.height, + this.angle, + this.center, { + int? version, + this.type = SerializerType.HEART_SHAPE_COMMAND, + }) : version = version ?? + VersionStrategyManager.strategy.getHeartShapeCommandVersion(); + + Path get path => _getHeartPath(angle); + + @override + void call(Canvas canvas) => canvas.drawPath(path, paint); + + @override + List get props => [paint, width, height, center]; + + @override + Map toJson() => _$HeartShapeCommandToJson(this); + + factory HeartShapeCommand.fromJson(Map json) { + int version = json['version'] as int; + + switch (version) { + case Version.v1: + return _$HeartShapeCommandFromJson(json); + default: + return _$HeartShapeCommandFromJson(json); + } + } + + Path _getHeartPath(double angle) { + Path path = Path(); + final double w = width / 2; + final double h = height / 2; + const double rotationOffset = 3 * pi / 4; + + path.moveTo(0, -h * 0.25); + + path.cubicTo( + -w, + -h * 1.25, + -w * 1.25, + h * 0.25, + 0, + h * 0.75, + ); + + path.cubicTo( + w * 1.25, + h * 0.25, + w, + -h * 1.25, + 0, + -h * 0.25, + ); + + path.close(); + + final Matrix4 rotationMatrix = Matrix4.identity() + ..rotateZ(angle + rotationOffset); + + path = path.transform(rotationMatrix.storage); + + path = path.shift(center); + + return path; + } +} diff --git a/lib/core/commands/command_implementation/graphic/shape/heart_shape_command.g.dart b/lib/core/commands/command_implementation/graphic/shape/heart_shape_command.g.dart new file mode 100644 index 00000000..ddecba98 --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/shape/heart_shape_command.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'heart_shape_command.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +HeartShapeCommand _$HeartShapeCommandFromJson(Map json) => + HeartShapeCommand( + const PaintConverter().fromJson(json['paint'] as Map), + (json['width'] as num).toDouble(), + (json['height'] as num).toDouble(), + (json['angle'] as num).toDouble(), + const OffsetConverter().fromJson(json['center'] as Map), + version: (json['version'] as num?)?.toInt(), + type: json['type'] as String? ?? SerializerType.HEART_SHAPE_COMMAND, + ); + +Map _$HeartShapeCommandToJson(HeartShapeCommand instance) => + { + 'paint': const PaintConverter().toJson(instance.paint), + 'width': instance.width, + 'height': instance.height, + 'angle': instance.angle, + 'center': const OffsetConverter().toJson(instance.center), + 'version': instance.version, + 'type': instance.type, + }; diff --git a/lib/core/commands/command_implementation/graphic/shape/star_shape_command.dart b/lib/core/commands/command_implementation/graphic/shape/star_shape_command.dart new file mode 100644 index 00000000..f7340d8c --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/shape/star_shape_command.dart @@ -0,0 +1,78 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/shape_command.dart'; +import 'package:paintroid/core/extensions/path_extension.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/versioning/serializer_version.dart'; +import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; + +part 'star_shape_command.g.dart'; + +@JsonSerializable() +class StarShapeCommand extends ShapeCommand { + final int numberOfPoints; + final double radius; + final double angle; + @OffsetConverter() + final Offset center; + + final int version; + final String type; + + StarShapeCommand( + super.paint, + this.numberOfPoints, + this.radius, + this.angle, + this.center, { + int? version, + this.type = SerializerType.STAR_SHAPE_COMMAND, + }) : version = version ?? + VersionStrategyManager.strategy.getStarShapeCommandVersion(); + + Path get path => _getStarPath(); + + @override + void call(Canvas canvas) => canvas.drawPath(path, paint); + + @override + List get props => [paint, numberOfPoints, radius, center]; + + @override + Map toJson() => _$StarShapeCommandToJson(this); + + factory StarShapeCommand.fromJson(Map json) { + int version = json['version'] as int; + + switch (version) { + case Version.v1: + return _$StarShapeCommandFromJson(json); + case Version.v2: + // For different versions of StarShapeCommand the deserialization + // has to be implemented manually. + // Autogenerated code can only be used for one version + default: + return _$StarShapeCommandFromJson(json); + } + } + + Path _getStarPath() { + final path = Path(); + final innerRadius = radius / 2; + final angleStep = pi / numberOfPoints; + for (int i = 0; i < numberOfPoints * 2; i++) { + final currentRadius = (i % 2 == 0) ? radius : innerRadius; + final currentAngle = i * angleStep + angle; + final point = Offset( + center.dx + currentRadius * cos(currentAngle), + center.dy + currentRadius * sin(currentAngle), + ); + i == 0 ? path.moveToOffset(point) : path.lineToOffset(point); + } + path.close(); + return path; + } +} diff --git a/lib/core/commands/command_implementation/graphic/shape/star_shape_command.g.dart b/lib/core/commands/command_implementation/graphic/shape/star_shape_command.g.dart new file mode 100644 index 00000000..2cba3a92 --- /dev/null +++ b/lib/core/commands/command_implementation/graphic/shape/star_shape_command.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'star_shape_command.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +StarShapeCommand _$StarShapeCommandFromJson(Map json) => + StarShapeCommand( + const PaintConverter().fromJson(json['paint'] as Map), + (json['numberOfPoints'] as num).toInt(), + (json['radius'] as num).toDouble(), + (json['angle'] as num).toDouble(), + const OffsetConverter().fromJson(json['center'] as Map), + version: (json['version'] as num?)?.toInt(), + type: json['type'] as String? ?? SerializerType.STAR_SHAPE_COMMAND, + ); + +Map _$StarShapeCommandToJson(StarShapeCommand instance) => + { + 'paint': const PaintConverter().toJson(instance.paint), + 'numberOfPoints': instance.numberOfPoints, + 'radius': instance.radius, + 'angle': instance.angle, + 'center': const OffsetConverter().toJson(instance.center), + '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 2a658d86..b7b8e38b 100644 --- a/lib/core/commands/command_manager/command_manager.dart +++ b/lib/core/commands/command_manager/command_manager.dart @@ -1,10 +1,13 @@ import 'dart:ui'; +import 'package:flutter/material.dart'; import 'package:paintroid/core/commands/command_implementation/command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart'; import 'package:paintroid/core/tools/line_tool/vertex.dart'; import 'package:paintroid/core/tools/line_tool/vertex_stack.dart'; import 'package:paintroid/core/tools/tool_data.dart'; @@ -109,6 +112,10 @@ class CommandManager { return ToolData.SHAPES; } else if (command.runtimeType == CircleShapeCommand) { return ToolData.SHAPES; + } else if (command.runtimeType == StarShapeCommand) { + return ToolData.SHAPES; + } else if (command.runtimeType == HeartShapeCommand) { + return ToolData.SHAPES; } else { return ToolData.BRUSH; } diff --git a/lib/core/database/project_database.g.dart b/lib/core/database/project_database.g.dart index 56d553e9..ea36ed0d 100644 --- a/lib/core/database/project_database.g.dart +++ b/lib/core/database/project_database.g.dart @@ -6,32 +6,21 @@ part of 'project_database.dart'; // FloorGenerator // ************************************************************************** -abstract class $ProjectDatabaseBuilderContract { - /// Adds migrations to the builder. - $ProjectDatabaseBuilderContract addMigrations(List migrations); - - /// Adds a database [Callback] to the builder. - $ProjectDatabaseBuilderContract addCallback(Callback callback); - - /// Creates the database and initializes it. - Future build(); -} - // ignore: avoid_classes_with_only_static_members class $FloorProjectDatabase { /// Creates a database builder for a persistent database. /// Once a database is built, you should keep a reference to it and re-use it. - static $ProjectDatabaseBuilderContract databaseBuilder(String name) => + static _$ProjectDatabaseBuilder databaseBuilder(String name) => _$ProjectDatabaseBuilder(name); /// Creates a database builder for an in memory database. /// Information stored in an in memory database disappears when the process is killed. /// Once a database is built, you should keep a reference to it and re-use it. - static $ProjectDatabaseBuilderContract inMemoryDatabaseBuilder() => + static _$ProjectDatabaseBuilder inMemoryDatabaseBuilder() => _$ProjectDatabaseBuilder(null); } -class _$ProjectDatabaseBuilder implements $ProjectDatabaseBuilderContract { +class _$ProjectDatabaseBuilder { _$ProjectDatabaseBuilder(this.name); final String? name; @@ -40,19 +29,19 @@ class _$ProjectDatabaseBuilder implements $ProjectDatabaseBuilderContract { Callback? _callback; - @override - $ProjectDatabaseBuilderContract addMigrations(List migrations) { + /// Adds migrations to the builder. + _$ProjectDatabaseBuilder addMigrations(List migrations) { _migrations.addAll(migrations); return this; } - @override - $ProjectDatabaseBuilderContract addCallback(Callback callback) { + /// Adds a database [Callback] to the builder. + _$ProjectDatabaseBuilder addCallback(Callback callback) { _callback = callback; return this; } - @override + /// Creates the database and initializes it. Future build() async { final path = name != null ? await sqfliteDatabaseFactory.getDatabasePath(name!) diff --git a/lib/core/enums/shape_type.dart b/lib/core/enums/shape_type.dart index ceef618b..e438b246 100644 --- a/lib/core/enums/shape_type.dart +++ b/lib/core/enums/shape_type.dart @@ -1,4 +1 @@ -enum ShapeType { - circle, - square, -} +enum ShapeType { circle, square, star, heart } diff --git a/lib/core/json_serialization/converter/offset_converter.dart b/lib/core/json_serialization/converter/offset_converter.dart index 79d416fc..6330c06e 100644 --- a/lib/core/json_serialization/converter/offset_converter.dart +++ b/lib/core/json_serialization/converter/offset_converter.dart @@ -12,9 +12,6 @@ class OffsetConverter implements JsonConverter> { @override Map toJson(Offset offset) { - return { - 'dx': offset.dx, - 'dy': offset.dy, - }; + return {'dx': offset.dx, 'dy': offset.dy}; } } diff --git a/lib/core/json_serialization/versioning/serializer_version.dart b/lib/core/json_serialization/versioning/serializer_version.dart index 4867c010..f56d079e 100644 --- a/lib/core/json_serialization/versioning/serializer_version.dart +++ b/lib/core/json_serialization/versioning/serializer_version.dart @@ -5,6 +5,8 @@ class SerializerVersion { static const int LINE_COMMAND_VERSION = Version.v1; static const int SQUARE_SHAPE_COMMAND_VERSION = Version.v1; static const int CIRCLE_SHAPE_COMMAND_VERSION = Version.v1; + static const int STAR_SHAPE_COMMAND_VERSION = Version.v1; + static const int HEART_SHAPE_COMMAND_VERSION = Version.v1; } class Version { @@ -21,4 +23,6 @@ class SerializerType { static const String CLOSE_ACTION = 'CloseAction'; static const String SQUARE_SHAPE_COMMAND = 'SquareShapeCommand'; static const String CIRCLE_SHAPE_COMMAND = 'CircleShapeCommand'; + static const String STAR_SHAPE_COMMAND = 'StarShapeCommand'; + static const String HEART_SHAPE_COMMAND = 'HeartShapeCommand'; } diff --git a/lib/core/json_serialization/versioning/version_strategy.dart b/lib/core/json_serialization/versioning/version_strategy.dart index 10b9d8cb..12bd1235 100644 --- a/lib/core/json_serialization/versioning/version_strategy.dart +++ b/lib/core/json_serialization/versioning/version_strategy.dart @@ -6,6 +6,8 @@ abstract class IVersionStrategy { int getLineCommandVersion(); int getSquareShapeCommandVersion(); int getCircleShapeCommandVersion(); + int getStarShapeCommandVersion(); + int getHeartShapeCommandVersion(); } class ProductionVersionStrategy implements IVersionStrategy { @@ -25,6 +27,14 @@ class ProductionVersionStrategy implements IVersionStrategy { @override int getCircleShapeCommandVersion() => SerializerVersion.CIRCLE_SHAPE_COMMAND_VERSION; + + @override + int getStarShapeCommandVersion() => + SerializerVersion.STAR_SHAPE_COMMAND_VERSION; + + @override + int getHeartShapeCommandVersion() => + SerializerVersion.HEART_SHAPE_COMMAND_VERSION; } class VersionStrategyManager { diff --git a/lib/core/tools/implementation/shapes_tool/bounding_box.dart b/lib/core/tools/implementation/shapes_tool/bounding_box.dart index 82b4d978..11daed76 100644 --- a/lib/core/tools/implementation/shapes_tool/bounding_box.dart +++ b/lib/core/tools/implementation/shapes_tool/bounding_box.dart @@ -1,5 +1,6 @@ -import 'dart:ui'; +import 'dart:math'; +import 'package:flutter/widgets.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; import 'package:paintroid/core/enums/bounding_box_corners.dart'; import 'package:paintroid/core/extensions/offset_extension.dart'; @@ -47,6 +48,12 @@ class BoundingBox { double get innerRadius => distanceToEdgeFromCenter - padding; + double get angle => (topLeft - center).direction; + + double get width => topLeft.distanceTo(topRight); + + double get height => topLeft.distanceTo(bottomLeft); + double get activeCornerDirection { switch (activeCorner) { case BoundingBoxCorner.topLeft: @@ -219,4 +226,59 @@ class BoundingBox { ..lineToOffset(getPaddedBottomRight(padding: padding)) ..lineToOffset(getPaddedBottomLeft(padding: padding)) ..close(); + + Path getStarPath(int numberOfPoints, double radius) { + final path = Path(); + final innerRadius = radius / 2; + final angleStep = pi / numberOfPoints; + for (int i = 0; i < numberOfPoints * 2; i++) { + final currentRadius = (i % 2 == 0) ? radius : innerRadius; + final currentAngle = i * angleStep + angle; + final point = Offset( + center.dx + currentRadius * cos(currentAngle), + center.dy + currentRadius * sin(currentAngle), + ); + i == 0 ? path.moveToOffset(point) : path.lineToOffset(point); + } + path.close(); + return path; + } + + Path getHeartPath() { + Path path = Path(); + final double w = width / 2; + final double h = height / 2; + const double rotationOffset = 3 * pi / 4; + + path.moveTo(0, -h * 0.25); + + path.cubicTo( + -w, + -h * 1.25, + -w * 1.25, + h * 0.25, + 0, + h * 0.75, + ); + + path.cubicTo( + w * 1.25, + h * 0.25, + w, + -h * 1.25, + 0, + -h * 0.25, + ); + + path.close(); + + final Matrix4 rotationMatrix = Matrix4.identity() + ..rotateZ(angle + rotationOffset); + + path = path.transform(rotationMatrix.storage); + + path = path.shift(center); + + return path; + } } diff --git a/lib/core/tools/implementation/shapes_tool/shapes_tool.dart b/lib/core/tools/implementation/shapes_tool/shapes_tool.dart index ac1fda68..3f44456c 100644 --- a/lib/core/tools/implementation/shapes_tool/shapes_tool.dart +++ b/lib/core/tools/implementation/shapes_tool/shapes_tool.dart @@ -8,6 +8,7 @@ import 'package:paintroid/core/tools/implementation/shapes_tool/bounding_box.dar import 'package:paintroid/core/tools/tool.dart'; class ShapesTool extends Tool { + static const starShapeNumberOfPoints = 5; bool isRotating; BoundingBox boundingBox; ShapeType shapeType; @@ -59,6 +60,24 @@ class ShapesTool extends Tool { boundingBox.center, ); break; + case ShapeType.star: + final radius = boundingBox.innerRadius - padding; + command = commandFactory.createStarShapeCommand( + paint, + starShapeNumberOfPoints, + radius, + boundingBox.angle, + boundingBox.center, + ); + break; + case ShapeType.heart: + command = commandFactory.createHeartShapeCommand( + paint, + boundingBox.width, + boundingBox.height, + boundingBox.angle, + boundingBox.center, + ); } commandManager.addGraphicCommand(command); commandManager.clearRedoStack(); @@ -83,6 +102,13 @@ class ShapesTool extends Tool { final radius = boundingBox.innerRadius - padding; canvas.drawCircle(boundingBox.center, radius, paint); break; + case ShapeType.star: + final radius = boundingBox.innerRadius - padding; + final path = boundingBox.getStarPath(starShapeNumberOfPoints, radius); + canvas.drawPath(path, paint); + case ShapeType.heart: + final path = boundingBox.getHeartPath(); + canvas.drawPath(path, paint); } } @@ -116,6 +142,8 @@ class ShapesTool extends Tool { double _calculatePaddingAdjustedForStrokeWidth(double strokeWidth) => switch (shapeType) { ShapeType.square => strokeWidth * sqrt2 / 2, - ShapeType.circle => strokeWidth / 2 + ShapeType.circle => strokeWidth / 2, + ShapeType.star => strokeWidth * sqrt2 / 2, + ShapeType.heart => strokeWidth / 2, }; } diff --git a/lib/core/utils/widget_identifier.dart b/lib/core/utils/widget_identifier.dart index 0276cec6..ad1826c0 100644 --- a/lib/core/utils/widget_identifier.dart +++ b/lib/core/utils/widget_identifier.dart @@ -1,10 +1,11 @@ class WidgetIdentifier { static const canvasPainter = 'CanvasPainter'; static const newImageActionButton = 'NewImageActionButton'; + static const squareShapeTypeChip = 'SquareShapeTypeChip'; static const circleShapeTypeChip = 'CircleShapeTypeChip'; + static const starShapeTypeChip = 'StarShapeTypeChip'; + static const heartShapeTypeChip = 'HeartShapeTypeChip'; static const backButton = 'BackButton'; - - // GenericDialogAction static const genericDialogActionDone = 'GenericDialogActionDone'; static const genericDialogActionDiscard = 'GenericDialogActionDiscard'; static const genericDialogActionSave = 'GenericDialogActionSave'; diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/shapes_tool/shapes_tool_shape_type_options.dart b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/shapes_tool/shapes_tool_shape_type_options.dart index 2eddcf8c..5cadb7f7 100644 --- a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/shapes_tool/shapes_tool_shape_type_options.dart +++ b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/shapes_tool/shapes_tool_shape_type_options.dart @@ -22,9 +22,11 @@ class ShapesToolShapeTypeOptions extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ Wrap( - spacing: 8, children: [ CustomActionChip( + key: const ValueKey( + WidgetIdentifier.squareShapeTypeChip, + ), hint: 'Square', chipBackgroundColor: shapesToolOptionsState.shapeType == ShapeType.square @@ -54,6 +56,40 @@ class ShapesToolShapeTypeOptions extends ConsumerWidget { onPressed: () => ref .read(shapesToolOptionsStateProvider.notifier) .setShapeType(shapeType: ShapeType.circle), + ), + CustomActionChip( + key: const ValueKey( + WidgetIdentifier.starShapeTypeChip, + ), + hint: 'Star', + chipBackgroundColor: + shapesToolOptionsState.shapeType == ShapeType.star + ? PaintroidTheme.of(context).primaryColor + : Colors.white, + chipIcon: Icon( + Icons.star_outline, + color: PaintroidTheme.of(context).shadowColor, + ), + onPressed: () => ref + .read(shapesToolOptionsStateProvider.notifier) + .setShapeType(shapeType: ShapeType.star), + ), + CustomActionChip( + key: const ValueKey( + WidgetIdentifier.heartShapeTypeChip, + ), + hint: 'Heart', + chipBackgroundColor: + shapesToolOptionsState.shapeType == ShapeType.heart + ? PaintroidTheme.of(context).primaryColor + : Colors.white, + chipIcon: Icon( + Icons.favorite_outline, + color: PaintroidTheme.of(context).shadowColor, + ), + onPressed: () => ref + .read(shapesToolOptionsStateProvider.notifier) + .setShapeType(shapeType: ShapeType.heart), ) ], ) diff --git a/pubspec.lock b/pubspec.lock index 41146529..01c51cd8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -292,10 +292,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_picker: dependency: "direct main" description: @@ -607,10 +607,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" io: dependency: transitive description: @@ -651,6 +651,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -679,26 +703,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.0" mime: dependency: transitive description: @@ -751,10 +775,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_drawing: dependency: transitive description: @@ -871,10 +895,10 @@ packages: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: @@ -895,10 +919,10 @@ packages: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" pub_semver: dependency: transitive description: @@ -1196,10 +1220,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" timing: dependency: transitive description: @@ -1316,10 +1340,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "14.2.1" watcher: dependency: transitive description: @@ -1348,10 +1372,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" win32: dependency: transitive description: @@ -1393,5 +1417,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/test/integration/shapes_tool_test.dart b/test/integration/shapes_tool_test.dart index d35639f4..d42f4393 100644 --- a/test/integration/shapes_tool_test.dart +++ b/test/integration/shapes_tool_test.dart @@ -55,7 +55,9 @@ void main() { await tester.pumpWidget(sut); await UIInteraction.createNewImage(); await UIInteraction.selectTool(ToolData.SHAPES.name); - await UIInteraction.selectCircleShapeTypeChip(); + await UIInteraction.selectShapesToolShapeType( + WidgetFinder.circleShapeTypeChip, + ); final (left, top, right, bottom) = await UIInteraction.getCircleShapeColors(); @@ -79,4 +81,62 @@ void main() { expect(bottomAfter, currentColor); }); } + + if (testID == -1 || testID == 1) { + testWidgets('[SHAPES_TOOL]: test star shape', (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + await UIInteraction.selectTool(ToolData.SHAPES.name); + await UIInteraction.selectShapesToolShapeType( + WidgetFinder.starShapeTypeChip, + ); + + final colorsBefore = await UIInteraction.getStarShapeColors(); + + for (final color in colorsBefore) { + expect(color, Colors.transparent); + } + + await UIInteraction.tapAt(CanvasPosition.center); + await UIInteraction.clickCheckmark(); + + final colorsAfter = await UIInteraction.getStarShapeColors(); + + final currentColor = UIInteraction.getCurrentColor(); + + for (final color in colorsAfter) { + expect(color, currentColor); + } + }); + } + + if (testID == -1 || testID == 1) { + testWidgets('[SHAPES_TOOL]: test heart shape', (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + await UIInteraction.selectTool(ToolData.SHAPES.name); + await UIInteraction.selectShapesToolShapeType( + WidgetFinder.heartShapeTypeChip, + ); + + final colorsBefore = await UIInteraction.getHeartShapeColors(); + + for (final color in colorsBefore) { + expect(color, Colors.transparent); + } + + await UIInteraction.tapAt(CanvasPosition.center); + await UIInteraction.clickCheckmark(); + + final colorsAfter = await UIInteraction.getHeartShapeColors(); + + final currentColor = UIInteraction.getCurrentColor(); + + for (final color in colorsAfter) { + expect(color, currentColor); + } + }); + } } diff --git a/test/unit/command/shape_command_test.dart b/test/unit/command/shape_command_test.dart index 0b12c22f..1eb962f7 100644 --- a/test/unit/command/shape_command_test.dart +++ b/test/unit/command/shape_command_test.dart @@ -3,7 +3,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart'; import 'shape_command_test.mocks.dart'; @GenerateMocks([Canvas]) @@ -23,6 +25,27 @@ void main() { const center = Offset(200, 200); final circleShapeCommand = CircleShapeCommand(testPaint, radius, center); + const numberOfPoints = 5; + const angle = 0.0; + final starShapeCommand = StarShapeCommand( + testPaint, + numberOfPoints, + radius, + angle, + center, + ); + + const width = 200.0; + const height = 200.0; + + final heartShapeCommand = HeartShapeCommand( + testPaint, + width, + height, + angle, + center, + ); + setUp(() => mockCanvas = MockCanvas()); test('SquareShapeCommand: should call drawPath path', () { @@ -38,4 +61,18 @@ void main() { verify(mockCanvas.drawCircle(any, any, testPaint)); verifyNoMoreInteractions(mockCanvas); }); + + test('StarShapeCommand: should call drawPath path', () { + when(mockCanvas.drawPath(any, testPaint)).thenReturn(null); + starShapeCommand.call(mockCanvas); + verify(mockCanvas.drawPath(any, testPaint)); + verifyNoMoreInteractions(mockCanvas); + }); + + test('HeartShapeCommand: should call drawPath path', () { + when(mockCanvas.drawPath(any, testPaint)).thenReturn(null); + heartShapeCommand.call(mockCanvas); + verify(mockCanvas.drawPath(any, testPaint)); + verifyNoMoreInteractions(mockCanvas); + }); } diff --git a/test/unit/serialization/command/heart_shape_serializer_test.dart b/test/unit/serialization/command/heart_shape_serializer_test.dart new file mode 100644 index 00000000..36a4a24c --- /dev/null +++ b/test/unit/serialization/command/heart_shape_serializer_test.dart @@ -0,0 +1,43 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart'; +import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; + +import '../utils/dummy_command_factory.dart'; +import '../utils/dummy_paint_factory.dart'; + +void main() { + group('Version 1', () { + test('Test Star serialization', () { + const type = SerializerType.HEART_SHAPE_COMMAND; + final originalPaint = DummyPaintFactory.createPaint(version: Version.v1); + const center = Offset(100, 100); + const width = 50.0; + const height = 50.0; + const angle = 0.0; + + final command = DummyCommandFactory.createHeartShapeCommand( + originalPaint, + width, + height, + angle, + center, + ); + + final deserializedCommand = HeartShapeCommand.fromJson(command.toJson()); + + expect( + DummyPaintFactory.comparePaint( + originalPaint, + deserializedCommand.paint, + version: Version.v1, + ), + isTrue); + expect(command.version, equals(deserializedCommand.version)); + expect(deserializedCommand.center, equals(center)); + expect(deserializedCommand.type, equals(type)); + expect(deserializedCommand.angle, equals(angle)); + expect(deserializedCommand.width, equals(width)); + expect(deserializedCommand.height, equals(height)); + }); + }); +} diff --git a/test/unit/serialization/command/star_shape_serializer_test.dart b/test/unit/serialization/command/star_shape_serializer_test.dart new file mode 100644 index 00000000..da356b63 --- /dev/null +++ b/test/unit/serialization/command/star_shape_serializer_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart'; +import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; + +import '../utils/dummy_command_factory.dart'; +import '../utils/dummy_paint_factory.dart'; + +void main() { + group('Version 1', () { + test('Test Star serialization', () { + const type = SerializerType.STAR_SHAPE_COMMAND; + final originalPaint = DummyPaintFactory.createPaint(version: Version.v1); + const center = Offset(100, 100); + const radius = 50.0; + const numberOfPoints = 5; + const angle = 0.0; + final command = DummyCommandFactory.createStarShapeCommand( + originalPaint, + numberOfPoints, + radius, + angle, + center, + ); + + final deserializedCommand = StarShapeCommand.fromJson(command.toJson()); + + expect( + DummyPaintFactory.comparePaint( + originalPaint, + deserializedCommand.paint, + version: Version.v1, + ), + isTrue); + expect(command.version, equals(deserializedCommand.version)); + expect(deserializedCommand.center, equals(center)); + expect(deserializedCommand.radius, equals(radius)); + expect(deserializedCommand.type, equals(type)); + expect(deserializedCommand.numberOfPoints, equals(numberOfPoints)); + expect(deserializedCommand.angle, equals(angle)); + }); + }); +} diff --git a/test/unit/serialization/utils/dummy_command_factory.dart b/test/unit/serialization/utils/dummy_command_factory.dart index af32175b..fd96fa89 100644 --- a/test/unit/serialization/utils/dummy_command_factory.dart +++ b/test/unit/serialization/utils/dummy_command_factory.dart @@ -5,7 +5,9 @@ import 'package:paintroid/core/commands/command_implementation/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/circle_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; @@ -87,6 +89,46 @@ class DummyCommandFactory { return commandFactory.createCircleShapeCommand(paint, radius, center); } + static StarShapeCommand createStarShapeCommand( + Paint paint, + int numPoints, + double radius, + double angle, + Offset center, { + int version = Version.v1, + }) { + VersionStrategyManager.setStrategy( + DummyVersionStrategy(starShapeCommandVersion: version), + ); + return commandFactory.createStarShapeCommand( + paint, + numPoints, + radius, + angle, + center, + ); + } + + static HeartShapeCommand createHeartShapeCommand( + Paint paint, + double width, + double height, + double angle, + Offset center, { + int version = Version.v1, + }) { + VersionStrategyManager.setStrategy( + DummyVersionStrategy(heartShapeCommandVersion: version), + ); + return commandFactory.createHeartShapeCommand( + paint, + width, + height, + angle, + center, + ); + } + static bool compareCommandLists( Iterable commands1, Iterable commands2) { if (commands1.length != commands2.length) { diff --git a/test/unit/serialization/utils/dummy_version_strategy.dart b/test/unit/serialization/utils/dummy_version_strategy.dart index c42bdd9a..eaff6054 100644 --- a/test/unit/serialization/utils/dummy_version_strategy.dart +++ b/test/unit/serialization/utils/dummy_version_strategy.dart @@ -7,6 +7,8 @@ class DummyVersionStrategy implements IVersionStrategy { final int catrobatImageVersion; final int squareShapeCommandVersion; final int circleShapeCommandVersion; + final int starShapeCommandVersion; + final int heartShapeCommandVersion; DummyVersionStrategy({ this.pathCommandVersion = SerializerVersion.PATH_COMMAND_VERSION, @@ -16,6 +18,8 @@ class DummyVersionStrategy implements IVersionStrategy { SerializerVersion.SQUARE_SHAPE_COMMAND_VERSION, this.circleShapeCommandVersion = SerializerVersion.CIRCLE_SHAPE_COMMAND_VERSION, + this.starShapeCommandVersion = SerializerVersion.STAR_SHAPE_COMMAND_VERSION, + this.heartShapeCommandVersion = SerializerVersion.HEART_SHAPE_COMMAND_VERSION, }); @override @@ -32,4 +36,11 @@ class DummyVersionStrategy implements IVersionStrategy { @override int getCircleShapeCommandVersion() => circleShapeCommandVersion; + + @override + int getStarShapeCommandVersion() => starShapeCommandVersion; + + @override + int getHeartShapeCommandVersion() => heartShapeCommandVersion; + } diff --git a/test/unit/tools/shapes_tool_test.dart b/test/unit/tools/shapes_tool_test.dart index 428ff370..eee26a1a 100644 --- a/test/unit/tools/shapes_tool_test.dart +++ b/test/unit/tools/shapes_tool_test.dart @@ -2,7 +2,9 @@ import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/heart_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/shape/star_shape_command.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/enums/shape_type.dart'; import 'package:paintroid/core/enums/tool_types.dart'; @@ -31,6 +33,20 @@ void main() { ); }); + test('onCheckmark: should generate HearthShapeCommand', () { + sut.shapeType = ShapeType.heart; + sut.onCheckmark(paint); + final command = sut.commandManager.undoStack.last; + expect(command.runtimeType, HeartShapeCommand); + }); + + test('onCheckmark: should generate StarShapeCommand', () { + sut.shapeType = ShapeType.star; + sut.onCheckmark(paint); + final command = sut.commandManager.undoStack.last; + expect(command.runtimeType, StarShapeCommand); + }); + test('onCheckmark: should generate CircleShapeCommand', () { sut.shapeType = ShapeType.circle; sut.onCheckmark(paint); diff --git a/test/utils/ui_interaction.dart b/test/utils/ui_interaction.dart index f7f12ab7..6d2703eb 100644 --- a/test/utils/ui_interaction.dart +++ b/test/utils/ui_interaction.dart @@ -1,3 +1,6 @@ +import 'dart:math'; +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -76,6 +79,31 @@ class UIInteraction { return (leftPixel, rightPixel, topPixel, bottomPixel); } + static Future> getStarShapeColors() async { + final shapesTool = getShapesTool(); + final padding = getCurrentPaint().strokeWidth * sqrt2 / 2; + final radius = shapesTool.boundingBox.innerRadius - padding; + final path = shapesTool.boundingBox + .getStarPath(ShapesTool.starShapeNumberOfPoints, radius); + final points = extractPointsFromPath(path); + final colors = []; + for (final point in points) { + colors.add(await getPixelColor(point.dx.toInt(), point.dy.toInt())); + } + return colors; + } + + static Future> getHeartShapeColors() async { + final shapesTool = getShapesTool(); + final path = shapesTool.boundingBox.getHeartPath(); + final points = extractPointsFromPath(path); + final colors = []; + for (final point in points) { + colors.add(await getPixelColor(point.dx.toInt(), point.dy.toInt())); + } + return colors; + } + static Future getPixelColor(int x, int y) async { final container = ProviderScope.containerOf(tester.element(find.byType(App))); @@ -101,6 +129,19 @@ class UIInteraction { return Color(argbColor); } + static List extractPointsFromPath(Path path) { + final List points = []; + for (PathMetric pathMetric in path.computeMetrics()) { + for (double distance = 0; distance < pathMetric.length; distance += 1.0) { + Tangent? tangent = pathMetric.getTangentForOffset(distance); + if (tangent != null) { + points.add(tangent.position); + } + } + } + return points; +} + static Future createNewImage() async { expect(WidgetFinder.newImageButton, findsOneWidget); await tester.tap(WidgetFinder.newImageButton); @@ -187,9 +228,9 @@ class UIInteraction { } } - static Future selectCircleShapeTypeChip() async { - expect(WidgetFinder.circleShapeTypeChip, findsOneWidget); - await tester.tap(WidgetFinder.circleShapeTypeChip); + static Future selectShapesToolShapeType(Finder shapeTypeFinder) async { + expect(shapeTypeFinder, findsOneWidget); + await tester.tap(shapeTypeFinder); await tester.pumpAndSettle(); } diff --git a/test/utils/widget_finder.dart b/test/utils/widget_finder.dart index 31fc0b9f..d4f4f8ec 100644 --- a/test/utils/widget_finder.dart +++ b/test/utils/widget_finder.dart @@ -21,6 +21,12 @@ class WidgetFinder { find.byKey(ValueKey(TopBarActionData.REDO.name)); static final Finder circleShapeTypeChip = find.byKey(const ValueKey(WidgetIdentifier.circleShapeTypeChip)); + static final Finder squareShapeTypeChip = + find.byKey(const ValueKey(WidgetIdentifier.squareShapeTypeChip)); + static final Finder heartShapeTypeChip = + find.byKey(const ValueKey(WidgetIdentifier.heartShapeTypeChip)); + static final Finder starShapeTypeChip = + find.byKey(const ValueKey(WidgetIdentifier.starShapeTypeChip)); static final Finder backButton = find.byKey(const ValueKey(WidgetIdentifier.backButton));