Skip to content

Commit

Permalink
feat: add download button to lightbox to bottom app bar
Browse files Browse the repository at this point in the history
  • Loading branch information
chimnayajith committed Dec 12, 2024
1 parent 28b3536 commit fb8bab9
Show file tree
Hide file tree
Showing 15 changed files with 372 additions and 1 deletion.
35 changes: 34 additions & 1 deletion android/app/src/main/kotlin/com/zulip/flutter/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
package com.zulip.flutter

import android.media.MediaScannerConnection
import android.net.Uri
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
class MainActivity : FlutterActivity() {
private val CHANNEL = "gallery_saver"

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "scanFile") {
val filePath = call.argument<String>("path")
if (filePath != null) {
scanFile(filePath)
result.success("MediaScanner invoked for $filePath")
} else {
result.error("INVALID_ARGUMENT", "File path is null", null)
}
} else {
result.notImplemented()
}
}
}

private fun scanFile(filePath: String) {
MediaScannerConnection.scanFile(
applicationContext,
arrayOf(filePath),
null
) { _, _ ->
// Intentionally left empty
}
}
}
16 changes: 16 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,22 @@
"@lightboxCopyLinkTooltip": {
"description": "Tooltip in lightbox for the copy link action."
},
"lightboxDownloadImageTooltip": "Download image",
"@lightboxDownloadImageTooltip": {
"description": "Tooltip in lightbox for the download image action."
},
"lightboxDownloadImageSuccess": "Image downloaded successfully!",
"@lightboxDownloadImageSuccess": {
"description": "Message shown when the image downloads successfully."
},
"lightboxDownloadImageFailed": "Failed to download the image.",
"@lightboxDownloadImageFailed": {
"description": "Message shown when the image download fails."
},
"lightboxDownloadImageError": "An error occurred while downloading the image.",
"@lightboxDownloadImageError": {
"description": "Message shown when an unexpected error occurs during image download."
},
"loginPageTitle": "Log in",
"@loginPageTitle": {
"description": "Title for login page."
Expand Down
24 changes: 24 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,30 @@ abstract class ZulipLocalizations {
/// **'Copy link'**
String get lightboxCopyLinkTooltip;

/// Tooltip in lightbox for the download image action.
///
/// In en, this message translates to:
/// **'Download image'**
String get lightboxDownloadImageTooltip;

/// Message shown when the image downloads successfully.
///
/// In en, this message translates to:
/// **'Image downloaded successfully!'**
String get lightboxDownloadImageSuccess;

/// Message shown when the image download fails.
///
/// In en, this message translates to:
/// **'Failed to download the image.'**
String get lightboxDownloadImageFailed;

/// Message shown when an unexpected error occurs during image download.
///
/// In en, this message translates to:
/// **'An error occurred while downloading the image.'**
String get lightboxDownloadImageError;

/// Title for login page.
///
/// In en, this message translates to:
Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxDownloadImageTooltip => 'Download image';

@override
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';

@override
String get lightboxDownloadImageFailed => 'Failed to download the image.';

@override
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';

@override
String get loginPageTitle => 'Log in';

Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxDownloadImageTooltip => 'Download image';

@override
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';

@override
String get lightboxDownloadImageFailed => 'Failed to download the image.';

@override
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';

@override
String get loginPageTitle => 'Log in';

Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations_fr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@ class ZulipLocalizationsFr extends ZulipLocalizations {
@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxDownloadImageTooltip => 'Download image';

@override
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';

@override
String get lightboxDownloadImageFailed => 'Failed to download the image.';

@override
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';

@override
String get loginPageTitle => 'Log in';

Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations_ja.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxDownloadImageTooltip => 'Download image';

@override
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';

@override
String get lightboxDownloadImageFailed => 'Failed to download the image.';

@override
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';

@override
String get loginPageTitle => 'Log in';

Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations_pl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
@override
String get lightboxCopyLinkTooltip => 'Skopiuj odnośnik';

@override
String get lightboxDownloadImageTooltip => 'Download image';

@override
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';

@override
String get lightboxDownloadImageFailed => 'Failed to download the image.';

@override
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';

@override
String get loginPageTitle => 'Zaloguj';

Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations_ru.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,18 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
@override
String get lightboxCopyLinkTooltip => 'Copy link';

@override
String get lightboxDownloadImageTooltip => 'Download image';

@override
String get lightboxDownloadImageSuccess => 'Image downloaded successfully!';

@override
String get lightboxDownloadImageFailed => 'Failed to download the image.';

@override
String get lightboxDownloadImageError => 'An error occurred while downloading the image.';

@override
String get loginPageTitle => 'Log in';

Expand Down
156 changes: 156 additions & 0 deletions lib/widgets/lightbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:video_player/video_player.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:async';

import '../api/core.dart';
import '../api/model/model.dart';
Expand Down Expand Up @@ -89,6 +93,157 @@ class _CopyLinkButton extends StatelessWidget {
}
}

// class _DownloadImageButton extends StatelessWidget {
// const _DownloadImageButton({required this.url});

// final Uri url;

// static const platform = MethodChannel('gallery_saver');

// @override
// Widget build(BuildContext context) {
// final zulipLocalizations = ZulipLocalizations.of(context);
// return IconButton(
// tooltip: zulipLocalizations.lightboxDownloadImageTooltip,
// icon: const Icon(Icons.download),
// onPressed: () async {
// final scaffoldMessenger = ScaffoldMessenger.of(context);
// String message = zulipLocalizations.lightboxDownloadImageFailed;

// try {
// // Fetch the image with a timeout
// final response = await http.get(url).timeout(
// const Duration(seconds: 30),
// onTimeout: () {
// throw TimeoutException("timed out");
// },
// );
// if (response.statusCode == 200) {
// // Get the external storage directory
// final directory = await getExternalStorageDirectory();
// if (directory == null) {
// message = zulipLocalizations.lightboxDownloadImageError;
// } else {
// final downloadPath = '${directory.path.split("Android")[0]}Download';

// // Create the Downloads folder if it doesn't exist
// final downloadFolder = Directory(downloadPath);
// if (!await downloadFolder.exists()) {
// await downloadFolder.create(recursive: true);
// }

// final fileName = url.pathSegments.last;
// final filePath = '$downloadPath/$fileName';

// final file = File(filePath);
// await file.writeAsBytes(response.bodyBytes);

// // Trigger Media Scanner so it reflects in the gallery.
// await platform.invokeMethod('scanFile', {'path': filePath});

// message = zulipLocalizations.lightboxDownloadImageSuccess;
// }
// } else {
// message = zulipLocalizations.lightboxDownloadImageFailed;
// }
// } catch (e) {
// if (e is TimeoutException || e is SocketException) {
// message = zulipLocalizations.lightboxDownloadImageError;
// } else {
// message = zulipLocalizations.lightboxDownloadImageError;
// }
// }

// // Show a SnackBar notification

// scaffoldMessenger.showSnackBar(
// SnackBar(behavior: SnackBarBehavior.floating, content: Text(message)),
// );
// }
// );
// }
// }

class _DownloadImageButton extends StatelessWidget {
const _DownloadImageButton({required this.url});

final Uri url;

static const platform = MethodChannel('gallery_saver');

@override
Widget build(BuildContext context) {
final zulipLocalizations = ZulipLocalizations.of(context);
return IconButton(
tooltip: zulipLocalizations.lightboxDownloadImageTooltip,
icon: const Icon(Icons.download),
onPressed: () async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
String message = zulipLocalizations.lightboxDownloadImageFailed;

try {
// Fetch the image with a timeout
final response = await http.get(url).timeout(
const Duration(seconds: 30),
onTimeout: () {
throw TimeoutException("timed out");
},
);

if (response.statusCode == 200) {
// Get the external storage directory
final directory = await getExternalStorageDirectory();
if (directory == null) {
message = zulipLocalizations.lightboxDownloadImageError;
} else {
// Refactored to use MediaStore for Android 10+ (Scoped Storage)
if (Platform.isAndroid) {
final downloadFolder = await getDownloadDirectory();
final fileName = url.pathSegments.last;
final filePath = '$downloadFolder/$fileName';

final file = File(filePath);
await file.writeAsBytes(response.bodyBytes);

// Trigger Media Scanner so it reflects in the gallery.
await platform.invokeMethod('scanFile', {'path': filePath});

message = zulipLocalizations.lightboxDownloadImageSuccess;
} else {
message = zulipLocalizations.lightboxDownloadImageError;
}
}
} else {
message = zulipLocalizations.lightboxDownloadImageFailed;
}
} catch (e) {
if (e is TimeoutException || e is SocketException) {
message = zulipLocalizations.lightboxDownloadImageError;
} else {
message = zulipLocalizations.lightboxDownloadImageError;
}
}

// Show a SnackBar notification
scaffoldMessenger.showSnackBar(
SnackBar(behavior: SnackBarBehavior.floating, content: Text(message)),
);
}
);
}

// Returns the download directory for Android 10+ using scoped storage
Future<String> getDownloadDirectory() async {
if (Platform.isAndroid) {
final directory = await getExternalStorageDirectory();
final downloadFolder = '${directory?.path.split("Android")[0]}Download';
return downloadFolder;
}
return '';
}
}


class _LightboxPageLayout extends StatefulWidget {
const _LightboxPageLayout({
required this.routeEntranceAnimation,
Expand Down Expand Up @@ -258,6 +413,7 @@ class _ImageLightboxPageState extends State<_ImageLightboxPage> {
elevation: elevation,
child: Row(children: [
_CopyLinkButton(url: widget.src),
_DownloadImageButton(url: widget.src)
// TODO(#43): Share image
// TODO(#42): Download image
]),
Expand Down
Loading

0 comments on commit fb8bab9

Please sign in to comment.