diff --git a/core/lib/data/network/download/download_manager.dart b/core/lib/data/network/download/download_manager.dart index efe7a791e..e143cf2dc 100644 --- a/core/lib/data/network/download/download_manager.dart +++ b/core/lib/data/network/download/download_manager.dart @@ -109,17 +109,25 @@ class DownloadManager { } } - Future openDownloadedFileWeb(Uint8List bytes, String? mimeType) async { + Future openDownloadedFileWeb( + Uint8List bytes, + String? mimeType, + String? fileName + ) async { try { + fileName ??= 'unknown.pdf'; final deviceInfo = await _deviceInfoPlugin.deviceInfo; - if ('${deviceInfo.data['browserName']}' != 'BrowserName.chrome' - || mimeType != Constant.pdfMimeType) { + if (mimeType != Constant.pdfMimeType + || '${deviceInfo.data['browserName']}' == 'BrowserName.firefox') { final blob = html.Blob([bytes], mimeType); - final url = html.Url.createObjectUrlFromBlob(blob); + final file = html.File([blob], fileName, {'type': mimeType}); + final url = html.Url.createObjectUrl(file); html.window.open(url, '_blank'); html.Url.revokeObjectUrl(url); + } else if ('${deviceInfo.data['browserName']}' == 'BrowserName.chrome') { + HtmlUtils.openNewTabHtmlDocument(HtmlUtils.chromePdfViewer(bytes, fileName)); } else { - HtmlUtils.openNewTabHtmlDocument(HtmlUtils.pdfViewer(bytes)); + HtmlUtils.openNewTabHtmlDocument(HtmlUtils.safariPdfViewer(bytes, fileName)); } } catch (exception) { logError('DownloadManager::openDownloadedFileWeb(): ERROR: $exception'); diff --git a/core/lib/utils/html/html_utils.dart b/core/lib/utils/html/html_utils.dart index ca20c0ab8..423b772df 100644 --- a/core/lib/utils/html/html_utils.dart +++ b/core/lib/utils/html/html_utils.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'js_interop_stub.dart' if (dart.library.html) 'dart:js_interop'; import 'dart:typed_data'; @@ -151,44 +153,256 @@ class HtmlUtils { html.Url.revokeObjectUrl(url); } - static get iframeFullScreenCssStyle => ''' - position: fixed; - border: 0; - width: 100%; - height: 100%;'''; - - static String pdfViewer(Uint8List bytes) { + static String chromePdfViewer(Uint8List bytes, String fileName) { return ''' - - + + $fileName + - - + + '''; } + + static String safariPdfViewer(Uint8List bytes, String fileName) { + final base64 = base64Encode(bytes); + + return ''' + + + + + $fileName + + + +
+ $_pdfAppbarElement +
+
+ + + + + '''; + } + + static const String _pdfContainerStyle = ''' + display: flex; + flex-direction: column; + width: 100%;'''; + + static const String _pdfViewerStyle = ''' + flex: 1; /* Allow viewer to fill remaining space */ + border: 1px solid #ddd; + margin-left: auto; + margin-right: auto; + padding-top: 53px; + border: none;'''; + + static const String _pdfAppBarStyle = ''' + position: fixed; /* Fix app bar to top */ + top: 0; + left: 0; + right: 0; /* Stretch across entire viewport */ + display: flex; + justify-content: space-between; + padding: 5px 10px; + background-color: #f0f0f0; + z-index: 100; /* Ensure buttons stay on top */'''; + + static const String _pdfDownloadButtonStyle = ''' + padding: 5px 10px; + border: 1px solid #ddd; + border-radius: 5px; + cursor: pointer; + margin-left: 10px;'''; + + static const String _pdfFileInfoStyle = ''' + width: 30%; + display: flex; + align-items: center; + padding: 5px 10px;'''; + + static const String _pdfFileNameStyle = ''' + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical;'''; + + static const String _pdfAppbarElement = ''' +
+
+ + () +
+
+
+ +
+
'''; + + static String _downloadButtonListenerScript(Uint8List bytes, String? fileName) { + return ''' + const downloadBtn = document.getElementById('download-btn'); + downloadBtn.addEventListener('click', () => { + const buffer = new Uint8Array(${bytes.toJS}).buffer; + const blob = new Blob([buffer], { type: "application/pdf" }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.download = "$fileName"; + a.href = url; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + });'''; + } + + static String _fileInfoScript(Uint8List bytes, String? fileName) { + return ''' + function formatFileSize(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat(bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]; + } + + const fileNameSpan = document.getElementById('file-name'); + fileNameSpan.textContent = "$fileName"; + + const fileSizeSpan = document.getElementById('file-size'); + const byteArray = new Uint8Array(${bytes.toJS}); + fileSizeSpan.textContent = formatFileSize(bytesJs.length);'''; + } } diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 29e727cd2..50b72991f 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -828,7 +828,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _downloadManager.openDownloadedFileWeb( success.bytes, - success.attachment.type?.mimeType); + success.attachment.type?.mimeType, + success.attachment.name); } void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) { diff --git a/test/features/email/presentation/controller/single_email_controller_test.dart b/test/features/email/presentation/controller/single_email_controller_test.dart index 770bddce1..c3233e3d4 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -178,7 +178,10 @@ void main() { 'when attachment mime type is pdf', () async { // arrange - final testAttachment = Attachment(type: MediaType('application', 'pdf')); + const attachmentName = 'test_name.pdf'; + final testAttachment = Attachment( + type: MediaType('application', 'pdf'), + name: attachmentName); when(mailboxDashboardController.sessionCurrent).thenReturn(testSession); when(viewAttachmentForWebInteractor.execute( testDownloadTaskId, @@ -201,10 +204,11 @@ void main() { await untilCalled(mailboxDashboardController.deleteDownloadTask(any)); verify(mailboxDashboardController.deleteDownloadTask(testDownloadTaskId)) .called(1); - await untilCalled(downloadManager.openDownloadedFileWeb(any, any)); + await untilCalled(downloadManager.openDownloadedFileWeb(any, any, any)); verify(downloadManager.openDownloadedFileWeb( testBytes, Constant.pdfMimeType, + attachmentName, )).called(1); }, );