Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lightbox: Add download button to bottom app bar #1144

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions android/app/src/main/kotlin/com/zulip/flutter/DownloadManager.g.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Autogenerated from Pigeon (v22.7.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

package com.zulip.flutter

import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer

private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}

private fun wrapError(exception: Throwable): List<Any?> {
return if (exception is DownloadManagerError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}

/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class DownloadManagerError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
private open class DownloadManagerPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}


/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface DownloadManagerHostApi {
/**
* Downloads a file using the given URL and saves it with the specified file name in the Downloads directory.
* Returns a success message or an error message.
*/
fun downloadFile(fileUrl: String, fileName: String, header: String, callback: (Result<String>) -> Unit)

companion object {
/** The codec used by DownloadManagerHostApi. */
val codec: MessageCodec<Any?> by lazy {
DownloadManagerPigeonCodec()
}
/** Sets up an instance of `DownloadManagerHostApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: DownloadManagerHostApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.zulip.DownloadManagerHostApi.downloadFile$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val fileUrlArg = args[0] as String
val fileNameArg = args[1] as String
val headerArg = args[2] as String
api.downloadFile(fileUrlArg, fileNameArg, headerArg) { result: Result<String> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
42 changes: 40 additions & 2 deletions android/app/src/main/kotlin/com/zulip/flutter/ZulipPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.zulip.flutter

import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
Expand All @@ -19,7 +21,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.graphics.drawable.IconCompat
import io.flutter.embedding.engine.plugins.FlutterPlugin

import org.json.JSONObject
private const val TAG = "ZulipPlugin"

fun toAndroidPerson(person: Person): androidx.core.app.Person {
Expand Down Expand Up @@ -283,17 +285,50 @@ private class AndroidNotificationHost(val context: Context)
}
}

/** Host class for handling downloads via DownloadManager */
class DownloadHost(private val context: Context) : DownloadManagerHostApi {

/** Downloads a file from the given URL and saves it to the specified filename in the Downloads folder. */
override fun downloadFile(url: String, fileName: String, header: String, callback: (Result<String>) -> Unit) {
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val headersJsonObject = JSONObject(header)
val headersMap = mutableMapOf<String, String>()
for (key in headersJsonObject.keys()) {
headersMap[key] = headersJsonObject.getString(key)
}
val uri = Uri.parse(url)

val request = DownloadManager.Request(uri).apply {
setTitle("Downloading $fileName")
setDescription("File is being downloaded...")
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)

for ((key, value) in headersMap) {
addRequestHeader(key, value)
}
}
// Queue the download
downloadManager.enqueue(request)
callback(Result.success("Download started successfully for: $fileName"))
}
}

/** A Flutter plugin for the Zulip app's ad-hoc needs. */
// @Keep is needed because this class is used only
// from ZulipShimPlugin, via reflection.
@Keep
class ZulipPlugin : FlutterPlugin { // TODO ActivityAware too?
private var notificationHost: AndroidNotificationHost? = null
private var downloadHost: DownloadHost? = null

override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
Log.d(TAG, "Attaching to Flutter engine.")
notificationHost = AndroidNotificationHost(binding.applicationContext)
downloadHost = DownloadHost(binding.applicationContext)

AndroidNotificationHostApi.setUp(binding.binaryMessenger, notificationHost)
DownloadManagerHostApi.setUp(binding.binaryMessenger, downloadHost)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
Expand All @@ -302,6 +337,9 @@ class ZulipPlugin : FlutterPlugin { // TODO ActivityAware too?
return
}
AndroidNotificationHostApi.setUp(binding.binaryMessenger, null)
DownloadManagerHostApi.setUp(binding.binaryMessenger, null)

notificationHost = null
downloadHost = null
}
}
}
16 changes: 16 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,22 @@
"@lightboxVideoDuration": {
"description": "The total duration of the video playing in the lightbox."
},
"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 @@ -681,6 +681,30 @@ abstract class ZulipLocalizations {
/// **'Video duration'**
String get lightboxVideoDuration;

/// 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 @@ -340,6 +340,18 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get lightboxVideoDuration => 'Video duration';

@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 @@ -340,6 +340,18 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get lightboxVideoDuration => 'Video duration';

@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 @@ -340,6 +340,18 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get lightboxVideoDuration => 'Video duration';

@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_nb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,18 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
@override
String get lightboxVideoDuration => 'Video duration';

@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 @@ -340,6 +340,18 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
@override
String get lightboxVideoDuration => 'Video duration';

@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 @@ -340,6 +340,18 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
@override
String get lightboxVideoDuration => 'Video duration';

@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 => 'Вход в систему';

Expand Down
12 changes: 12 additions & 0 deletions lib/generated/l10n/zulip_localizations_sk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,18 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
@override
String get lightboxVideoDuration => 'Video duration';

@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 => 'Prihlásiť sa';

Expand Down
11 changes: 11 additions & 0 deletions lib/host/android_download_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'android_download_manager.g.dart';

// Wrapper class for Download functionality
class AndroidDownloader {
final DownloadManagerHostApi _api = DownloadManagerHostApi();

/// Downloads a file from the given URL and saves it with the specified file name.
Future<void> downloadFile(String url, String fileName, String header) async {
await _api.downloadFile(url, fileName, header);
}
}
Loading
Loading