@@ -3,6 +3,10 @@ import 'package:flutter/scheduler.dart';
33import  'package:flutter/services.dart' ;
44import  'package:intl/intl.dart' ;
55import  'package:video_player/video_player.dart' ;
6+ import  'package:http/http.dart'  as  http;
7+ import  'package:path_provider/path_provider.dart' ;
8+ import  'dart:io' ;
9+ import  'dart:async' ;
610
711import  '../api/core.dart' ;
812import  '../api/model/model.dart' ;
@@ -89,6 +93,157 @@ class _CopyLinkButton extends StatelessWidget {
8993  }
9094}
9195
96+ // class _DownloadImageButton extends StatelessWidget { 
97+ //   const _DownloadImageButton({required this.url}); 
98+ 
99+ //   final Uri url; 
100+ 
101+ //   static const platform = MethodChannel('gallery_saver'); 
102+ 
103+ //   @override 
104+ //   Widget build(BuildContext context) { 
105+ //     final zulipLocalizations = ZulipLocalizations.of(context); 
106+ //     return IconButton( 
107+ //       tooltip: zulipLocalizations.lightboxDownloadImageTooltip, 
108+ //       icon: const Icon(Icons.download), 
109+ //       onPressed: () async { 
110+ //         final scaffoldMessenger = ScaffoldMessenger.of(context); 
111+ //         String message = zulipLocalizations.lightboxDownloadImageFailed; 
112+ 
113+ //         try { 
114+ //           // Fetch the image with a timeout 
115+ //           final response = await http.get(url).timeout( 
116+ //             const Duration(seconds: 30), 
117+ //             onTimeout: () { 
118+ //               throw TimeoutException("timed out"); 
119+ //             }, 
120+ //           ); 
121+ //           if (response.statusCode == 200) { 
122+ //             // Get the external storage directory 
123+ //             final directory = await getExternalStorageDirectory(); 
124+ //             if (directory == null) { 
125+ //               message = zulipLocalizations.lightboxDownloadImageError; 
126+ //             } else { 
127+ //               final downloadPath = '${directory.path.split("Android")[0]}Download'; 
128+ 
129+ //               // Create the Downloads folder if it doesn't exist 
130+ //               final downloadFolder = Directory(downloadPath); 
131+ //               if (!await downloadFolder.exists()) { 
132+ //                 await downloadFolder.create(recursive: true); 
133+ //               } 
134+ 
135+ //               final fileName = url.pathSegments.last; 
136+ //               final filePath = '$downloadPath/$fileName'; 
137+ 
138+ //               final file = File(filePath); 
139+ //               await file.writeAsBytes(response.bodyBytes); 
140+ 
141+ //               // Trigger Media Scanner so it reflects in the gallery. 
142+ //               await platform.invokeMethod('scanFile', {'path': filePath}); 
143+ 
144+ //               message = zulipLocalizations.lightboxDownloadImageSuccess; 
145+ //             } 
146+ //           } else { 
147+ //             message = zulipLocalizations.lightboxDownloadImageFailed; 
148+ //           } 
149+ //         } catch (e) { 
150+ //           if (e is TimeoutException || e is SocketException) { 
151+ //             message = zulipLocalizations.lightboxDownloadImageError; 
152+ //           } else { 
153+ //             message = zulipLocalizations.lightboxDownloadImageError; 
154+ //           } 
155+ //         } 
156+ 
157+ //         // Show a SnackBar notification 
158+ 
159+ //         scaffoldMessenger.showSnackBar( 
160+ //             SnackBar(behavior: SnackBarBehavior.floating, content: Text(message)), 
161+ //         ); 
162+ //       } 
163+ //     ); 
164+ //   } 
165+ // } 
166+ 
167+ class  _DownloadImageButton  extends  StatelessWidget  {
168+   const  _DownloadImageButton ({required  this .url});
169+ 
170+   final  Uri  url;
171+ 
172+   static  const  platform =  MethodChannel ('gallery_saver' );
173+ 
174+   @override 
175+   Widget  build (BuildContext  context) {
176+     final  zulipLocalizations =  ZulipLocalizations .of (context);
177+     return  IconButton (
178+       tooltip:  zulipLocalizations.lightboxDownloadImageTooltip,
179+       icon:  const  Icon (Icons .download),
180+       onPressed:  () async  {
181+         final  scaffoldMessenger =  ScaffoldMessenger .of (context);
182+         String  message =  zulipLocalizations.lightboxDownloadImageFailed;
183+ 
184+         try  {
185+           // Fetch the image with a timeout 
186+           final  response =  await  http.get (url).timeout (
187+             const  Duration (seconds:  30 ),
188+             onTimeout:  () {
189+               throw  TimeoutException ("timed out" );
190+             },
191+           );
192+ 
193+           if  (response.statusCode ==  200 ) {
194+             // Get the external storage directory 
195+             final  directory =  await  getExternalStorageDirectory ();
196+             if  (directory ==  null ) {
197+               message =  zulipLocalizations.lightboxDownloadImageError;
198+             } else  {
199+               // Refactored to use MediaStore for Android 10+ (Scoped Storage) 
200+               if  (Platform .isAndroid) {
201+                 final  downloadFolder =  await  getDownloadDirectory ();
202+                 final  fileName =  url.pathSegments.last;
203+                 final  filePath =  '$downloadFolder /$fileName ' ;
204+ 
205+                 final  file =  File (filePath);
206+                 await  file.writeAsBytes (response.bodyBytes);
207+ 
208+                 // Trigger Media Scanner so it reflects in the gallery. 
209+                 await  platform.invokeMethod ('scanFile' , {'path' :  filePath});
210+ 
211+                 message =  zulipLocalizations.lightboxDownloadImageSuccess;
212+               } else  {
213+                 message =  zulipLocalizations.lightboxDownloadImageError;
214+               }
215+             }
216+           } else  {
217+             message =  zulipLocalizations.lightboxDownloadImageFailed;
218+           }
219+         } catch  (e) {
220+           if  (e is  TimeoutException  ||  e is  SocketException ) {
221+             message =  zulipLocalizations.lightboxDownloadImageError;
222+           } else  {
223+             message =  zulipLocalizations.lightboxDownloadImageError;
224+           }
225+         }
226+ 
227+         // Show a SnackBar notification 
228+         scaffoldMessenger.showSnackBar (
229+           SnackBar (behavior:  SnackBarBehavior .floating, content:  Text (message)),
230+         );
231+       }
232+     );
233+   }
234+ 
235+   // Returns the download directory for Android 10+ using scoped storage 
236+   Future <String > getDownloadDirectory () async  {
237+     if  (Platform .isAndroid) {
238+       final  directory =  await  getExternalStorageDirectory ();
239+       final  downloadFolder =  '${directory ?.path .split ("Android" )[0 ]}Download' ;
240+       return  downloadFolder;
241+     }
242+     return  '' ;
243+   }
244+ }
245+ 
246+ 
92247class  _LightboxPageLayout  extends  StatefulWidget  {
93248  const  _LightboxPageLayout ({
94249    required  this .routeEntranceAnimation,
@@ -258,6 +413,7 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
258413      elevation:  elevation,
259414      child:  Row (children:  [
260415        _CopyLinkButton (url:  widget.src),
416+         _DownloadImageButton (url:  widget.src)
261417        // TODO(#43): Share image 
262418        // TODO(#42): Download image 
263419      ]),
0 commit comments