TF-2737 Separate PDF view handler between each browser
This commit is contained in:
@@ -109,17 +109,25 @@ class DownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openDownloadedFileWeb(Uint8List bytes, String? mimeType) async {
|
||||
Future<void> 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');
|
||||
|
||||
@@ -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 '''
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<meta charset="utf-8" />
|
||||
<title>$fileName</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.mjs" type="module"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
iframe {
|
||||
${HtmlUtils.iframeFullScreenCssStyle}
|
||||
#pdf-container {
|
||||
$_pdfContainerStyle
|
||||
}
|
||||
|
||||
#pdf-viewer {
|
||||
$_pdfViewerStyle
|
||||
}
|
||||
|
||||
#app-bar {
|
||||
$_pdfAppBarStyle
|
||||
}
|
||||
|
||||
#download-btn {
|
||||
$_pdfDownloadButtonStyle
|
||||
}
|
||||
|
||||
#file-info {
|
||||
$_pdfFileInfoStyle
|
||||
}
|
||||
|
||||
#file-name {
|
||||
$_pdfFileNameStyle
|
||||
}
|
||||
</style>
|
||||
<body></body>
|
||||
<script>
|
||||
const bytesJs = new Uint8Array(${bytes.toJS}).buffer;
|
||||
const blob = new Blob([bytesJs], { type: "application/pdf" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
</head>
|
||||
<body>
|
||||
<div id="pdf-container">
|
||||
$_pdfAppbarElement
|
||||
<div id="pdf-viewer"></div>
|
||||
</div>
|
||||
|
||||
window.onload = function() {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = url;
|
||||
document.body.appendChild(iframe);
|
||||
};
|
||||
window.addEventListener("beforeunload", function listener(event) {
|
||||
URL.revokeObjectURL(url);
|
||||
window.removeEventListener("beforeunload", listener);
|
||||
});
|
||||
</script>
|
||||
<script type="module">
|
||||
function renderPage(pdfDoc, pageNumber, canvas) {
|
||||
pdfDoc.getPage(pageNumber).then(page => {
|
||||
const viewport = page.getViewport({ scale: 1.5 });
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
page.render(renderContext);
|
||||
});
|
||||
}
|
||||
|
||||
const bytesJs = new Uint8Array(${bytes.toJS});
|
||||
const pdfContainer = document.getElementById('pdf-viewer');
|
||||
|
||||
var { pdfjsLib } = globalThis;
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.mjs';
|
||||
|
||||
var loadingTask = pdfjsLib.getDocument(bytesJs);
|
||||
loadingTask.promise.then(function(pdf) {
|
||||
const numPages = pdf.numPages;
|
||||
|
||||
for (let i = 1; i <= numPages; i++) {
|
||||
const pageContainer = document.createElement('div');
|
||||
pageContainer.classList.add('pdf-page');
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.id = `page-\${i}`;
|
||||
|
||||
pageContainer.appendChild(canvas);
|
||||
pdfContainer.appendChild(pageContainer);
|
||||
|
||||
renderPage(pdf, i, canvas);
|
||||
}
|
||||
}, function (reason) {
|
||||
console.error(reason);
|
||||
});
|
||||
|
||||
${_fileInfoScript(bytes, fileName)}
|
||||
|
||||
${_downloadButtonListenerScript(bytes, fileName)}
|
||||
</script>
|
||||
</body>
|
||||
</html>''';
|
||||
}
|
||||
|
||||
static String safariPdfViewer(Uint8List bytes, String fileName) {
|
||||
final base64 = base64Encode(bytes);
|
||||
|
||||
return '''
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>$fileName</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#pdf-container {
|
||||
$_pdfContainerStyle
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#pdf-viewer {
|
||||
$_pdfViewerStyle
|
||||
width: 100%;
|
||||
height: calc(100vh - 53px);
|
||||
}
|
||||
|
||||
#app-bar {
|
||||
$_pdfAppBarStyle
|
||||
}
|
||||
|
||||
#download-btn {
|
||||
$_pdfDownloadButtonStyle
|
||||
}
|
||||
|
||||
#file-info {
|
||||
$_pdfFileInfoStyle
|
||||
}
|
||||
|
||||
#file-name {
|
||||
$_pdfFileNameStyle
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="pdf-container">
|
||||
$_pdfAppbarElement
|
||||
<div id="pdf-viewer"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.3.0/pdfobject.min.js"></script>
|
||||
<script>
|
||||
const bytesJs = new Uint8Array(${bytes.toJS});
|
||||
PDFObject.embed('data:application/pdf;base64,$base64', "#pdf-viewer");
|
||||
|
||||
${_fileInfoScript(bytes, fileName)}
|
||||
|
||||
${_downloadButtonListenerScript(bytes, fileName)}
|
||||
</script>
|
||||
</body>
|
||||
</html>''';
|
||||
}
|
||||
|
||||
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 = '''
|
||||
<div id="app-bar">
|
||||
<div id="file-info">
|
||||
<span id="file-name" style="margin-right: 10px;"></span>
|
||||
(<span id="file-size", style="white-space: nowrap;"></span>)
|
||||
</div>
|
||||
<div style="width: 10px;"></div>
|
||||
<div id="buttons">
|
||||
<button id="download-btn">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 20V18H5V20H19ZM19 10H15V4H9V10H5L12 17L19 10Z" fill="#7B7B7B"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>''';
|
||||
|
||||
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);''';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user