remove_metadata_page.dart 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import 'dart:io';
  2. import 'package:flutter/material.dart';
  3. import 'package:image_picker/image_picker.dart';
  4. import 'package:exif/exif.dart';
  5. import 'image_helper.dart';
  6. import 'package:file_picker/file_picker.dart';
  7. import 'package:path/path.dart' as path;
  8. class RemoveMetadataPage extends StatefulWidget {
  9. const RemoveMetadataPage({super.key});
  10. @override
  11. _RemoveMetadataPageState createState() => _RemoveMetadataPageState();
  12. }
  13. class _RemoveMetadataPageState extends State<RemoveMetadataPage> {
  14. File? _image;
  15. Map<String, IfdTag> _metadata = {};
  16. String? shareImagePath;
  17. bool _isImagePickerActive = false;
  18. // Show a dialog asking if the user wants to replace an existing file
  19. Future<int> _showReplaceDialog(BuildContext context) async {
  20. return (await showDialog<int>(
  21. context: context,
  22. builder: (BuildContext context) {
  23. return AlertDialog(
  24. title: Text('File Already Exists'),
  25. content: Text(
  26. 'A file with this name already exists. Do you want to replace it? If not, the new file will be renamed with "clean_" at the beginning.'),
  27. actions: <Widget>[
  28. TextButton(
  29. onPressed: () {
  30. Navigator.of(context).pop(0);
  31. },
  32. child: Text('Cancel'),
  33. ),
  34. TextButton(
  35. onPressed: () {
  36. Navigator.of(context).pop(1);
  37. },
  38. child: Text('Replace'),
  39. ),
  40. ],
  41. );
  42. },
  43. )) ??
  44. 0;
  45. }
  46. // Select an image from the gallery
  47. Future<void> getImage() async {
  48. if (_isImagePickerActive) {
  49. print('Image picker is already active.');
  50. return;
  51. }
  52. setState(() {
  53. _isImagePickerActive = true;
  54. });
  55. final picker = ImagePicker();
  56. final pickedFile = await picker.pickImage(source: ImageSource.gallery);
  57. setState(() {
  58. _isImagePickerActive = false;
  59. });
  60. if (pickedFile != null) {
  61. _image = File(pickedFile.path);
  62. Map<String, IfdTag>? data = await ImageHelper.extractMetadata(_image!);
  63. setState(() {
  64. _metadata = data ?? {};
  65. });
  66. } else {
  67. ScaffoldMessenger.of(context).showSnackBar(
  68. const SnackBar(content: Text('No image selected.')),
  69. );
  70. }
  71. }
  72. // Remove metadata from the image and save it
  73. void removeMetadata() async {
  74. if (_image == null) return;
  75. String? selectedDirectory = await FilePicker.platform
  76. .getDirectoryPath(dialogTitle: "Select Directory To Save The Image");
  77. if (selectedDirectory == null) {
  78. ScaffoldMessenger.of(context).showSnackBar(
  79. const SnackBar(content: Text('No directory selected.')),
  80. );
  81. return;
  82. }
  83. String originalFileName = path.basename(_image!.path);
  84. String targetPath = path.join(selectedDirectory, originalFileName);
  85. // Check if the file already exists in the selected directory
  86. if (await File(targetPath).exists()) {
  87. int choice = await _showReplaceDialog(context);
  88. if (choice == 0) {
  89. // User chose not to replace the file
  90. originalFileName = 'clean_$originalFileName';
  91. targetPath = path.join(selectedDirectory, originalFileName);
  92. }
  93. }
  94. File? cleanedImage;
  95. try {
  96. cleanedImage = await ImageHelper.saveImageWithoutMetadata(_image!);
  97. await ImageHelper.saveToDirectory(
  98. cleanedImage, selectedDirectory, originalFileName);
  99. ScaffoldMessenger.of(context).showSnackBar(
  100. const SnackBar(content: Text('Image saved to gallery.')),
  101. );
  102. // Delete The _image Cach File
  103. if (Platform.isAndroid || Platform.isIOS) {
  104. await _image!.delete();
  105. }
  106. shareImagePath = targetPath;
  107. setState(() {
  108. _metadata = {};
  109. });
  110. } catch (e) {
  111. ScaffoldMessenger.of(context).showSnackBar(
  112. const SnackBar(content: Text('Failed to save image to gallery.')),
  113. );
  114. } finally {
  115. // Delete the temporary file after saving it to the gallery
  116. await cleanedImage?.delete();
  117. }
  118. }
  119. // Check if remove metadata button should be visible
  120. bool shouldShowRemoveButton() {
  121. return _image != null && _metadata.isNotEmpty;
  122. }
  123. // Check if share button should be visible
  124. bool shouldShowShareButton() {
  125. return _image != null && _metadata.isEmpty && Platform.isAndroid;
  126. }
  127. // Build metadata list
  128. Widget _buildMetadataList() {
  129. if (_metadata.isEmpty) {
  130. return const Text('No metadata available.');
  131. }
  132. return ListView(
  133. shrinkWrap: true,
  134. children: _metadata.entries.map((entry) {
  135. return ListTile(
  136. title: Text(entry.key),
  137. subtitle: Text(entry.value.toString()),
  138. );
  139. }).toList(),
  140. );
  141. }
  142. @override
  143. Widget build(BuildContext context) {
  144. return Scaffold(
  145. appBar: AppBar(
  146. title: const Text('Image Meta Cleaner'),
  147. actions: [
  148. IconButton(
  149. icon: const Icon(Icons.info),
  150. onPressed: () {
  151. Navigator.pushNamed(context, '/about');
  152. },
  153. ),
  154. ],
  155. ),
  156. body: Column(
  157. crossAxisAlignment: CrossAxisAlignment.start,
  158. children: [
  159. Center(
  160. child: _image == null
  161. ? const Text('No image selected.')
  162. : Image.file(
  163. _image!,
  164. height: 300,
  165. width: 300,
  166. fit: BoxFit.cover,
  167. ),
  168. ),
  169. const SizedBox(height: 20),
  170. const Padding(
  171. padding: EdgeInsets.symmetric(horizontal: 20),
  172. child: Text(
  173. 'Image Metadata:',
  174. style: TextStyle(
  175. fontWeight: FontWeight.bold,
  176. fontSize: 18,
  177. ),
  178. ),
  179. ),
  180. const SizedBox(height: 10),
  181. Expanded(
  182. child: Padding(
  183. padding: const EdgeInsets.symmetric(horizontal: 20),
  184. child: _buildMetadataList(),
  185. ),
  186. ),
  187. ],
  188. ),
  189. floatingActionButton: Column(
  190. mainAxisAlignment: MainAxisAlignment.end,
  191. children: [
  192. FloatingActionButton(
  193. onPressed: getImage,
  194. tooltip: 'Pick Image',
  195. child: const Icon(Icons.photo),
  196. ),
  197. const SizedBox(height: 16),
  198. Visibility(
  199. visible: shouldShowRemoveButton(),
  200. child: FloatingActionButton(
  201. onPressed: removeMetadata,
  202. tooltip: 'Remove Metadata',
  203. child: const Icon(Icons.delete),
  204. ),
  205. ),
  206. Visibility(
  207. visible: shouldShowShareButton(),
  208. child: FloatingActionButton(
  209. onPressed: () => ImageHelper.shareImage(shareImagePath!),
  210. tooltip: 'Share Image',
  211. child: const Icon(Icons.share),
  212. ),
  213. ),
  214. ],
  215. ),
  216. );
  217. }
  218. }