diff --git a/README.md b/README.md index 9a394df..febd2af 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You can provide the image using any Imageprovider. ## Parameters -### @required image +### required image The image that needs to be cropped ### cropController @@ -69,8 +69,8 @@ class MyHomePage extends StatefulWidget { final String title; MyHomePage({ - @required this.title, - Key key, + required this.title, + Key? key, }) : super(key: key); @override @@ -78,7 +78,7 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - CustomImageCropController controller; + late CustomImageCropController controller; @override void initState() { @@ -118,7 +118,9 @@ class _MyHomePageState extends State { icon: const Icon(Icons.crop), onPressed: () async { final image = await controller.onCropImage(); - Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image))); + if (image != null) { + Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image))); + } }, ), ], diff --git a/example/lib/main.dart b/example/lib/main.dart index 3c58730..1150ad8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -26,8 +26,8 @@ class MyHomePage extends StatefulWidget { final String title; MyHomePage({ - @required this.title, - Key key, + required this.title, + Key? key, }) : super(key: key); @override @@ -35,7 +35,7 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - CustomImageCropController controller; + late CustomImageCropController controller; @override void initState() { @@ -75,7 +75,9 @@ class _MyHomePageState extends State { icon: const Icon(Icons.crop), onPressed: () async { final image = await controller.onCropImage(); - Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image))); + if (image != null) { + Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => ResultScreen(image: image))); + } }, ), ], diff --git a/example/lib/resultScreen.dart b/example/lib/resultScreen.dart index c3ab568..afc4cb6 100644 --- a/example/lib/resultScreen.dart +++ b/example/lib/resultScreen.dart @@ -4,8 +4,8 @@ class ResultScreen extends StatelessWidget { final MemoryImage image; const ResultScreen({ - @required this.image, - Key key, + required this.image, + Key? key, }) : super(key: key); @override diff --git a/example/pubspec.lock b/example/pubspec.lock index 3da5672..8a5701a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -80,7 +80,7 @@ packages: name: gesture_x_detector url: "https://pub.dartlang.org" source: hosted - version: "0.0.5" + version: "1.0.0" matcher: dependency: transitive description: @@ -164,5 +164,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=2.12.0-0.0 <3.0.0" - flutter: ">=1.17.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=2.0.2" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e874bca..6437d25 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: diff --git a/lib/src/controllers/controller.dart b/lib/src/controllers/controller.dart index ee98320..9cc5625 100644 --- a/lib/src/controllers/controller.dart +++ b/lib/src/controllers/controller.dart @@ -4,15 +4,9 @@ import 'package:flutter/material.dart'; class CustomImageCropController { final listeners = []; - Future onCropImage() => listeners.map((e) => e.onCropImage()).firstWhere( - (element) => element != null, - orElse: () => null, - ); + Future onCropImage() => listeners.map((e) => e.onCropImage()).first; - CropImageData get cropImageData => listeners.map((e) => e.data).firstWhere( - (element) => element != null, - orElse: () => null, - ); + CropImageData? get cropImageData => listeners.map((e) => e.data).first; void addListener(CustomImageCropListener listener) => listeners.add(listener); @@ -36,5 +30,5 @@ mixin CustomImageCropListener { void setData(CropImageData transition); - Future onCropImage(); + Future onCropImage(); } diff --git a/lib/src/widgets/custom_image_crop_widget.dart b/lib/src/widgets/custom_image_crop_widget.dart index 24646cb..5b20e26 100644 --- a/lib/src/widgets/custom_image_crop_widget.dart +++ b/lib/src/widgets/custom_image_crop_widget.dart @@ -38,14 +38,14 @@ class CustomImageCrop extends StatefulWidget { /// `DottedCropPathPainter.drawPath` and /// `SolidCropPathPainter.drawPath` const CustomImageCrop({ - @required this.image, - @required this.cropController, - Key key, + required this.image, + required this.cropController, this.overlayColor = const Color.fromRGBO(0, 0, 0, 0.5), this.backgroundColor = Colors.white, this.shape = CustomCropShape.Circle, this.cropPercentage = 0.8, this.drawPath = DottedCropPathPainter.drawPath, + Key? key, }) : super(key: key); @override @@ -53,12 +53,12 @@ class CustomImageCrop extends StatefulWidget { } class _CustomImageCropState extends State with CustomImageCropListener { - CropImageData dataTransitionStart; - Path path; - double width, height; - ui.Image imageAsUIImage; - ImageStream _imageStream; - ImageStreamListener _imageListener; + CropImageData? dataTransitionStart; + late Path path; + late double width, height; + ui.Image? imageAsUIImage; + ImageStream? _imageStream; + ImageStreamListener? _imageListener; @override void initState() { @@ -75,10 +75,12 @@ class _CustomImageCropState extends State with CustomImageCropL void _getImage() { final oldImageStream = _imageStream; _imageStream = widget.image.resolve(createLocalImageConfiguration(context)); - if (_imageStream.key != oldImageStream?.key) { - oldImageStream?.removeListener(_imageListener); + if (_imageStream?.key != oldImageStream?.key) { + if (_imageListener != null) { + oldImageStream?.removeListener(_imageListener!); + } _imageListener = ImageStreamListener(_updateImage); - _imageStream.addListener(_imageListener); + _imageStream?.addListener(_imageListener!); } } @@ -90,14 +92,17 @@ class _CustomImageCropState extends State with CustomImageCropL @override void dispose() { - _imageStream?.removeListener(_imageListener); + if (_imageListener != null) { + _imageStream?.removeListener(_imageListener!); + } widget.cropController.removeListener(this); super.dispose(); } @override Widget build(BuildContext context) { - if (imageAsUIImage == null) { + final image = imageAsUIImage; + if (image == null) { return const Center(child: CircularProgressIndicator()); } return LayoutBuilder( @@ -105,7 +110,7 @@ class _CustomImageCropState extends State with CustomImageCropL width = constraints.maxWidth; height = constraints.maxHeight; final cropWidth = min(width, height) * widget.cropPercentage; - final defaultScale = min(imageAsUIImage.width, imageAsUIImage.height) / cropWidth; + final defaultScale = min(image.width, image.height) / cropWidth; final scale = data.scale * defaultScale; path = _getPath(cropWidth, width, height); return XGestureDetector( @@ -125,7 +130,7 @@ class _CustomImageCropState extends State with CustomImageCropL child: Transform( transform: Matrix4.diagonal3(vector_math.Vector3(scale, scale, 0)) ..rotateZ(data.angle) - ..translate(-imageAsUIImage.width / 2, -imageAsUIImage.height / 2), + ..translate(-image.width / 2, -image.height / 2), child: Image( image: widget.image, ), @@ -139,9 +144,7 @@ class _CustomImageCropState extends State with CustomImageCropL ), ), ), - if (widget.drawPath != null) ...{ - widget.drawPath(path), - }, + widget.drawPath(path), ], ), ), @@ -156,7 +159,7 @@ class _CustomImageCropState extends State with CustomImageCropL void onScaleUpdate(ScaleEvent event) { if (dataTransitionStart != null) { - addTransition(dataTransitionStart - CropImageData(scale: event.scale, angle: event.rotationAngle)); + addTransition(dataTransitionStart! - CropImageData(scale: event.scale, angle: event.rotationAngle)); } dataTransitionStart = CropImageData(scale: event.scale, angle: event.rotationAngle); } @@ -192,14 +195,14 @@ class _CustomImageCropState extends State with CustomImageCropL } @override - Future onCropImage() async { + Future onCropImage() async { if (imageAsUIImage == null) { return null; } final cropWidth = min(width, height) * widget.cropPercentage; final pictureRecorder = ui.PictureRecorder(); final canvas = Canvas(pictureRecorder); - final defaultScale = min(imageAsUIImage.width, imageAsUIImage.height) / cropWidth; + final defaultScale = min(imageAsUIImage!.width, imageAsUIImage!.height) / cropWidth; final scale = data.scale * defaultScale; final clipPath = Path.from(_getPath(cropWidth, cropWidth, cropWidth)); final matrix4Image = Matrix4.diagonal3(vector_math.Vector3(1, 1, 0)) @@ -214,7 +217,7 @@ class _CustomImageCropState extends State with CustomImageCropL canvas.save(); canvas.clipPath(clipPath); canvas.transform(matrix4Image.storage); - canvas.drawImage(imageAsUIImage, Offset(-imageAsUIImage.width / 2, -imageAsUIImage.height / 2), imagePaint); + canvas.drawImage(imageAsUIImage!, Offset(-imageAsUIImage!.width / 2, -imageAsUIImage!.height / 2), imagePaint); canvas.restore(); // Optionally remove magenta from image by evaluating every pixel @@ -228,7 +231,7 @@ class _CustomImageCropState extends State with CustomImageCropL // A workaround would be to save the image and load it inside of the isolate final bytes = await image.toByteData(format: ui.ImageByteFormat.png); - return MemoryImage(bytes.buffer.asUint8List()); + return bytes == null ? null : MemoryImage(bytes.buffer.asUint8List()); } @override diff --git a/pubspec.lock b/pubspec.lock index 473f60f..da89220 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,49 +7,49 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -66,28 +66,28 @@ packages: name: gesture_x_detector url: "https://pub.dartlang.org" source: hosted - version: "0.0.5" + version: "1.0.0" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -99,56 +99,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" sdks: - dart: ">=2.10.0-110 <2.11.0" - flutter: ">=1.17.0 <2.0.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=2.0.2" diff --git a/pubspec.yaml b/pubspec.yaml index c35d564..2c0ae81 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,13 +4,13 @@ version: 0.0.1 homepage: https://github.com/icapps/flutter-custom-image-crop environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.17.0" + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.0.2" dependencies: flutter: sdk: flutter - gesture_x_detector: ^0.0.5 + gesture_x_detector: ^1.0.0 dev_dependencies: flutter_test: