diff --git a/assets/images/simulator.jpeg b/assets/images/simulator.jpeg index fb272dd..de95ff7 100644 Binary files a/assets/images/simulator.jpeg and b/assets/images/simulator.jpeg differ diff --git a/assets/sounds/camera-shutter-click.mp3 b/assets/sounds/camera-shutter-click.mp3 new file mode 100644 index 0000000..b376d7c Binary files /dev/null and b/assets/sounds/camera-shutter-click.mp3 differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 293cc2d..fe07d70 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -7,6 +7,11 @@ PODS: - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) + - flutter_image_compress_common (1.0.0): + - Flutter + - Mantle + - SDWebImage + - SDWebImageWebPCoder - fluttertoast (0.0.2): - Flutter - Toast @@ -17,6 +22,18 @@ PODS: - Flutter - image_picker_ios (0.0.1): - Flutter + - libwebp (1.2.4): + - libwebp/demux (= 1.2.4) + - libwebp/mux (= 1.2.4) + - libwebp/webp (= 1.2.4) + - libwebp/demux (1.2.4): + - libwebp/webp + - libwebp/mux (1.2.4): + - libwebp/demux + - libwebp/webp (1.2.4) + - Mantle (2.2.0): + - Mantle/extobjc (= 2.2.0) + - Mantle/extobjc (2.2.0) - ObjectBox (1.8.1) - objectbox_flutter_libs (0.0.1): - Flutter @@ -26,9 +43,13 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.0.4): - - Flutter - ReachabilitySwift (5.0.0) + - SDWebImage (5.16.0): + - SDWebImage/Core (= 5.16.0) + - SDWebImage/Core (5.16.0) + - SDWebImageWebPCoder (0.11.0): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.15) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -46,13 +67,13 @@ DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) + - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) - - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - wakelock (from `.symlinks/plugins/wakelock/ios`) @@ -61,8 +82,12 @@ DEPENDENCIES: SPEC REPOS: trunk: - FMDB + - libwebp + - Mantle - ObjectBox - ReachabilitySwift + - SDWebImage + - SDWebImageWebPCoder - Toast EXTERNAL SOURCES: @@ -74,6 +99,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter + flutter_image_compress_common: + :path: ".symlinks/plugins/flutter_image_compress_common/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" image_gallery_saver: @@ -86,8 +113,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/ios" - permission_handler_apple: - :path: ".symlinks/plugins/permission_handler_apple/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/ios" sqflite: @@ -102,16 +127,20 @@ SPEC CHECKSUMS: connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a image_gallery_saver: 6eb11e5a866e9ac2c8a98c74ef99a04fc62878b2 image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 + libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef + Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d ObjectBox: a7900d5335218cd437cbc080b7ccc38a5211f7b4 objectbox_flutter_libs: 61d74196d924fbc773da5f5757d1e9fab7b3cc78 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6 + SDWebImageWebPCoder: 295a6573c512f54ad2dd58098e64e17dcf008499 shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 diff --git a/lib/core/components/custom_camera_preview.dart b/lib/core/components/custom_camera_preview.dart index e7e85fb..67d8fdf 100644 --- a/lib/core/components/custom_camera_preview.dart +++ b/lib/core/components/custom_camera_preview.dart @@ -2,6 +2,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; +import 'package:mobdr/service/shared_prefs.dart'; class CustomCameraPreview extends StatefulWidget { final List photoFiles; @@ -16,6 +19,9 @@ class CustomCameraPreview extends StatefulWidget { } class _CustomCameraPreviewState extends State { + final AudioPlayer _audioPlayer = AudioPlayer(); + bool isTakingPhoto = false; + @override Widget build(BuildContext context) { return Stack( @@ -23,80 +29,88 @@ class _CustomCameraPreviewState extends State { children: [ Positioned.fill( child: AspectRatio( - aspectRatio: widget.cameraController.value.aspectRatio, - child: CameraPreview(widget.cameraController)), + aspectRatio: widget.cameraController.value.aspectRatio, + child: CameraPreview(widget.cameraController), + ), ), Positioned( - bottom: 5, - child: FloatingActionButton( - heroTag: "camera", - backgroundColor: Colors.white, - onPressed: () async { - //you can give limit that's user can take how many photo - if (widget.photoFiles.length != 10) { - //take a photo - var videoFile = await widget.cameraController.takePicture(); - File file = File(videoFile.path); - //add photo into files list - widget.photoFiles.add(file); + bottom: 5, + child: FloatingActionButton( + heroTag: "camera", + backgroundColor: Colors.white, + onPressed: () async { + if (widget.photoFiles.length != 10 && !isTakingPhoto) { + setState(() { + isTakingPhoto = true; + }); + + try { + var dirCacheXFile = + await widget.cameraController.takePicture(); + + /// move jpg file to photo directory + final dirPhotoFile = + await moveFromCacheToPhotosDir(File(dirCacheXFile.path)); + + widget.photoFiles.add(dirPhotoFile); + + if (SharedPrefs().photoSound) { + await _audioPlayer.setVolume((0.5)); + await _audioPlayer.play( + AssetSource('sounds/camera-shutter-click.mp3'), + ); + } + } finally { + setState(() { + isTakingPhoto = false; + }); } - }, - child: const Icon( - Icons.camera_alt, - color: Colors.black, - ), - )), + } + }, + child: const Icon( + Icons.camera_alt, + color: Colors.black, + ), + ), + ), _confirmButton(), _rejectButton(), Positioned( - bottom: 80, - child: SizedBox( - height: 80, - width: MediaQuery.of(context).size.width, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - return Container( + bottom: 80, + child: SizedBox( + height: 80, + width: MediaQuery.of(context).size.width, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + return GestureDetector( + onVerticalDragEnd: (details) { + if (details.primaryVelocity! < 0) { + setState(() { + widget.photoFiles.removeAt(index); + }); + } + }, + child: Container( margin: const EdgeInsets.symmetric(horizontal: 2.0), decoration: BoxDecoration( - border: Border.all(width: 2.0, color: Colors.white)), + border: Border.all(width: 2.0, color: Colors.white), + ), child: index >= widget.photoFiles.length ? Container(color: Colors.white, width: 100) - : Stack( - children: [ - Image.file( - widget.photoFiles[index], - fit: BoxFit.cover, - height: 100, - width: 100, - ), - Positioned( - top: 2.5, - right: 2.5, - child: Container( - height: 20, - width: 20, - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle), - child: GestureDetector( - onTap: () { - setState(() { - widget.photoFiles.removeAt(index); - }); - }, - child: const Icon(Icons.close, - size: 12, color: Colors.black), - ), - ), - ), - ], + : Image.file( + widget.photoFiles[index], + fit: BoxFit.cover, + height: 100, + width: 100, ), - ); - }, - itemCount: 10, - ), - )), + ), + ); + }, + itemCount: widget.photoFiles.length, + ), + ), + ), ], ); } @@ -108,9 +122,11 @@ class _CustomCameraPreviewState extends State { child: FloatingActionButton( heroTag: "close", backgroundColor: Colors.black, - onPressed: () { - Navigator.pop(context, false); - }, + onPressed: !isTakingPhoto + ? () { + Navigator.pop(context, false); + } + : null, child: const Icon( Icons.close, color: Colors.white, @@ -121,18 +137,62 @@ class _CustomCameraPreviewState extends State { Positioned _confirmButton() { return Positioned( - bottom: 5, - right: 5, - child: FloatingActionButton( - heroTag: "confirm", - backgroundColor: Colors.black, - onPressed: () { - Navigator.pop(context, true); - }, - child: const Icon( - Icons.check, - color: Colors.white, - ), - )); + bottom: 5, + right: 5, + child: FloatingActionButton( + heroTag: "confirm", + backgroundColor: Colors.black, + onPressed: !isTakingPhoto + ? () { + Navigator.pop(context, true); + } + : null, + child: const Icon( + Icons.check, + color: Colors.white, + ), + ), + ); + } + + // Moves a temporary file to the photos directory in the app's document directory. + /// + /// Returns a `File` object for the new file in the photos directory. + /// + /// Parameters: + /// * `tempFile`: The temporary `File` object to move to the photos directory. + /// + /// Throws a `FileSystemException` if there is an error renaming the file. + Future moveFromCacheToPhotosDir(File tempFile) async { + // Set the new file path with the original file name + final String newPath = + '${SharedPrefs().photosDir}/${tempFile.path.split('/').last}'; + + // Create a new File object with the new path + final newFile = File(newPath); + + final bool photoResizing = SharedPrefs().photoResizing; + final int? minWidth = photoResizing ? 1024 : null; + final int? minHeight = photoResizing ? 768 : null; + + final fixedImageBytes = await FlutterImageCompress.compressWithFile( + tempFile.path, + minWidth: minWidth!, + minHeight: minHeight!, + rotate: 0, + quality: 100, + keepExif: false, + autoCorrectionAngle: true, + format: CompressFormat.jpeg, + ); + + // Write the compressed image bytes to the new file + await newFile.writeAsBytes(fixedImageBytes!); + + // deleting the temporary file + await tempFile.delete(); + + // Create and return a new File object for the new file + return File(newPath); } } diff --git a/lib/service/shared_prefs.dart b/lib/service/shared_prefs.dart index 88c174d..0f563c5 100644 --- a/lib/service/shared_prefs.dart +++ b/lib/service/shared_prefs.dart @@ -156,7 +156,7 @@ class SharedPrefs { /// get/set photo quality String get photoQuality => - _sharedPrefs.getString('key_photo_quality') ?? "Defaut"; + _sharedPrefs.getString('key_photo_quality') ?? "veryHigh"; set photoQuality(String value) { _sharedPrefs.setString('key_photo_quality', value); diff --git a/lib/ui/account/settings.dart b/lib/ui/account/settings.dart index d0b104d..5ecdf9e 100644 --- a/lib/ui/account/settings.dart +++ b/lib/ui/account/settings.dart @@ -208,7 +208,13 @@ class _SettingsPageState extends State { } Widget _showQualityPopup() { - List qualityOptions = ['Defaut', 'High', 'Medium', 'Low']; + List qualityOptions = [ + 'medium', + 'high', + 'veryHigh', + 'ultraHigh', + 'max' + ]; return StatefulBuilder( builder: (BuildContext context, StateSetter mystate) { @@ -239,27 +245,18 @@ class _SettingsPageState extends State { itemCount: qualityOptions.length, itemBuilder: (BuildContext context, int index) { String qualityOption = qualityOptions[index]; - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { + return RadioListTile( + dense: true, // Ajouter cette ligne + title: Text(qualityOption), + value: qualityOption, + groupValue: _photoQuality, + onChanged: (String? value) { setState(() { - _photoQuality = qualityOption; - SharedPrefs().photoQuality = qualityOption; + _photoQuality = value!; + SharedPrefs().photoQuality = value; }); Navigator.pop(context); }, - child: Container( - margin: EdgeInsets.only(top: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - qualityOption, - style: GlobalStyle.courierType, - ), - ], - ), - ), ); }, ), @@ -302,12 +299,17 @@ class _SettingsPageState extends State { itemCount: languageOptions.length, itemBuilder: (BuildContext context, int index) { String languageOption = languageOptions[index]; - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { + return RadioListTile( + title: Text( + languageOption, + style: GlobalStyle.courierType, + ), + value: languageOption, + groupValue: _language, + onChanged: (String? value) { setState(() { - _language = languageOption; - switch (languageOption) { + _language = value!; + switch (value) { case 'English': SharedPrefs().langage = 'en'; break; @@ -315,25 +317,12 @@ class _SettingsPageState extends State { SharedPrefs().langage = 'fr'; break; default: - // TODO a vérifier _language = 'French'; break; } }); Navigator.pop(context); }, - child: Container( - margin: EdgeInsets.only(top: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - languageOption, - style: GlobalStyle.courierType, - ), - ], - ), - ), ); }, ), diff --git a/lib/ui/home.dart b/lib/ui/home.dart index b4c2252..78ec950 100644 --- a/lib/ui/home.dart +++ b/lib/ui/home.dart @@ -11,7 +11,6 @@ import 'package:mobdr/config/constant.dart'; import 'package:mobdr/events.dart'; import 'package:mobdr/service/plausible.dart'; import 'package:mobdr/service/logger_util.dart'; -import 'package:mobdr/service/plausible.dart'; class HomePage extends StatefulWidget { @override diff --git a/lib/ui/reusable/reusable_widget.dart b/lib/ui/reusable/reusable_widget.dart index 59c48b1..8c14f73 100644 --- a/lib/ui/reusable/reusable_widget.dart +++ b/lib/ui/reusable/reusable_widget.dart @@ -3,8 +3,6 @@ import 'package:flutter/material.dart'; import 'package:mobdr/config/constant.dart'; -//TODO Rechercher toutes les utilisations - class ReusableWidget { PreferredSizeWidget bottomAppBar() { return PreferredSize( diff --git a/lib/ui/visit/photo_camera.dart b/lib/ui/visit/photo_camera.dart index 1df837e..ee4dc0c 100644 --- a/lib/ui/visit/photo_camera.dart +++ b/lib/ui/visit/photo_camera.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; import 'package:flutter/services.dart'; import 'package:mobdr/core/components/custom_camera_preview.dart'; +import 'package:mobdr/service/shared_prefs.dart'; class CameraPage extends StatefulWidget { const CameraPage({Key? key, required this.photoFiles}) : super(key: key); @@ -53,10 +54,35 @@ class _CameraPageState extends State { } Future _initCameraController(CameraDescription cameraDescription) async { + ResolutionPreset getResolutionPresetFromPhotoQuality(String photoQuality) { + switch (photoQuality) { + case 'low': + return ResolutionPreset.low; + case 'medium': + return ResolutionPreset.medium; + case 'high': + return ResolutionPreset.high; + case 'veryHigh': + return ResolutionPreset.veryHigh; + case 'ultraHigh': + return ResolutionPreset.ultraHigh; + case 'max': + return ResolutionPreset.max; + default: + return ResolutionPreset + .veryHigh; // Default value if quality is not recognised + } + } + + ResolutionPreset resolutionPreset = + getResolutionPresetFromPhotoQuality(SharedPrefs().photoQuality); + if (controller != null) { await controller!.dispose(); } - controller = CameraController(cameraDescription, ResolutionPreset.high); + + controller = CameraController(cameraDescription, resolutionPreset, + enableAudio: false); controller!.addListener(() { if (mounted) { setState(() {}); diff --git a/lib/ui/visit/visit_photo_typology_detail.dart b/lib/ui/visit/visit_photo_typology_detail.dart index d4bd320..e536251 100644 --- a/lib/ui/visit/visit_photo_typology_detail.dart +++ b/lib/ui/visit/visit_photo_typology_detail.dart @@ -3,7 +3,9 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image/image.dart' as img; import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:mobdr/config/constant.dart'; import 'package:mobdr/config/global_style.dart'; @@ -45,6 +47,10 @@ class _VisitPhotoTypologyDetailPageState bool _isLoading = true; String _errorMessage = ''; + late String imageSize; + late String imageResolution; + late String imageOrientation; + // Typology list late List _photoTypologiesList = []; int _photoTypologyIndex = 0; @@ -62,6 +68,8 @@ class _VisitPhotoTypologyDetailPageState void initState() { super.initState(); + getImageDetails(); + // track & log page access final page = 'visit_photo_typology_detail'; LoggerUtil.dblog('LOG', 'MOBDR', 'Page : ${page}', 0); @@ -74,6 +82,36 @@ class _VisitPhotoTypologyDetailPageState }); } + void getImageDetails() { + File imageFile = File(widget.pp_image); + + if (imageFile.existsSync()) { + img.Image? image = img.decodeImage(imageFile.readAsBytesSync()); + + int imageSizeInBytes = imageFile.lengthSync(); + double imageSizeInMb = imageSizeInBytes / (1024 * 1024); + + int imageWidth = image!.width; + int imageHeight = image.height; + + imageSize = '${imageSizeInMb.toStringAsFixed(2)} MB'; + imageResolution = '$imageWidth x $imageHeight'; + + if (imageWidth > imageHeight) { + imageOrientation = 'Landscape'; + } else if (imageHeight > imageWidth) { + imageOrientation = 'Portrait'; + } else { + imageOrientation = 'Square'; + } + } else { + // The image file does not exist + imageSize = ''; + imageResolution = ''; + imageOrientation = ''; + } + } + @override void dispose() { super.dispose(); @@ -136,10 +174,11 @@ class _VisitPhotoTypologyDetailPageState child: ListView( children: [ Image.file(File(widget.pp_image), fit: BoxFit.cover), - _buildPhotoTypology(), - _buildPhotoVisibility(), + _buildPhotoDetail(), _buildPhotoTag(context), + _buildPhotoVisibility(), _BuildPhotoCompetitor(), + _buildPhotoTypology(), SizedBox(height: 16) ], ), @@ -209,6 +248,45 @@ class _VisitPhotoTypologyDetailPageState )); } + Widget _buildPhotoDetail() { + return Container( + child: Row( + children: [ + Padding( + padding: EdgeInsets.only(left: 5), // Décalage de 10 pixels à gauche + child: Text( + imageSize, + style: TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 16), + Flexible( + child: Text( + imageResolution, + style: TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 16), + Flexible( + child: Text( + '($imageOrientation)', + style: TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + } + Widget _buildPhotoTypology() { return Container( margin: EdgeInsets.only(top: 12), @@ -519,9 +597,17 @@ class _VisitPhotoTypologyDetailPageState } Future saveImageToGallery(String imagePath) async { - final result = await ImageGallerySaver.saveFile(imagePath); - bool isSuccess = result["isSuccess"]; - return isSuccess; + // Request permission to access external storage + PermissionStatus status = await Permission.storage.request(); + + if (status.isGranted) { + final result = await ImageGallerySaver.saveFile(imagePath); + bool isSuccess = result["isSuccess"]; + return isSuccess; + } else { + // The user has refused permission + return false; + } } /// Initializes data when the page loads. diff --git a/lib/ui/visit/visit_photo_typology_list.dart b/lib/ui/visit/visit_photo_typology_list.dart index ecfd140..ad4cde0 100644 --- a/lib/ui/visit/visit_photo_typology_list.dart +++ b/lib/ui/visit/visit_photo_typology_list.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:image/image.dart' as img; +import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart' as path; @@ -413,10 +413,7 @@ class _VisitPhotoTypologyListPageState if (_visitPhotoFiles.length > 0) { final List _listPhotos = []; - for (var myTmpPhoto in _visitPhotoFiles) { - /// move jpg file to photo directory - final myPhoto = await moveFileFromTempToPhotosDir(myTmpPhoto); - + for (var myPhoto in _visitPhotoFiles) { /// to insert into database _listPhotos.add(VisitPhoto( id_visite: widget.pp_visitModel.id_visite, @@ -543,26 +540,6 @@ class _VisitPhotoTypologyListPageState ); } - // Moves a temporary file to the photos directory in the app's document directory. - /// - /// Returns a `File` object for the new file in the photos directory. - /// - /// Parameters: - /// * `tempFile`: The temporary `File` object to move to the photos directory. - /// - /// Throws a `FileSystemException` if there is an error renaming the file. - Future moveFileFromTempToPhotosDir(File tempFile) async { - // Set the new file path with the original file name - final String newPath = - '${SharedPrefs().photosDir}/${tempFile.path.split('/').last}'; - - // Rename the file to move it to the documents directory - await tempFile.rename(newPath); - - // Create and return a new File object for the new file - return File(newPath); - } - /// Rotates the image file clockwise by the specified angle (in degrees) and overwrites the original file with the rotated image. /// /// Parameters: @@ -571,24 +548,20 @@ class _VisitPhotoTypologyListPageState /// /// Throws an exception if there is an error in the rotation process. Future rotateAndReplaceImage(File imageFile, int angle) async { - // Read the image file into a Uint8List - Uint8List bytes = await imageFile.readAsBytes(); + // Rotate the image using flutter_image_compress + Uint8List? rotatedBytes = await FlutterImageCompress.compressWithFile( + imageFile.absolute.path, + rotate: angle, + ); - // Decode the bytes into an Image object using the image package - img.Image? image = img.decodeImage(bytes); + // Check that rotatedBytes is not null before using it + if (rotatedBytes != null) { + // Overwrite the original file with the rotated image + await imageFile.writeAsBytes(rotatedBytes); - // Rotate the image clockwise by 90 degrees - img.Image? rotatedImage = img.copyRotate(image!, angle: angle); - - // Encode the rotated image back into a Uint8List - // TODO : png ?? - Uint8List rotatedBytes = img.encodePng(rotatedImage); - - // Overwrite the original file with the rotated image - await imageFile.writeAsBytes(rotatedBytes); - - // remove the flutter cache image - FileImage(imageFile).evict(); + // remove the flutter cache image + FileImage(imageFile).evict(); + } } /// Copies the 'simulator.jpg' image file from the app's assets to a randomly named file in the app's temporary directory and returns the File object. diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c58ebc9..ce6bafd 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin"); objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 94262e7..b5effe1 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux objectbox_flutter_libs ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 4464393..77494a3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import connectivity_plus import device_info_plus import objectbox_flutter_libs @@ -15,6 +16,7 @@ import sqflite import wakelock_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 6226d07..a98caa4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: archive - sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" url: "https://pub.dev" source: hosted - version: "3.3.6" + version: "3.3.7" args: dependency: transitive description: @@ -41,6 +41,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.10.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: "61583554386721772f9309f509e17712865b38565a903c761f96b1115a979282" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: dbdc9b7f2aa2440314c638aa55aadd45c7705e8340d5eddf2e3fb8da32d4ae2c + url: "https://pub.dev" + source: hosted + version: "3.0.2" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "6aea96df1d12f7ad5a71d88c6d1b22a216211a9564219920124c16768e456e9d" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: "396b62ac62c92dd26c3bc5106583747f57a8b325ebd2b41e5576f840cfc61338" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: f7daaed4659143094151ecf6bacd927d29ab8acffba98c110c59f0b81ae51143 + url: "https://pub.dev" + source: hosted + version: "5.0.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: ec84fd46eed1577148ed4113f5998a36a18da4fce7170c37ce3e21b631393339 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "1d3aaac98a192b8488167711ba1e67d8b96333e8d0572ede4e2912e5bbce69a3" + url: "https://pub.dev" + source: hosted + version: "2.0.2" badges: dependency: "direct main" description: @@ -430,6 +486,38 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.0" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "2babae2c69ee4849c3d3ae0f1f10c14a6b8132390e4583c5d3b1f02e8167a022" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: "88aae2219cd4507992643f19aee7882fe8ff82375ffa8cb4c876e3bfe3e7c3b6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: dae0a3eb1fb7f38cf327cc2005dc287bc891becdd83c519277de4415fd9e065d + url: "https://pub.dev" + source: hosted + version: "1.0.1" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: "677f02509bdf5fd71afb560a22f0444e82c9ee887d223cd006cb44fd145fcb17" + url: "https://pub.dev" + source: hosted + version: "0.1.3" flutter_localizations: dependency: "direct main" description: flutter @@ -513,10 +601,10 @@ packages: dependency: "direct main" description: name: image - sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227" + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf url: "https://pub.dev" source: hosted - version: "4.0.15" + version: "4.0.17" image_gallery_saver: dependency: "direct main" description: @@ -781,6 +869,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "1b6b3e73f0bcbc856548bbdfb1c33084a401c4f143e220629a9055233d76c331" + url: "https://pub.dev" + source: hosted + version: "10.3.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "8f6a95ccbca13766882f95d32684d7c9bfe6c45650c32bedba948ef1c6a4ddf7" + url: "https://pub.dev" + source: hosted + version: "10.2.3" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: "08dcb6ce628ac0b257e429944b4c652c2a4e6af725bdf12b498daa2c6b2b1edb" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: de20a5c3269229c1ae2e5a6b822f6cb59578b23e8255c93fbeebfc82116e6b11 + url: "https://pub.dev" + source: hosted + version: "3.10.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + url: "https://pub.dev" + source: hosted + version: "0.1.2" petitparser: dependency: transitive description: @@ -825,10 +953,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" url: "https://pub.dev" source: hosted - version: "3.7.2" + version: "3.7.3" pool: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index de64f60..f3995ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,16 +66,18 @@ dependencies: # https://pub.dev/packages/camera camera: ^0.10.5+2 + # https://pub.dev/packages/audioplayers + audioplayers: ^4.1.0 + # https://pub.dev/packages/image - # Rotations ... - image: ^4.0.15 + image: ^4.0.17 + + # https://pub.dev/packages/flutter_image_compress + flutter_image_compress: ^2.0.3 # https://pub.dev/packages/super_tag_editor super_tag_editor: ^0.1.1 - - # https://pub.dev/packages/flutter_cache_manager - #flutter_cache_manager: ^3.3.0 - + # https://pub.dev/packages/cached_network_image cached_network_image: 3.2.3 @@ -116,15 +118,18 @@ dependencies: # https://pub.dev/packages/image_gallery_saver image_gallery_saver: ^2.0.1 + # https://pub.dev/packages/permission_handler + permission_handler: 10.3.0 + # hhttps://pub.dev/packages/timelines #timelines: ^0.1.0 - - # https://pub.dev/packages/permission_handler - #permission_handler: 10.2.0 - + # https://pub.dev/packages/flutter_html #flutter_html: 3.0.0-alpha.6 + # https://pub.dev/packages/flutter_cache_manager + #flutter_cache_manager: ^3.3.0 + dev_dependencies: flutter_test: sdk: flutter @@ -209,4 +214,6 @@ flutter: - assets/lang/zh.json - assets/lang/hi.json - assets/lang/th.json - - assets/lang/tk.json \ No newline at end of file + - assets/lang/tk.json + + - assets/sounds/camera-shutter-click.mp3 \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 90fc39c..701cf8e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,18 @@ #include "generated_plugin_registrant.h" +#include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); ObjectboxFlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a2e129f..cc547ce 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,8 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows connectivity_plus objectbox_flutter_libs + permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST