Quick "Done" or "Cancel" Triggers Double Close in Cropper View
Opened this issue · 1 comments
When using the flutter_image_cropper package, an issue occurs when there is an open modal in the Flutter app and the native crop view is displayed for image cropping. If the user presses the "Done" or "Cancel" button quickly (in rapid succession), the close action is executed twice. This results in not only closing the crop view but also closing the underlying modal unintentionally.
Steps to Reproduce:
Open a modal in your Flutter app.
Launch the native image cropper view by selecting an image to crop.
Press the "Done" or "Cancel" button quickly, before the crop view fully loads or responds.
Observe that the crop view closes, but the underlying modal also closes unexpectedly.
Expected Behavior:
The crop view should close without affecting the underlying modal. Only the cropper view should be dismissed when "Done" or "Cancel" is pressed, and the first modal should remain open.
Actual Behavior:
Pressing the "Done" or "Cancel" button in the crop view rapidly causes the crop view to close twice, which also closes the parent modal unintentionally.
Version Information:
flutter_image_cropper version: 8.0.2
Platform: iOS
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
enum CropSettings {
planImage(1500, 750),
userImage(500, 500);
final int height, width;
const CropSettings(this.height, this.width);
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
child: Text('press to open the modal'),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () => openImagePicker(),
child: Text('Select an image'),
)
],
),
),
);
});
},
)
],
),
),
);
}
Future<void> openImagePicker() async {
pickImage(ImageSource.gallery).then((pickedImage) {
if (pickedImage != null) {
cropImage(pickedImage.path, CropSettings.userImage).then((croppedImage) {});
}
}).catchError((_) {
if (!context.mounted) return;
});
}
Future<XFile?> cropImage(String path, CropSettings settings) async {
if (!await File(path).exists()) {
print("File does not exist at path: $path");
return null;
}
final CroppedFile? croppedImage = await ImageCropper().cropImage(
sourcePath: path,
aspectRatio: CropAspectRatio(ratioY: settings.height.toDouble(), ratioX: settings.width.toDouble()),
maxHeight: settings.height,
maxWidth: settings.width,
compressQuality: 50,
uiSettings: [
AndroidUiSettings(
toolbarTitle: '',
statusBarColor: Colors.black,
toolbarColor: Colors.black,
toolbarWidgetColor: Colors.white,
activeControlsWidgetColor: Colors.yellow,
),
IOSUiSettings(
aspectRatioLockEnabled: true,
aspectRatioPickerButtonHidden: true,
resetButtonHidden: true,
showCancelConfirmationDialog: true,
),
],
);
return croppedImage != null ? XFile(croppedImage.path) : null;
}
Future<XFile?> pickImage(ImageSource source) async => await ImagePicker().pickImage(source: source, requestFullMetadata: false);
}