Skip to content

Commit 9723938

Browse files
author
chimnayajith
committed
lightbox: add download button to bottom app bar
1 parent 28b3536 commit 9723938

15 files changed

+372
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
11
package com.zulip.flutter
22

3+
import android.media.MediaScannerConnection
4+
import android.net.Uri
35
import io.flutter.embedding.android.FlutterActivity
6+
import io.flutter.embedding.engine.FlutterEngine
7+
import io.flutter.plugin.common.MethodChannel
48

5-
class MainActivity: FlutterActivity() {
9+
class MainActivity : FlutterActivity() {
10+
private val CHANNEL = "gallery_saver"
11+
12+
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
13+
super.configureFlutterEngine(flutterEngine)
14+
15+
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
16+
if (call.method == "scanFile") {
17+
val filePath = call.argument<String>("path")
18+
if (filePath != null) {
19+
scanFile(filePath)
20+
result.success("MediaScanner invoked for $filePath")
21+
} else {
22+
result.error("INVALID_ARGUMENT", "File path is null", null)
23+
}
24+
} else {
25+
result.notImplemented()
26+
}
27+
}
28+
}
29+
30+
private fun scanFile(filePath: String) {
31+
MediaScannerConnection.scanFile(
32+
applicationContext,
33+
arrayOf(filePath),
34+
null
35+
) { _, _ ->
36+
// Intentionally left empty
37+
}
38+
}
639
}

assets/l10n/app_en.arb

+16
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,22 @@
395395
"@lightboxCopyLinkTooltip": {
396396
"description": "Tooltip in lightbox for the copy link action."
397397
},
398+
"lightboxDownloadImageTooltip": "Download image",
399+
"@lightboxDownloadImageTooltip": {
400+
"description": "Tooltip in lightbox for the download image action."
401+
},
402+
"lightboxDownloadImageSuccess": "Image downloaded successfully!",
403+
"@lightboxDownloadImageSuccess": {
404+
"description": "Message shown when the image downloads successfully."
405+
},
406+
"lightboxDownloadImageFailed": "Failed to download the image.",
407+
"@lightboxDownloadImageFailed": {
408+
"description": "Message shown when the image download fails."
409+
},
410+
"lightboxDownloadImageError": "An error occurred while downloading the image.",
411+
"@lightboxDownloadImageError": {
412+
"description": "Message shown when an unexpected error occurs during image download."
413+
},
398414
"loginPageTitle": "Log in",
399415
"@loginPageTitle": {
400416
"description": "Title for login page."

lib/generated/l10n/zulip_localizations.dart

+24
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,30 @@ abstract class ZulipLocalizations {
631631
/// **'Copy link'**
632632
String get lightboxCopyLinkTooltip;
633633

634+
/// Tooltip in lightbox for the download image action.
635+
///
636+
/// In en, this message translates to:
637+
/// **'Download image'**
638+
String get lightboxDownloadImageTooltip;
639+
640+
/// Message shown when the image downloads successfully.
641+
///
642+
/// In en, this message translates to:
643+
/// **'Image downloaded successfully!'**
644+
String get lightboxDownloadImageSuccess;
645+
646+
/// Message shown when the image download fails.
647+
///
648+
/// In en, this message translates to:
649+
/// **'Failed to download the image.'**
650+
String get lightboxDownloadImageFailed;
651+
652+
/// Message shown when an unexpected error occurs during image download.
653+
///
654+
/// In en, this message translates to:
655+
/// **'An error occurred while downloading the image.'**
656+
String get lightboxDownloadImageError;
657+
634658
/// Title for login page.
635659
///
636660
/// In en, this message translates to:

lib/generated/l10n/zulip_localizations_ar.dart

+12
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,18 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
310310
@override
311311
String get lightboxCopyLinkTooltip => 'Copy link';
312312

313+
@override
314+
String get lightboxDownloadImageTooltip => 'Download image';
315+
316+
@override
317+
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';
318+
319+
@override
320+
String get lightboxDownloadImageFailed => 'Failed to download the image.';
321+
322+
@override
323+
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';
324+
313325
@override
314326
String get loginPageTitle => 'Log in';
315327

lib/generated/l10n/zulip_localizations_en.dart

+12
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,18 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
310310
@override
311311
String get lightboxCopyLinkTooltip => 'Copy link';
312312

313+
@override
314+
String get lightboxDownloadImageTooltip => 'Download image';
315+
316+
@override
317+
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';
318+
319+
@override
320+
String get lightboxDownloadImageFailed => 'Failed to download the image.';
321+
322+
@override
323+
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';
324+
313325
@override
314326
String get loginPageTitle => 'Log in';
315327

lib/generated/l10n/zulip_localizations_fr.dart

+12
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,18 @@ class ZulipLocalizationsFr extends ZulipLocalizations {
310310
@override
311311
String get lightboxCopyLinkTooltip => 'Copy link';
312312

313+
@override
314+
String get lightboxDownloadImageTooltip => 'Download image';
315+
316+
@override
317+
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';
318+
319+
@override
320+
String get lightboxDownloadImageFailed => 'Failed to download the image.';
321+
322+
@override
323+
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';
324+
313325
@override
314326
String get loginPageTitle => 'Log in';
315327

lib/generated/l10n/zulip_localizations_ja.dart

+12
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,18 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
310310
@override
311311
String get lightboxCopyLinkTooltip => 'Copy link';
312312

313+
@override
314+
String get lightboxDownloadImageTooltip => 'Download image';
315+
316+
@override
317+
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';
318+
319+
@override
320+
String get lightboxDownloadImageFailed => 'Failed to download the image.';
321+
322+
@override
323+
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';
324+
313325
@override
314326
String get loginPageTitle => 'Log in';
315327

lib/generated/l10n/zulip_localizations_pl.dart

+12
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,18 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
310310
@override
311311
String get lightboxCopyLinkTooltip => 'Skopiuj odnośnik';
312312

313+
@override
314+
String get lightboxDownloadImageTooltip => 'Download image';
315+
316+
@override
317+
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';
318+
319+
@override
320+
String get lightboxDownloadImageFailed => 'Failed to download the image.';
321+
322+
@override
323+
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';
324+
313325
@override
314326
String get loginPageTitle => 'Zaloguj';
315327

lib/generated/l10n/zulip_localizations_ru.dart

+12
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,18 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
310310
@override
311311
String get lightboxCopyLinkTooltip => 'Copy link';
312312

313+
@override
314+
String get lightboxDownloadImageTooltip => 'Download image';
315+
316+
@override
317+
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';
318+
319+
@override
320+
String get lightboxDownloadImageFailed => 'Failed to download the image.';
321+
322+
@override
323+
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';
324+
313325
@override
314326
String get loginPageTitle => 'Log in';
315327

lib/widgets/lightbox.dart

+156
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import 'package:flutter/scheduler.dart';
33
import 'package:flutter/services.dart';
44
import 'package:intl/intl.dart';
55
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';
610

711
import '../api/core.dart';
812
import '../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+
92247
class _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

Comments
 (0)