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 e50a8be
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 1 deletion.
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
https://developer.android.com/build/manage-manifests#inspect_the_merged_manifest_and_find_conflicts -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:label="Zulip beta"
android:name="${applicationName}"
Expand Down
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
76 changes: 76 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,77 @@ 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 _LightboxPageLayout extends StatefulWidget {
const _LightboxPageLayout({
required this.routeEntranceAnimation,
Expand Down Expand Up @@ -258,6 +333,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
48 changes: 48 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev"
source: hosted
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
url: "https://pub.dev"
source: hosted
version: "12.0.13"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
url: "https://pub.dev"
source: hosted
version: "4.2.3"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies:
wakelock_plus: ^1.2.8
zulip_plugin:
path: ./packages/zulip_plugin
permission_handler: ^11.3.1
# Keep list sorted when adding dependencies; it helps prevent merge conflicts.

dependency_overrides:
Expand Down
Loading

0 comments on commit e50a8be

Please sign in to comment.