From ea45bf828c36aa833a5261e55fb02eeba06583d1 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Sat, 13 Mar 2021 17:50:59 +0100 Subject: [PATCH] Can crop image --- android/app/src/main/AndroidManifest.xml | 7 ++++ lib/ui/dialogs/pick_file_dialog.dart | 3 ++ lib/ui/routes/image_editor_route.dart | 53 ++++++++++++++++++++++++ lib/utils/files_utils.dart | 15 +++++++ lib/utils/video_utils.dart | 9 ++-- pubspec.lock | 7 ++++ pubspec.yaml | 3 ++ 7 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 lib/ui/routes/image_editor_route.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3599d42..0894668 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ + @@ -69,5 +70,11 @@ + + + diff --git a/lib/ui/dialogs/pick_file_dialog.dart b/lib/ui/dialogs/pick_file_dialog.dart index 08f47d8..9a83154 100644 --- a/lib/ui/dialogs/pick_file_dialog.dart +++ b/lib/ui/dialogs/pick_file_dialog.dart @@ -1,5 +1,6 @@ import 'package:comunic/models/api_request.dart'; import 'package:comunic/ui/dialogs/record_audio_dialog.dart'; +import 'package:comunic/ui/routes/image_editor_route.dart'; import 'package:comunic/utils/files_utils.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; @@ -132,6 +133,8 @@ Future showPickFileDialog({ file = BytesFile(image.path.split("/").last, await image.readAsBytes()); + file = await showImageCropper(context, file); + break; // Pick an video diff --git a/lib/ui/routes/image_editor_route.dart b/lib/ui/routes/image_editor_route.dart new file mode 100644 index 0000000..9d0ffd6 --- /dev/null +++ b/lib/ui/routes/image_editor_route.dart @@ -0,0 +1,53 @@ +import 'dart:io'; + +import 'package:comunic/models/api_request.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/log_utils.dart'; +import 'package:comunic/utils/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:image_cropper/image_cropper.dart'; + +import '../../models/api_request.dart'; +import '../../utils/files_utils.dart'; + +/// Image cropper route +/// +/// @author Pierre Hubert + +/// Attempt to crop image +/// +/// Return original image in case of error / if the user did not crop the image +Future showImageCropper( + BuildContext context, BytesFile source) async { + assert(context != null); + assert(source != null); + + File file; + File cropped; + + try { + file = await generateTemporaryFile(); + await file.writeAsBytes(source.bytes); + + File cropped = await ImageCropper.cropImage( + sourcePath: file.absolute.path, + compressFormat: ImageCompressFormat.png, + androidUiSettings: AndroidUiSettings( + toolbarColor: Colors.black, + toolbarTitle: tr("Crop Photo"), + toolbarWidgetColor: Colors.white, + ), + ); + + if (cropped == null) return null; + + return BytesFile("cropped.png", await cropped.readAsBytes()); + } catch (e, s) { + logError(e, s); + snack(context, tr("Failed to execute image cropper!")); + return source; + } finally { + if (file != null && await file.exists()) file.delete(); + if (cropped != null && await cropped.exists()) cropped.delete(); + } +} diff --git a/lib/utils/files_utils.dart b/lib/utils/files_utils.dart index 61788e8..b9fa7fb 100644 --- a/lib/utils/files_utils.dart +++ b/lib/utils/files_utils.dart @@ -1,6 +1,11 @@ +import 'dart:io'; + import 'package:comunic/utils/intl_utils.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; +import 'package:random_string/random_string.dart'; /// Files utilities /// @@ -46,6 +51,16 @@ Future pickImage(BuildContext context) async { : ImageSource.gallery); } +/// Generate a new temporary file +/// +/// Throws in case of failure +Future generateTemporaryFile() async { + final tempDir = await getTemporaryDirectory(); + if (tempDir == null) + throw Exception("Could not generate temporary directory!"); + return File(path.join(tempDir.path, randomString(15, from: 65, to: 90))); +} + /// Check if a mime type maps to an image or not bool isImage(String mimeType) => mimeType.startsWith("image/"); diff --git a/lib/utils/video_utils.dart b/lib/utils/video_utils.dart index 23e86dd..074caa2 100644 --- a/lib/utils/video_utils.dart +++ b/lib/utils/video_utils.dart @@ -3,11 +3,10 @@ import 'dart:io'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/utils/log_utils.dart'; import 'package:flutter/material.dart'; -import 'package:path/path.dart' as path; -import 'package:path_provider/path_provider.dart'; -import 'package:random_string/random_string.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; +import 'files_utils.dart'; + /// Video utilities /// /// @author Pierre Hubert @@ -20,9 +19,7 @@ Future generateVideoThumbnail({ File file; try { - final tempDir = await getTemporaryDirectory(); - if (tempDir == null) return null; - file = File(path.join(tempDir.path, randomString(15, from: 65, to: 90))); + file = await generateTemporaryFile(); await file.writeAsBytes(videoFile.bytes); diff --git a/pubspec.lock b/pubspec.lock index edd3b57..03909f6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -282,6 +282,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.19" + image_cropper: + dependency: "direct main" + description: + name: image_cropper + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" image_picker: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 38bad91..623ce50 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -123,6 +123,9 @@ dependencies: # Color picker flutter_colorpicker: ^0.3.5 + # Image cropper + image_cropper: ^1.4.0 + dev_dependencies: flutter_test: sdk: flutter