@@ -3,6 +3,10 @@ import 'package:flutter/scheduler.dart';
3
3
import 'package:flutter/services.dart' ;
4
4
import 'package:intl/intl.dart' ;
5
5
import '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' ;
6
10
7
11
import '../api/core.dart' ;
8
12
import '../api/model/model.dart' ;
@@ -89,6 +93,157 @@ class _CopyLinkButton extends StatelessWidget {
89
93
}
90
94
}
91
95
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
+
92
247
class _LightboxPageLayout extends StatefulWidget {
93
248
const _LightboxPageLayout ({
94
249
required this .routeEntranceAnimation,
@@ -258,6 +413,7 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
258
413
elevation: elevation,
259
414
child: Row (children: [
260
415
_CopyLinkButton (url: widget.src),
416
+ _DownloadImageButton (url: widget.src)
261
417
// TODO(#43): Share image
262
418
// TODO(#42): Download image
263
419
]),
0 commit comments