Skip to content

Commit ad1c17f

Browse files
author
chimnayajith
committed
lightbox: Add download button to bottom app bar.
Fixes: #42
1 parent 9d3f04a commit ad1c17f

15 files changed

+446
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Autogenerated from Pigeon (v20.0.2), do not edit directly.
2+
// See also: https://pub.dev/packages/pigeon
3+
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
4+
5+
package com.zulip.flutter
6+
7+
import android.util.Log
8+
import io.flutter.plugin.common.BasicMessageChannel
9+
import io.flutter.plugin.common.BinaryMessenger
10+
import io.flutter.plugin.common.MessageCodec
11+
import io.flutter.plugin.common.StandardMessageCodec
12+
import java.io.ByteArrayOutputStream
13+
import java.nio.ByteBuffer
14+
15+
private fun wrapResult(result: Any?): List<Any?> {
16+
return listOf(result)
17+
}
18+
19+
private fun wrapError(exception: Throwable): List<Any?> {
20+
return if (exception is NotificationsError) {
21+
listOf(
22+
exception.code,
23+
exception.message,
24+
exception.details
25+
)
26+
} else {
27+
listOf(
28+
exception.javaClass.simpleName,
29+
exception.toString(),
30+
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
31+
)
32+
}
33+
}
34+
35+
/**
36+
* Error class for passing custom error details to Flutter via a thrown PlatformException.
37+
* @property code The error code.
38+
* @property message The error message.
39+
* @property details The error details. Must be a datatype supported by the api codec.
40+
*/
41+
class NotificationsError (
42+
val code: String,
43+
override val message: String? = null,
44+
val details: Any? = null
45+
) : Throwable()
46+
private object MediaScannerPigeonCodec : StandardMessageCodec() {
47+
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
48+
return super.readValueOfType(type, buffer)
49+
}
50+
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
51+
super.writeValue(stream, value)
52+
}
53+
}
54+
55+
56+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
57+
interface MediaScannerHostApi {
58+
/**
59+
* Scans the file at the given path to make it visible in the device's media library.
60+
* Returns a success message if the scan was initiated successfully.
61+
*/
62+
fun scanFile(filePath: String, callback: (Result<String>) -> Unit)
63+
64+
companion object {
65+
/** The codec used by MediaScannerHostApi. */
66+
val codec: MessageCodec<Any?> by lazy {
67+
MediaScannerPigeonCodec
68+
}
69+
/** Sets up an instance of `MediaScannerHostApi` to handle messages through the `binaryMessenger`. */
70+
@JvmOverloads
71+
fun setUp(binaryMessenger: BinaryMessenger, api: MediaScannerHostApi?, messageChannelSuffix: String = "") {
72+
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
73+
run {
74+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.zulip.MediaScannerHostApi.scanFile$separatedMessageChannelSuffix", codec)
75+
if (api != null) {
76+
channel.setMessageHandler { message, reply ->
77+
val args = message as List<Any?>
78+
val filePathArg = args[0] as String
79+
api.scanFile(filePathArg) { result: Result<String> ->
80+
val error = result.exceptionOrNull()
81+
if (error != null) {
82+
reply.reply(wrapError(error))
83+
} else {
84+
val data = result.getOrNull()
85+
reply.reply(wrapResult(data))
86+
}
87+
}
88+
}
89+
} else {
90+
channel.setMessageHandler(null)
91+
}
92+
}
93+
}
94+
}
95+
}

android/app/src/main/kotlin/com/zulip/flutter/ZulipPlugin.kt

+37-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.content.ContentValues
66
import android.content.Context
77
import android.content.Intent
88
import android.media.AudioAttributes
9+
import android.media.MediaScannerConnection
910
import android.net.Uri
1011
import android.os.Build
1112
import android.os.Bundle
@@ -280,17 +281,48 @@ private class AndroidNotificationHost(val context: Context)
280281
}
281282
}
282283

284+
// Function to trigger scanFile which refreshes the media library so that the file is visible in the gallery or other media apps.
285+
private class MediaScannerHost(private val context: Context) : MediaScannerHostApi {
286+
override fun scanFile(filePath: String, callback: (Result<String>) -> Unit) {
287+
try {
288+
MediaScannerConnection.scanFile(
289+
context,
290+
arrayOf(filePath),
291+
null
292+
) { path, uri ->
293+
if (uri != null) {
294+
callback(Result.success("Successfully scanned file: $path"))
295+
} else {
296+
callback(Result.failure(NotificationsError(
297+
code = "SCAN_FAILED",
298+
message = "Failed to scan file: $path"
299+
)))
300+
}
301+
}
302+
} catch (e: Exception) {
303+
callback(Result.failure(NotificationsError(
304+
code = "SCAN_ERROR",
305+
message = e.message ?: "Unknown error occurred while scanning file"
306+
)))
307+
}
308+
}
309+
}
310+
283311
/** A Flutter plugin for the Zulip app's ad-hoc needs. */
284312
// @Keep is needed because this class is used only
285313
// from ZulipShimPlugin, via reflection.
286314
@Keep
287-
class ZulipPlugin : FlutterPlugin { // TODO ActivityAware too?
315+
class ZulipPlugin : FlutterPlugin {
288316
private var notificationHost: AndroidNotificationHost? = null
317+
private var mediaScannerHost: MediaScannerHost? = null
289318

290319
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
291320
Log.d(TAG, "Attaching to Flutter engine.")
292321
notificationHost = AndroidNotificationHost(binding.applicationContext)
322+
mediaScannerHost = MediaScannerHost(binding.applicationContext)
323+
293324
AndroidNotificationHostApi.setUp(binding.binaryMessenger, notificationHost)
325+
MediaScannerHostApi.setUp(binding.binaryMessenger, mediaScannerHost)
294326
}
295327

296328
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
@@ -299,6 +331,9 @@ class ZulipPlugin : FlutterPlugin { // TODO ActivityAware too?
299331
return
300332
}
301333
AndroidNotificationHostApi.setUp(binding.binaryMessenger, null)
334+
MediaScannerHostApi.setUp(binding.binaryMessenger, null)
335+
302336
notificationHost = null
337+
mediaScannerHost = null
303338
}
304-
}
339+
}

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/host/android_media_scanner.dart

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import 'android_media_scanner.g.dart';
2+
3+
// Wrapper class for MediaScanner functionality
4+
class AndroidMediaScanner {
5+
final MediaScannerHostApi _api = MediaScannerHostApi();
6+
7+
// Scans a file to make it visible in the device's media library
8+
Future<void> scanFile(String filePath) async {
9+
await _api.scanFile(filePath);
10+
}
11+
}

0 commit comments

Comments
 (0)