From b49e1b3a97132233ae3d6a2099601fb20192aeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Sat, 9 Nov 2024 19:18:06 +0100 Subject: [PATCH] Printing mails. (#471) * Implemented printing functionality. * Implemented icon for printing. * Remove debug code. --- Wino.Core.Domain/Enums/PrintingResult.cs | 10 + .../Interfaces/IApplicationConfiguration.cs | 6 + Wino.Core.Domain/Interfaces/IPrintService.cs | 10 + .../Models/Printing/PrintInformation.cs | 16 ++ .../Translations/en_US/resources.json | 4 + Wino.Core.UWP/CoreUWPContainerSetup.cs | 1 + Wino.Core.UWP/Services/PrintService.cs | 266 ++++++++++++++++++ Wino.Core.UWP/Wino.Core.UWP.csproj | 7 + .../Services/ApplicationConfiguration.cs | 2 +- .../MailRenderingPageViewModel.cs | 90 +++++- Wino.Mail/App.xaml.cs | 1 + Wino.Mail/Assets/WinoIcons.ttf | Bin 22028 -> 22188 bytes Wino.Mail/Controls/ControlConstants.cs | 2 +- Wino.Mail/Controls/WinoFontIcon.cs | 3 +- Wino.Mail/Helpers/XamlHelpers.cs | 1 + Wino.Mail/Views/MailRenderingPage.xaml.cs | 31 +- .../Client/Mails/PrintMailRequested.cs | 9 + Wino.Server/App.xaml.cs | 1 + 18 files changed, 422 insertions(+), 38 deletions(-) create mode 100644 Wino.Core.Domain/Enums/PrintingResult.cs create mode 100644 Wino.Core.Domain/Interfaces/IPrintService.cs create mode 100644 Wino.Core.Domain/Models/Printing/PrintInformation.cs create mode 100644 Wino.Core.UWP/Services/PrintService.cs create mode 100644 Wino.Messages/Client/Mails/PrintMailRequested.cs diff --git a/Wino.Core.Domain/Enums/PrintingResult.cs b/Wino.Core.Domain/Enums/PrintingResult.cs new file mode 100644 index 00000000..64ab8564 --- /dev/null +++ b/Wino.Core.Domain/Enums/PrintingResult.cs @@ -0,0 +1,10 @@ +namespace Wino.Core.Domain.Enums +{ + public enum PrintingResult + { + Abandoned, + Canceled, + Failed, + Submitted + } +} diff --git a/Wino.Core.Domain/Interfaces/IApplicationConfiguration.cs b/Wino.Core.Domain/Interfaces/IApplicationConfiguration.cs index 4a9aca60..f2efb5d7 100644 --- a/Wino.Core.Domain/Interfaces/IApplicationConfiguration.cs +++ b/Wino.Core.Domain/Interfaces/IApplicationConfiguration.cs @@ -17,5 +17,11 @@ /// Publisher shared folder path. /// string PublisherSharedFolderPath { get; set; } + + /// + /// Temp folder path of the application. + /// Files here are short-lived and can be deleted by system. + /// + string ApplicationTempFolderPath { get; set; } } } diff --git a/Wino.Core.Domain/Interfaces/IPrintService.cs b/Wino.Core.Domain/Interfaces/IPrintService.cs new file mode 100644 index 00000000..29578172 --- /dev/null +++ b/Wino.Core.Domain/Interfaces/IPrintService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Wino.Core.Domain.Enums; + +namespace Wino.Core.Domain.Interfaces +{ + public interface IPrintService + { + Task PrintPdfFileAsync(string pdfFilePath, string printTitle); + } +} diff --git a/Wino.Core.Domain/Models/Printing/PrintInformation.cs b/Wino.Core.Domain/Models/Printing/PrintInformation.cs new file mode 100644 index 00000000..b0dd289b --- /dev/null +++ b/Wino.Core.Domain/Models/Printing/PrintInformation.cs @@ -0,0 +1,16 @@ +using System; + +namespace Wino.Core.Domain.Models.Printing +{ + public class PrintInformation + { + public PrintInformation(string pDFFilePath, string pDFTitle) + { + PDFFilePath = pDFFilePath ?? throw new ArgumentNullException(nameof(pDFFilePath)); + PDFTitle = pDFTitle ?? throw new ArgumentNullException(nameof(pDFTitle)); + } + + public string PDFFilePath { get; } + public string PDFTitle { get; } + } +} diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 0420f72d..28242b46 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -100,6 +100,10 @@ "DialogMessage_ComposerValidationFailedTitle": "Validation Failed", "DialogMessage_CreateLinkedAccountMessage": "Give this new link a name. Accounts will be merged under this name.", "DialogMessage_CreateLinkedAccountTitle": "Account Link Name", + "DialogMessage_PrintingFailedMessage": "Failed to print this mail. Result: {0}", + "DialogMessage_PrintingFailedTitle": "Failed", + "DialogMessage_PrintingSuccessTitle": "Success", + "DialogMessage_PrintingSuccessMessage": "Mail is sent to printer.", "DialogMessage_DeleteAccountConfirmationMessage": "Delete {0}?", "DialogMessage_DeleteAccountConfirmationTitle": "All data associated with this account will be deleted from disk permanently.", "DialogMessage_DiscardDraftConfirmationMessage": "This draft will be discarded. Do you want to continue?", diff --git a/Wino.Core.UWP/CoreUWPContainerSetup.cs b/Wino.Core.UWP/CoreUWPContainerSetup.cs index 07cd00e8..dd825f77 100644 --- a/Wino.Core.UWP/CoreUWPContainerSetup.cs +++ b/Wino.Core.UWP/CoreUWPContainerSetup.cs @@ -28,6 +28,7 @@ namespace Wino.Core.UWP services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddSingleton(); } } } diff --git a/Wino.Core.UWP/Services/PrintService.cs b/Wino.Core.UWP/Services/PrintService.cs new file mode 100644 index 00000000..127d92a1 --- /dev/null +++ b/Wino.Core.UWP/Services/PrintService.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Printing; +using Windows.Data.Pdf; +using Windows.Graphics.Printing; +using Windows.Graphics.Printing.OptionDetails; +using Windows.Storage.Streams; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Printing; + +namespace Wino.Core.UWP.Services +{ + /// + /// Printer service that uses WinRT APIs to print PDF files. + /// Used modified version of the code here: + /// https://github.com/microsoft/Win2D-Samples/blob/reunion_master/ExampleGallery/PrintingExample.xaml.cs + /// HTML file is saved as PDF to temporary location. + /// Then PDF is loaded as PdfDocument and printed using CanvasBitmap for each page. + /// + public class PrintService : IPrintService + { + private TaskCompletionSource _taskCompletionSource; + private CanvasPrintDocument printDocument; + private PrintTask printTask; + private PdfDocument pdfDocument; + + private List bitmaps = new(); + private Vector2 largestBitmap; + private Vector2 pageSize; + private Vector2 imagePadding = new Vector2(64, 64); + private Vector2 cellSize; + + private int bitmapCount; + private int columns; + private int rows; + private int bitmapsPerPage; + private int pageCount = -1; + + private PrintInformation _currentPrintInformation; + + public async Task PrintPdfFileAsync(string pdfFilePath, string printTitle) + { + if (_taskCompletionSource != null) + { + _taskCompletionSource.TrySetResult(PrintingResult.Abandoned); + _taskCompletionSource = new TaskCompletionSource(); + } + + // Load the PDF file + var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfFilePath); + pdfDocument = await PdfDocument.LoadFromFileAsync(file); + + _taskCompletionSource ??= new TaskCompletionSource(); + + _currentPrintInformation = new PrintInformation(pdfFilePath, printTitle); + + printDocument = new CanvasPrintDocument(); + printDocument.PrintTaskOptionsChanged += OnDocumentTaskOptionsChanged; + printDocument.Preview += OnDocumentPreview; + printDocument.Print += OnDocumentPrint; + + var printManager = PrintManager.GetForCurrentView(); + printManager.PrintTaskRequested += PrintingExample_PrintTaskRequested; + + try + { + await PrintManager.ShowPrintUIAsync(); + + var result = await _taskCompletionSource.Task; + + return result; + } + finally + { + // Dispose everything. + UnregisterPrintManager(printManager); + ClearBitmaps(); + UnregisterTask(); + DisposePDFDocument(); + + _taskCompletionSource = null; + } + } + + private void DisposePDFDocument() + { + if (pdfDocument != null) + { + pdfDocument = null; + } + } + + private void UnregisterTask() + { + if (printTask != null) + { + printTask.Completed -= TaskCompleted; + printTask = null; + } + } + + private void UnregisterPrintManager(PrintManager manager) + { + manager.PrintTaskRequested -= PrintingExample_PrintTaskRequested; + } + + private void ClearBitmaps() + { + foreach (var bitmap in bitmaps) + { + bitmap.Dispose(); + } + + bitmaps.Clear(); + } + + private void PrintingExample_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args) + { + if (_currentPrintInformation == null) return; + + printTask = args.Request.CreatePrintTask(_currentPrintInformation.PDFTitle, (createPrintTaskArgs) => + { + createPrintTaskArgs.SetSource(printDocument); + }); + + printTask.Completed += TaskCompleted; + } + + private void TaskCompleted(PrintTask sender, PrintTaskCompletedEventArgs args) + => _taskCompletionSource?.TrySetResult((PrintingResult)args.Completion); + + private async void OnDocumentTaskOptionsChanged(CanvasPrintDocument sender, CanvasPrintTaskOptionsChangedEventArgs args) + { + var deferral = args.GetDeferral(); + + try + { + await LoadPDFPageBitmapsAsync(sender); + + var pageDesc = args.PrintTaskOptions.GetPageDescription(1); + var newPageSize = pageDesc.PageSize.ToVector2(); + + if (pageSize == newPageSize && pageCount != -1) + { + // We've already figured out the pages and the page size hasn't changed, so there's nothing left for us to do here. + return; + } + + pageSize = newPageSize; + sender.InvalidatePreview(); + + // Figure out the bitmap index at the top of the current preview page. We'll request that the preview defaults to showing + // the page that still has this bitmap on it in the new layout. + int indexOnCurrentPage = 0; + if (pageCount != -1) + { + indexOnCurrentPage = (int)(args.CurrentPreviewPageNumber - 1) * bitmapsPerPage; + } + + // Calculate the new layout + var printablePageSize = pageSize * 0.9f; + + cellSize = largestBitmap + imagePadding; + + var cellsPerPage = printablePageSize / cellSize; + + columns = Math.Max(1, (int)Math.Floor(cellsPerPage.X)); + rows = Math.Max(1, (int)Math.Floor(cellsPerPage.Y)); + + bitmapsPerPage = columns * rows; + + // Calculate the page count + bitmapCount = bitmaps.Count; + pageCount = (int)Math.Ceiling(bitmapCount / (double)bitmapsPerPage); + sender.SetPageCount((uint)pageCount); + + // Set the preview page to the one that has the item that was currently displayed in the last preview + args.NewPreviewPageNumber = (uint)(indexOnCurrentPage / bitmapsPerPage) + 1; + } + finally + { + deferral.Complete(); + } + } + + + private async Task LoadPDFPageBitmapsAsync(CanvasPrintDocument sender) + { + ClearBitmaps(); + + bitmaps ??= new List(); + + for (int i = 0; i < pdfDocument.PageCount; i++) + { + var page = pdfDocument.GetPage((uint)i); + var stream = new InMemoryRandomAccessStream(); + await page.RenderToStreamAsync(stream); + var bitmap = await CanvasBitmap.LoadAsync(sender, stream); + bitmaps.Add(bitmap); + } + + largestBitmap = Vector2.Zero; + + foreach (var bitmap in bitmaps) + { + largestBitmap.X = Math.Max(largestBitmap.X, (float)bitmap.Size.Width); + largestBitmap.Y = Math.Max(largestBitmap.Y, (float)bitmap.Size.Height); + } + } + + + private void OnDocumentPreview(CanvasPrintDocument sender, CanvasPreviewEventArgs args) + { + var ds = args.DrawingSession; + var pageNumber = args.PageNumber; + + DrawPdfPage(sender, ds, pageNumber); + } + + private void OnDocumentPrint(CanvasPrintDocument sender, CanvasPrintEventArgs args) + { + var detailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(args.PrintTaskOptions); + + int pageCountToPrint = (int)pdfDocument.PageCount; + + for (uint i = 1; i <= pageCountToPrint; ++i) + { + using var ds = args.CreateDrawingSession(); + var imageableRect = args.PrintTaskOptions.GetPageDescription(i).ImageableRect; + + DrawPdfPage(sender, ds, i); + } + } + + private void DrawPdfPage(CanvasPrintDocument sender, CanvasDrawingSession ds, uint pageNumber) + { + if (bitmaps?.Count == 0) return; + + var cellAcross = new Vector2(cellSize.X, 0); + var cellDown = new Vector2(0, cellSize.Y); + + var totalSize = cellAcross * columns + cellDown * rows; + Vector2 topLeft = (pageSize - totalSize) / 2; + + int bitmapIndex = ((int)pageNumber - 1) * bitmapsPerPage; + + for (int row = 0; row < rows; ++row) + { + for (int column = 0; column < columns; ++column) + { + var cellTopLeft = topLeft + cellAcross * column + cellDown * row; + var bitmapInfo = bitmaps[bitmapIndex % bitmaps.Count]; + var bitmapPos = cellTopLeft + (cellSize - bitmapInfo.Size.ToVector2()) / 2; + + ds.DrawImage(bitmapInfo, bitmapPos); + + bitmapIndex++; + } + } + } + } +} diff --git a/Wino.Core.UWP/Wino.Core.UWP.csproj b/Wino.Core.UWP/Wino.Core.UWP.csproj index a2fbcd9f..6073a330 100644 --- a/Wino.Core.UWP/Wino.Core.UWP.csproj +++ b/Wino.Core.UWP/Wino.Core.UWP.csproj @@ -91,6 +91,7 @@ + @@ -120,6 +121,9 @@ 7.1.3 + + 1.28.0 + @@ -140,6 +144,9 @@ Windows Desktop Extensions for the UWP + + + 14.0 diff --git a/Wino.Core/Services/ApplicationConfiguration.cs b/Wino.Core/Services/ApplicationConfiguration.cs index e920a12e..d5d12bf8 100644 --- a/Wino.Core/Services/ApplicationConfiguration.cs +++ b/Wino.Core/Services/ApplicationConfiguration.cs @@ -7,7 +7,7 @@ namespace Wino.Core.Services public const string SharedFolderName = "WinoShared"; public string ApplicationDataFolderPath { get; set; } - public string PublisherSharedFolderPath { get; set; } + public string ApplicationTempFolderPath { get; set; } } } diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs index 29664291..5256dfe7 100644 --- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs @@ -26,6 +26,7 @@ using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Messages; using Wino.Messaging.Client.Mails; using Wino.Messaging.Server; +using IMailService = Wino.Core.Domain.Interfaces.IMailService; namespace Wino.Mail.ViewModels { @@ -42,12 +43,17 @@ namespace Wino.Mail.ViewModels private readonly IContactService _contactService; private readonly IClipboardService _clipboardService; private readonly IUnsubscriptionService _unsubscriptionService; + private readonly IApplicationConfiguration _applicationConfiguration; private readonly IWinoServerConnectionManager _winoServerConnectionManager; private bool forceImageLoading = false; private MailItemViewModel initializedMailItemViewModel = null; private MimeMessageInformation initializedMimeMessageInformation = null; + // Func to get WebView2 to save current HTML as PDF to given location. + // Used in 'Save as' and 'Print' functionality. + public Func> SaveHTMLasPDFFunc { get; set; } + #region Properties public bool ShouldDisplayDownloadProgress => IsIndetermineProgress || (CurrentDownloadPercentage > 0 && CurrentDownloadPercentage <= 100); @@ -121,12 +127,13 @@ namespace Wino.Mail.ViewModels public INativeAppService NativeAppService { get; } public IStatePersistanceService StatePersistenceService { get; } public IPreferencesService PreferencesService { get; } + public IPrintService PrintService { get; } public MailRenderingPageViewModel(IDialogService dialogService, INativeAppService nativeAppService, IUnderlyingThemeService underlyingThemeService, IMimeFileService mimeFileService, - Core.Domain.Interfaces.IMailService mailService, + IMailService mailService, IFileService fileService, IWinoRequestDelegator requestDelegator, IStatePersistanceService statePersistenceService, @@ -134,12 +141,16 @@ namespace Wino.Mail.ViewModels IClipboardService clipboardService, IUnsubscriptionService unsubscriptionService, IPreferencesService preferencesService, + IPrintService printService, + IApplicationConfiguration applicationConfiguration, IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService) { NativeAppService = nativeAppService; StatePersistenceService = statePersistenceService; _contactService = contactService; PreferencesService = preferencesService; + PrintService = printService; + _applicationConfiguration = applicationConfiguration; _winoServerConnectionManager = winoServerConnectionManager; _clipboardService = clipboardService; _unsubscriptionService = unsubscriptionService; @@ -150,7 +161,6 @@ namespace Wino.Mail.ViewModels _requestDelegator = requestDelegator; } - [RelayCommand] private async Task CopyClipboard(string copyText) { @@ -243,14 +253,11 @@ namespace Wino.Mail.ViewModels IsDarkWebviewRenderer = !IsDarkWebviewRenderer; else if (operation == MailOperation.SaveAs) { - // Save as PDF - var pickedFolder = await DialogService.PickWindowsFolderAsync(); - - if (!string.IsNullOrEmpty(pickedFolder)) - { - var fullPath = Path.Combine(pickedFolder, $"{initializedMailItemViewModel.FromAddress}.pdf"); - Messenger.Send(new SaveAsPDFRequested(fullPath)); - } + await SaveAsAsync(); + } + else if (operation == MailOperation.Print) + { + await PrintAsync(); } else if (operation == MailOperation.Reply || operation == MailOperation.ReplyAll || operation == MailOperation.Forward) { @@ -519,6 +526,9 @@ namespace Wino.Mail.ViewModels // Save As PDF MenuItems.Add(MailOperationMenuItem.Create(MailOperation.SaveAs, true, true)); + // Print + MenuItems.Add(MailOperationMenuItem.Create(MailOperation.Print, true, true)); + if (initializedMailItemViewModel == null) return; @@ -659,6 +669,66 @@ namespace Wino.Mail.ViewModels } } + private async Task PrintAsync() + { + // Printing: + // 1. Let WebView2 save the current HTML as PDF to temporary location. + // 2. Saving as PDF will divide pages correctly for Win2D CanvasBitmap. + // 3. Use Win2D CanvasBitmap as IPrintDocumentSource and WinRT APIs to print the PDF. + + try + { + var printFilePath = Path.Combine(_applicationConfiguration.ApplicationTempFolderPath, "print.pdf"); + + if (File.Exists(printFilePath)) File.Delete(printFilePath); + + await SaveHTMLasPDFFunc(printFilePath); + + var result = await PrintService.PrintPdfFileAsync(printFilePath, Subject); + + if (result == PrintingResult.Submitted) + { + DialogService.InfoBarMessage(Translator.DialogMessage_PrintingSuccessTitle, Translator.DialogMessage_PrintingSuccessMessage, InfoBarMessageType.Success); + } + else + { + var message = string.Format(Translator.DialogMessage_PrintingFailedMessage, result); + DialogService.InfoBarMessage(Translator.DialogMessage_PrintingFailedTitle, message, InfoBarMessageType.Warning); + } + } + catch (Exception ex) + { + DialogService.InfoBarMessage(string.Empty, ex.Message, InfoBarMessageType.Error); + Crashes.TrackError(ex); + } + } + + private async Task SaveAsAsync() + { + try + { + var pickedFolder = await DialogService.PickWindowsFolderAsync(); + + if (string.IsNullOrEmpty(pickedFolder)) return; + + var pdfFilePath = Path.Combine(pickedFolder, $"{initializedMailItemViewModel.FromAddress}.pdf"); + + bool isSaved = await SaveHTMLasPDFFunc(pdfFilePath); + + if (isSaved) + { + DialogService.InfoBarMessage(Translator.Info_PDFSaveSuccessTitle, + string.Format(Translator.Info_PDFSaveSuccessMessage, pdfFilePath), + InfoBarMessageType.Success); + } + } + catch (Exception ex) + { + DialogService.InfoBarMessage(Translator.Info_PDFSaveFailedTitle, ex.Message, InfoBarMessageType.Error); + Crashes.TrackError(ex); + } + } + // Returns created file path. private async Task SaveAttachmentInternalAsync(MailAttachmentViewModel attachmentViewModel, string saveFolderPath) { diff --git a/Wino.Mail/App.xaml.cs b/Wino.Mail/App.xaml.cs index ceb71e8e..e3cbbf7e 100644 --- a/Wino.Mail/App.xaml.cs +++ b/Wino.Mail/App.xaml.cs @@ -96,6 +96,7 @@ namespace Wino // Make sure the paths are setup on app start. _applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path; _applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path; + _applicationFolderConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path; _appServiceConnectionManager = Services.GetService>(); _themeService = Services.GetService(); diff --git a/Wino.Mail/Assets/WinoIcons.ttf b/Wino.Mail/Assets/WinoIcons.ttf index dda05f2a2ed961321e18681560128f24f789030d..81d66189889144c82dca88fbfbc4866dd962a3a3 100644 GIT binary patch delta 1154 zcmXAoUrbY19LK-swsg=IZm;wfs0DjVdl7Zupp>a-D&YR;bP06E`QT_aW=)E+z_43l z+PS!x#cXIDb56;g;Dse-BpZvFX|fkh++r3V9Gi(wO`ICT{QE-azPINzpYyrDb9(;V z-#Pc#jF|aej0dy7f46TtAOb*HdT(-M7=Q& z1?W4+{#c3)<-)#=@m#6S4HWIp@msfp?SVVv~J9BFe^zp=4$lI|bKETxJ~H zpB$%4=a zSmX%nF0$Bp^}cm9*A>y+}W3eUh;g!>$zN2#7v zzh`iw>K}EFI`4^khP5-x2E3cRr}d!zgKws0ea%>HTkVB9XI;iW8n6d)fs6G@{jU0{ zhT4Wy!_^fhSNstiFfyT4p~IoY#{G>8;m^X)BBQ2zRhXq;WBe?c8b zXu|D4Yg;>%(g$k34osePxWUPxlN<&p+!S#%>Ljb>I7}fe*bNWDQ79hgazuloKzzlO zk-IC#jH8ARhB3np!?1opU#VNv-O^pudH8%jmDfctM^8mhM9oHZX*;xa+6--)#-Xuk zOqy8rJM}&F6?H&e961;1QuRrziXRn0UMp=)Nx71rqEJBT;DYCY&jhUGOAI6dUrHEA zCSPE{M($(4PVQ$Q1y~wpAeB7Fz;@tUDFbO_nk#{H@)#qC3~DSfumf0GXMl42eG3CS z$z}$!$Q}k9m^eNesJY~zYJ+PPBAE#ue Xyq14}a;^dzc?$|E%(&2U^)&YnGXLqo diff --git a/Wino.Mail/Controls/ControlConstants.cs b/Wino.Mail/Controls/ControlConstants.cs index 82539bd1..dbec4b7f 100644 --- a/Wino.Mail/Controls/ControlConstants.cs +++ b/Wino.Mail/Controls/ControlConstants.cs @@ -62,7 +62,7 @@ namespace Wino.Controls { WinoIconGlyph.Mail, "\uF509" }, { WinoIconGlyph.More, "\uE824" }, { WinoIconGlyph.CustomServer, "\uF509" }, - + { WinoIconGlyph.Print, "\uE954" }, { WinoIconGlyph.Attachment, "\uE723" }, { WinoIconGlyph.SortTextDesc, "\U000F3606" }, { WinoIconGlyph.SortLinesDesc, "\U000F038A" }, diff --git a/Wino.Mail/Controls/WinoFontIcon.cs b/Wino.Mail/Controls/WinoFontIcon.cs index 59c6af0f..91dd7736 100644 --- a/Wino.Mail/Controls/WinoFontIcon.cs +++ b/Wino.Mail/Controls/WinoFontIcon.cs @@ -69,7 +69,8 @@ namespace Wino.Controls Blocked, Message, New, - IMAP + IMAP, + Print } public class WinoFontIcon : FontIcon diff --git a/Wino.Mail/Helpers/XamlHelpers.cs b/Wino.Mail/Helpers/XamlHelpers.cs index 627db531..9a2cb1ed 100644 --- a/Wino.Mail/Helpers/XamlHelpers.cs +++ b/Wino.Mail/Helpers/XamlHelpers.cs @@ -144,6 +144,7 @@ namespace Wino.Helpers MailOperation.ReplyAll => WinoIconGlyph.ReplyAll, MailOperation.Zoom => WinoIconGlyph.Zoom, MailOperation.SaveAs => WinoIconGlyph.Save, + MailOperation.Print => WinoIconGlyph.Print, MailOperation.Find => WinoIconGlyph.Find, MailOperation.Forward => WinoIconGlyph.Forward, MailOperation.DarkEditor => WinoIconGlyph.DarkEditor, diff --git a/Wino.Mail/Views/MailRenderingPage.xaml.cs b/Wino.Mail/Views/MailRenderingPage.xaml.cs index 481268b5..560ef940 100644 --- a/Wino.Mail/Views/MailRenderingPage.xaml.cs +++ b/Wino.Mail/Views/MailRenderingPage.xaml.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; -using Microsoft.AppCenter.Crashes; using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; @@ -12,8 +11,6 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Navigation; -using Wino.Core.Domain; -using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Mail.ViewModels.Data; using Wino.Messaging.Client.Mails; @@ -25,8 +22,7 @@ namespace Wino.Views public sealed partial class MailRenderingPage : MailRenderingPageAbstract, IRecipient, IRecipient, - IRecipient, - IRecipient + IRecipient { private readonly IPreferencesService _preferencesService = App.Current.Services.GetService(); private readonly IDialogService _dialogService = App.Current.Services.GetService(); @@ -44,6 +40,11 @@ namespace Wino.Views Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache"); + + ViewModel.SaveHTMLasPDFFunc = new Func>((path) => + { + return Chromium.CoreWebView2.PrintToPdfAsync(path, null).AsTask(); + }); } public override async void OnEditorThemeChanged() @@ -277,26 +278,6 @@ namespace Wino.Views ViewModel.IsDarkWebviewRenderer = message.IsUnderlyingThemeDark; } - public async void Receive(SaveAsPDFRequested message) - { - try - { - bool isSaved = await Chromium.CoreWebView2.PrintToPdfAsync(message.FileSavePath, null); - - if (isSaved) - { - _dialogService.InfoBarMessage(Translator.Info_PDFSaveSuccessTitle, - string.Format(Translator.Info_PDFSaveSuccessMessage, message.FileSavePath), - InfoBarMessageType.Success); - } - } - catch (Exception ex) - { - _dialogService.InfoBarMessage(Translator.Info_PDFSaveFailedTitle, ex.Message, InfoBarMessageType.Error); - Crashes.TrackError(ex); - } - } - private void InternetAddressClicked(object sender, RoutedEventArgs e) { if (sender is HyperlinkButton hyperlinkButton) diff --git a/Wino.Messages/Client/Mails/PrintMailRequested.cs b/Wino.Messages/Client/Mails/PrintMailRequested.cs new file mode 100644 index 00000000..277a831d --- /dev/null +++ b/Wino.Messages/Client/Mails/PrintMailRequested.cs @@ -0,0 +1,9 @@ +namespace Wino.Messaging.Client.Mails +{ + /// + /// When print mail is requested. + /// + /// Path to PDF file that WebView2 saved the html content as PDF. + /// Printer title on the dialog. + public record PrintMailRequested(string PDFFilePath, string PrintTitle); +} diff --git a/Wino.Server/App.xaml.cs b/Wino.Server/App.xaml.cs index c41c51dc..61f3a77f 100644 --- a/Wino.Server/App.xaml.cs +++ b/Wino.Server/App.xaml.cs @@ -84,6 +84,7 @@ namespace Wino.Server applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path; applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path; + applicationFolderConfiguration.ApplicationTempFolderPath = ApplicationData.Current.TemporaryFolder.Path; // Setup logger var logInitializer = Services.GetService();