Custom print dialog and better message registrations

This commit is contained in:
Burak Kaan Köse
2025-10-21 01:27:29 +02:00
parent 4191b7314f
commit 057edb5488
27 changed files with 656 additions and 760 deletions
+14
View File
@@ -50,4 +50,18 @@ public sealed partial class AppShell : AppShellAbstract,
private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args)
=> ViewModel.NavigationService.GoBack();
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<CalendarDisplayTypeChangedMessage>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<CalendarDisplayTypeChangedMessage>(this);
}
}
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.Common;
using Wino.Core.Domain.Models.Printing;
namespace Wino.Core.Domain.Interfaces;
@@ -28,4 +29,5 @@ public interface IDialogServiceBase
IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult);
Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters);
Task<string> PickFilePathAsync(string saveFileName);
Task<WebView2PrintSettingsModel> ShowPrintDialogAsync(WebView2PrintSettingsModel initialSettings = null);
}
@@ -8,7 +8,7 @@ namespace Wino.Core.Domain.Models.MailItem;
/// </summary>
public class MailDragPackage
{
public MailDragPackage(IEnumerable<MailCopy> draggingMails)
public MailDragPackage(IEnumerable<object> draggingMails)
{
DraggingMails = draggingMails;
}
@@ -21,5 +21,5 @@ public class MailDragPackage
];
}
public IEnumerable<MailCopy> DraggingMails { get; set; }
public IEnumerable<object> DraggingMails { get; set; }
}
@@ -1,4 +1,5 @@
using System.ComponentModel;
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Printing;
@@ -6,339 +7,131 @@ namespace Wino.Core.Domain.Models.Printing;
/// <summary>
/// Wrapper model for CoreWebView2PrintSettings that provides bindable properties for UI controls.
/// </summary>
public class WebView2PrintSettingsModel : INotifyPropertyChanged
public partial class WebView2PrintSettingsModel : ObservableObject
{
private string _printerName = string.Empty;
private PrintOrientation _orientation = PrintOrientation.Portrait;
private PrintColorMode _colorMode = PrintColorMode.Color;
private PrintCollation _collation = PrintCollation.Default;
private PrintDuplex _duplex = PrintDuplex.Default;
private PrintMediaSize _mediaSize = PrintMediaSize.Default;
private int _copies = 1;
private double _marginTop = 1.0;
private double _marginBottom = 1.0;
private double _marginLeft = 1.0;
private double _marginRight = 1.0;
private bool _shouldPrintBackgrounds = false;
private bool _shouldPrintSelectionOnly = false;
private bool _shouldPrintHeaderAndFooter = true;
private string _headerTitle = string.Empty;
private string _footerUri = string.Empty;
private double _scaleFactor = 1.0;
private int _pagesPerSide = 1;
private string _pageRanges = string.Empty;
[ObservableProperty]
public partial string PrinterName { get; set; } = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
[ObservableProperty]
public partial PrintOrientation Orientation { get; set; } = PrintOrientation.Portrait;
[ObservableProperty]
public partial PrintColorMode ColorMode { get; set; } = PrintColorMode.Color;
[ObservableProperty]
public partial PrintCollation Collation { get; set; } = PrintCollation.Default;
[ObservableProperty]
public partial PrintDuplex Duplex { get; set; } = PrintDuplex.Default;
[ObservableProperty]
public partial PrintMediaSize MediaSize { get; set; } = PrintMediaSize.Default;
[ObservableProperty]
public partial int Copies { get; set; } = 1;
[ObservableProperty]
public partial double MarginTop { get; set; } = 1.0;
[ObservableProperty]
public partial double MarginBottom { get; set; } = 1.0;
[ObservableProperty]
public partial double MarginLeft { get; set; } = 1.0;
[ObservableProperty]
public partial double MarginRight { get; set; } = 1.0;
[ObservableProperty]
public partial bool ShouldPrintBackgrounds { get; set; } = false;
[ObservableProperty]
public partial bool ShouldPrintSelectionOnly { get; set; } = false;
[ObservableProperty]
public partial bool ShouldPrintHeaderAndFooter { get; set; } = true;
[ObservableProperty]
public partial string HeaderTitle { get; set; } = string.Empty;
[ObservableProperty]
public partial string FooterUri { get; set; } = string.Empty;
[ObservableProperty]
public partial double ScaleFactor { get; set; } = 1.0;
[ObservableProperty]
public partial int PagesPerSide { get; set; } = 1;
[ObservableProperty]
public partial string PageRanges { get; set; } = string.Empty;
/// <summary>
/// Name of the printer to use for printing.
/// Partial method for validation when Copies property changes.
/// </summary>
public string PrinterName
partial void OnCopiesChanged(int value)
{
get => _printerName;
set
if (value <= 0)
{
if (_printerName != value)
{
_printerName = value;
OnPropertyChanged(nameof(PrinterName));
}
Copies = 1; // Reset to minimum valid value
}
}
/// <summary>
/// Orientation of the printed document.
/// Partial method for validation when ScaleFactor property changes.
/// </summary>
public PrintOrientation Orientation
partial void OnScaleFactorChanged(double value)
{
get => _orientation;
set
if (value < 0.1 || value > 2.0)
{
if (_orientation != value)
{
_orientation = value;
OnPropertyChanged(nameof(Orientation));
}
ScaleFactor = Math.Clamp(value, 0.1, 2.0);
}
}
/// <summary>
/// Color mode for printing.
/// Partial method for validation when PagesPerSide property changes.
/// </summary>
public PrintColorMode ColorMode
partial void OnPagesPerSideChanged(int value)
{
get => _colorMode;
set
var validValues = new[] { 1, 2, 4, 6, 9, 16 };
if (System.Array.IndexOf(validValues, value) < 0)
{
if (_colorMode != value)
{
_colorMode = value;
OnPropertyChanged(nameof(ColorMode));
}
PagesPerSide = 1; // Reset to default valid value
}
}
/// <summary>
/// Collation setting for multiple copies.
/// Partial method for validation when margin properties change.
/// </summary>
public PrintCollation Collation
partial void OnMarginTopChanged(double value)
{
get => _collation;
set
if (value < 0)
{
if (_collation != value)
{
_collation = value;
OnPropertyChanged(nameof(Collation));
}
MarginTop = 0;
}
}
/// <summary>
/// Duplex printing mode.
/// </summary>
public PrintDuplex Duplex
partial void OnMarginBottomChanged(double value)
{
get => _duplex;
set
if (value < 0)
{
if (_duplex != value)
{
_duplex = value;
OnPropertyChanged(nameof(Duplex));
}
MarginBottom = 0;
}
}
/// <summary>
/// Media size for printing.
/// </summary>
public PrintMediaSize MediaSize
partial void OnMarginLeftChanged(double value)
{
get => _mediaSize;
set
if (value < 0)
{
if (_mediaSize != value)
{
_mediaSize = value;
OnPropertyChanged(nameof(MediaSize));
}
MarginLeft = 0;
}
}
/// <summary>
/// Number of copies to print.
/// </summary>
public int Copies
partial void OnMarginRightChanged(double value)
{
get => _copies;
set
if (value < 0)
{
if (_copies != value && value > 0)
{
_copies = value;
OnPropertyChanged(nameof(Copies));
}
MarginRight = 0;
}
}
/// <summary>
/// Top margin in inches.
/// </summary>
public double MarginTop
{
get => _marginTop;
set
{
if (_marginTop != value && value >= 0)
{
_marginTop = value;
OnPropertyChanged(nameof(MarginTop));
}
}
}
/// <summary>
/// Bottom margin in inches.
/// </summary>
public double MarginBottom
{
get => _marginBottom;
set
{
if (_marginBottom != value && value >= 0)
{
_marginBottom = value;
OnPropertyChanged(nameof(MarginBottom));
}
}
}
/// <summary>
/// Left margin in inches.
/// </summary>
public double MarginLeft
{
get => _marginLeft;
set
{
if (_marginLeft != value && value >= 0)
{
_marginLeft = value;
OnPropertyChanged(nameof(MarginLeft));
}
}
}
/// <summary>
/// Right margin in inches.
/// </summary>
public double MarginRight
{
get => _marginRight;
set
{
if (_marginRight != value && value >= 0)
{
_marginRight = value;
OnPropertyChanged(nameof(MarginRight));
}
}
}
/// <summary>
/// Whether to print background colors and images.
/// </summary>
public bool ShouldPrintBackgrounds
{
get => _shouldPrintBackgrounds;
set
{
if (_shouldPrintBackgrounds != value)
{
_shouldPrintBackgrounds = value;
OnPropertyChanged(nameof(ShouldPrintBackgrounds));
}
}
}
/// <summary>
/// Whether to print only the selected content.
/// </summary>
public bool ShouldPrintSelectionOnly
{
get => _shouldPrintSelectionOnly;
set
{
if (_shouldPrintSelectionOnly != value)
{
_shouldPrintSelectionOnly = value;
OnPropertyChanged(nameof(ShouldPrintSelectionOnly));
}
}
}
/// <summary>
/// Whether to print header and footer.
/// </summary>
public bool ShouldPrintHeaderAndFooter
{
get => _shouldPrintHeaderAndFooter;
set
{
if (_shouldPrintHeaderAndFooter != value)
{
_shouldPrintHeaderAndFooter = value;
OnPropertyChanged(nameof(ShouldPrintHeaderAndFooter));
}
}
}
/// <summary>
/// Title to display in the header.
/// </summary>
public string HeaderTitle
{
get => _headerTitle;
set
{
if (_headerTitle != value)
{
_headerTitle = value ?? string.Empty;
OnPropertyChanged(nameof(HeaderTitle));
}
}
}
/// <summary>
/// URI to display in the footer.
/// </summary>
public string FooterUri
{
get => _footerUri;
set
{
if (_footerUri != value)
{
_footerUri = value ?? string.Empty;
OnPropertyChanged(nameof(FooterUri));
}
}
}
/// <summary>
/// Scale factor for printing (0.1 to 2.0).
/// </summary>
public double ScaleFactor
{
get => _scaleFactor;
set
{
if (_scaleFactor != value && value >= 0.1 && value <= 2.0)
{
_scaleFactor = value;
OnPropertyChanged(nameof(ScaleFactor));
}
}
}
/// <summary>
/// Number of pages to print per sheet (1, 2, 4, 6, 9, 16).
/// </summary>
public int PagesPerSide
{
get => _pagesPerSide;
set
{
var validValues = new[] { 1, 2, 4, 6, 9, 16 };
if (_pagesPerSide != value && System.Array.IndexOf(validValues, value) >= 0)
{
_pagesPerSide = value;
OnPropertyChanged(nameof(PagesPerSide));
}
}
}
/// <summary>
/// Page ranges to print (e.g., "1-3,5,7-9").
/// </summary>
public string PageRanges
{
get => _pageRanges;
set
{
if (_pageRanges != value)
{
_pageRanges = value ?? string.Empty;
OnPropertyChanged(nameof(PageRanges));
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
@@ -9,4 +9,18 @@ public class CalendarBaseViewModel : CoreBaseViewModel, IRecipient<CalendarItemA
public void Receive(CalendarItemAdded message) => OnCalendarItemAdded(message.CalendarItem);
protected virtual void OnCalendarItemAdded(CalendarItem calendarItem) { }
protected override void RegisterRecipients()
{
base.RegisterRecipients();
Messenger.Register<CalendarItemAdded>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
Messenger.Unregister<CalendarItemAdded>(this);
}
}
+18 -2
View File
@@ -33,9 +33,15 @@ public class CoreBaseViewModel : ObservableRecipient,
}
}
public virtual void OnNavigatedTo(NavigationMode mode, object parameters) { IsActive = true; }
public virtual void OnNavigatedTo(NavigationMode mode, object parameters)
{
RegisterRecipients();
}
public virtual void OnNavigatedFrom(NavigationMode mode, object parameters) { IsActive = false; }
public virtual void OnNavigatedFrom(NavigationMode mode, object parameters)
{
UnregisterRecipients();
}
public virtual void OnPageLoaded() { }
@@ -44,6 +50,16 @@ public class CoreBaseViewModel : ObservableRecipient,
protected virtual void OnDispatcherAssigned() { }
/// <summary>
/// Register message recipients for this view model. Override to register specific message types.
/// </summary>
protected virtual void RegisterRecipients() { }
/// <summary>
/// Unregister message recipients for this view model. Override to unregister specific message types.
/// </summary>
protected virtual void UnregisterRecipients() { }
protected virtual void OnAccountCreated(MailAccount createdAccount) { }
protected virtual void OnAccountRemoved(MailAccount removedAccount) { }
protected virtual void OnAccountUpdated(MailAccount updatedAccount) { }
+14 -7
View File
@@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -27,6 +26,16 @@ public partial class BasePage : Page, IRecipient<LanguageChanged>
}
public virtual void OnLanguageChanged() { }
/// <summary>
/// Register message recipients for this page. Override to register specific message types.
/// </summary>
protected virtual void RegisterRecipients() { }
/// <summary>
/// Unregister message recipients for this page. Override to unregister specific message types.
/// </summary>
protected virtual void UnregisterRecipients() { }
}
public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
@@ -49,10 +58,7 @@ public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
private void PageLoaded(object sender, RoutedEventArgs e) => ViewModel.OnPageLoaded();
~BasePage()
{
Debug.WriteLine($"Disposed {GetType().Name}");
}
~BasePage() { Debug.WriteLine($"Disposed {GetType().Name}"); }
protected override void OnNavigatedTo(NavigationEventArgs e)
{
@@ -61,8 +67,8 @@ public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
WeakReferenceMessenger.Default.UnregisterAll(this);
WeakReferenceMessenger.Default.Register<LanguageChanged>(this);
RegisterRecipients();
ViewModel.OnNavigatedTo(mode, parameter);
}
@@ -74,7 +80,8 @@ public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
var mode = GetNavigationMode(e.NavigationMode);
var parameter = e.Parameter;
WeakReferenceMessenger.Default.UnregisterAll(this);
WeakReferenceMessenger.Default.Unregister<LanguageChanged>(this);
UnregisterRecipients();
ViewModel.OnNavigatedFrom(mode, parameter);
+48 -214
View File
@@ -1,220 +1,54 @@
<ContentDialog x:Class="Wino.Core.WinUI.Dialogs.PrintDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Print Settings"
PrimaryButtonText="Print"
SecondaryButtonText="Cancel"
DefaultButton="Primary"
MinWidth="600"
MinHeight="500"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
SecondaryButtonClick="ContentDialog_SecondaryButtonClick">
<ContentDialog
x:Class="Wino.Core.WinUI.Dialogs.PrintDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:printing="using:Wino.Core.Domain.Models.Printing"
Title="Print Settings"
MinWidth="400"
MinHeight="300"
DefaultButton="Primary"
Loaded="PrintDialog_Loaded"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
PrimaryButtonText="Print"
SecondaryButtonClick="ContentDialog_SecondaryButtonClick"
SecondaryButtonText="Cancel"
Style="{StaticResource WinoDialogStyle}"
mc:Ignorable="d">
<ScrollViewer>
<StackPanel Spacing="16" Margin="20">
<!-- Printer Selection -->
<StackPanel Spacing="8">
<TextBlock Text="Printer" FontWeight="SemiBold" />
<ComboBox x:Name="PrinterComboBox"
ItemsSource="{x:Bind ViewModel.AvailablePrinters, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.PrintSettings.PrinterName, Mode=TwoWay}"
Header="Select Printer"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Margin="20" Spacing="16">
<!-- Print Options Grid -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Printer Selection -->
<ComboBox
x:Name="PrinterComboBox"
HorizontalAlignment="Stretch"
Header="Printer"
SelectedItem="{x:Bind PrintSettings.PrinterName, Mode=TwoWay}"
SelectionChanged="PrinterComboBox_SelectionChanged" />
<!-- Left Column -->
<StackPanel Grid.Column="0" Spacing="16">
<!-- Copies -->
<StackPanel Spacing="8">
<TextBlock Text="Copies" FontWeight="SemiBold" />
<NumberBox Value="{x:Bind ViewModel.PrintSettings.Copies, Mode=TwoWay}"
Minimum="1" Maximum="999"
SpinButtonPlacementMode="Inline" />
</StackPanel>
<!-- Copies -->
<NumberBox
Header="Copies"
Maximum="999"
Minimum="1"
SpinButtonPlacementMode="Inline"
Value="{x:Bind PrintSettings.Copies, Mode=TwoWay}" />
<!-- Orientation -->
<StackPanel Spacing="8">
<TextBlock Text="Orientation" FontWeight="SemiBold" />
<RadioButtons SelectedIndex="{x:Bind ViewModel.OrientationIndex, Mode=TwoWay}">
<RadioButton Content="Portrait" />
<RadioButton Content="Landscape" />
</RadioButtons>
</StackPanel>
<!-- Color Mode -->
<StackPanel Spacing="8">
<TextBlock Text="Color" FontWeight="SemiBold" />
<RadioButtons SelectedIndex="{x:Bind ViewModel.ColorModeIndex, Mode=TwoWay}">
<RadioButton Content="Default" />
<RadioButton Content="Color" />
<RadioButton Content="Grayscale" />
</RadioButtons>
</StackPanel>
<!-- Media Size -->
<StackPanel Spacing="8">
<TextBlock Text="Paper Size" FontWeight="SemiBold" />
<ComboBox SelectedIndex="{x:Bind ViewModel.MediaSizeIndex, Mode=TwoWay}"
HorizontalAlignment="Stretch">
<ComboBoxItem Content="Default" />
<ComboBoxItem Content="Letter (8.5 x 11 in)" />
<ComboBoxItem Content="Legal (8.5 x 14 in)" />
<ComboBoxItem Content="A4 (210 x 297 mm)" />
<ComboBoxItem Content="A3 (297 x 420 mm)" />
<ComboBoxItem Content="A5 (148 x 210 mm)" />
<ComboBoxItem Content="Tabloid (11 x 17 in)" />
<ComboBoxItem Content="Executive (7.25 x 10.5 in)" />
<ComboBoxItem Content="B4 (250 x 353 mm)" />
<ComboBoxItem Content="B5 (176 x 250 mm)" />
</ComboBox>
</StackPanel>
</StackPanel>
<!-- Right Column -->
<StackPanel Grid.Column="2" Spacing="16">
<!-- Duplex -->
<StackPanel Spacing="8">
<TextBlock Text="Duplex" FontWeight="SemiBold" />
<RadioButtons SelectedIndex="{x:Bind ViewModel.DuplexIndex, Mode=TwoWay}">
<RadioButton Content="Default" />
<RadioButton Content="Single-sided" />
<RadioButton Content="Double-sided (Short Edge)" />
<RadioButton Content="Double-sided (Long Edge)" />
</RadioButtons>
</StackPanel>
<!-- Collation -->
<StackPanel Spacing="8">
<TextBlock Text="Collation" FontWeight="SemiBold" />
<RadioButtons SelectedIndex="{x:Bind ViewModel.CollationIndex, Mode=TwoWay}">
<RadioButton Content="Default" />
<RadioButton Content="Collated" />
<RadioButton Content="Uncollated" />
</RadioButtons>
</StackPanel>
<!-- Pages Per Side -->
<StackPanel Spacing="8">
<TextBlock Text="Pages Per Side" FontWeight="SemiBold" />
<ComboBox SelectedIndex="{x:Bind ViewModel.PagesPerSideIndex, Mode=TwoWay}"
HorizontalAlignment="Stretch">
<ComboBoxItem Content="1" />
<ComboBoxItem Content="2" />
<ComboBoxItem Content="4" />
<ComboBoxItem Content="6" />
<ComboBoxItem Content="9" />
<ComboBoxItem Content="16" />
</ComboBox>
</StackPanel>
</StackPanel>
</Grid>
<!-- Page Range -->
<StackPanel Spacing="8">
<TextBlock Text="Page Range" FontWeight="SemiBold" />
<RadioButtons SelectedIndex="{x:Bind ViewModel.PageRangeOptionIndex, Mode=TwoWay}">
<RadioButton Content="All pages" />
<RadioButton Content="Custom range" />
</RadioButtons>
<TextBox x:Name="PageRangeTextBox"
Text="{x:Bind ViewModel.PrintSettings.PageRanges, Mode=TwoWay}"
PlaceholderText="e.g., 1-3,5,7-9"
IsEnabled="{x:Bind ViewModel.IsCustomPageRange, Mode=OneWay}"
Margin="0,8,0,0" />
</StackPanel>
<!-- Scale Factor -->
<StackPanel Spacing="8">
<TextBlock Text="Scale" FontWeight="SemiBold" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<Slider x:Name="ScaleSlider"
Value="{x:Bind ViewModel.PrintSettings.ScaleFactor, Mode=TwoWay}"
Minimum="0.1" Maximum="2.0" StepFrequency="0.1"
Grid.Column="0" />
<TextBlock Text="{x:Bind ViewModel.ScalePercentageText, Mode=OneWay}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Column="1" />
</Grid>
</StackPanel>
<!-- Margins -->
<StackPanel Spacing="8">
<TextBlock Text="Margins (inches)" FontWeight="SemiBold" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Top" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Center" />
<TextBlock Text="Bottom" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Center" />
<TextBlock Text="Left" Grid.Column="2" Grid.Row="0" HorizontalAlignment="Center" />
<TextBlock Text="Right" Grid.Column="3" Grid.Row="0" HorizontalAlignment="Center" />
<NumberBox Value="{x:Bind ViewModel.PrintSettings.MarginTop, Mode=TwoWay}"
Minimum="0" Maximum="10" StepFrequency="0.1"
Grid.Column="0" Grid.Row="1" Margin="4" />
<NumberBox Value="{x:Bind ViewModel.PrintSettings.MarginBottom, Mode=TwoWay}"
Minimum="0" Maximum="10" StepFrequency="0.1"
Grid.Column="1" Grid.Row="1" Margin="4" />
<NumberBox Value="{x:Bind ViewModel.PrintSettings.MarginLeft, Mode=TwoWay}"
Minimum="0" Maximum="10" StepFrequency="0.1"
Grid.Column="2" Grid.Row="1" Margin="4" />
<NumberBox Value="{x:Bind ViewModel.PrintSettings.MarginRight, Mode=TwoWay}"
Minimum="0" Maximum="10" StepFrequency="0.1"
Grid.Column="3" Grid.Row="1" Margin="4" />
</Grid>
</StackPanel>
<!-- Print Options -->
<StackPanel Spacing="8">
<TextBlock Text="Options" FontWeight="SemiBold" />
<CheckBox Content="Print backgrounds"
IsChecked="{x:Bind ViewModel.PrintSettings.ShouldPrintBackgrounds, Mode=TwoWay}" />
<CheckBox Content="Print selection only"
IsChecked="{x:Bind ViewModel.PrintSettings.ShouldPrintSelectionOnly, Mode=TwoWay}" />
<CheckBox Content="Print headers and footers"
IsChecked="{x:Bind ViewModel.PrintSettings.ShouldPrintHeaderAndFooter, Mode=TwoWay}" />
</StackPanel>
<!-- Header and Footer -->
<StackPanel Spacing="8" Visibility="{x:Bind ViewModel.PrintSettings.ShouldPrintHeaderAndFooter, Mode=OneWay}">
<TextBlock Text="Header & Footer" FontWeight="SemiBold" />
<TextBox Header="Header Title"
Text="{x:Bind ViewModel.PrintSettings.HeaderTitle, Mode=TwoWay}"
PlaceholderText="Document title" />
<TextBox Header="Footer URI"
Text="{x:Bind ViewModel.PrintSettings.FooterUri, Mode=TwoWay}"
PlaceholderText="Document URL" />
</StackPanel>
<!-- Orientation -->
<RadioButtons
x:Name="OrientationRadioButtons"
Header="Orientation"
SelectionChanged="OrientationRadio_SelectionChanged">
<RadioButton Content="Portrait" />
<RadioButton Content="Landscape" />
</RadioButtons>
<!-- Print Options -->
<StackPanel Spacing="8">
<TextBlock Text="Options" />
<CheckBox Content="Print backgrounds" IsChecked="{x:Bind PrintSettings.ShouldPrintBackgrounds, Mode=TwoWay}" />
<CheckBox Content="Print headers and footers" IsChecked="{x:Bind PrintSettings.ShouldPrintHeaderAndFooter, Mode=TwoWay}" />
</StackPanel>
</ScrollViewer>
</ContentDialog>
</StackPanel>
</ContentDialog>
+114 -47
View File
@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System.Drawing.Printing;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Serilog;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Printing;
using Wino.Core.WinUI.Models;
namespace Wino.Core.WinUI.Dialogs;
@@ -10,32 +14,40 @@ namespace Wino.Core.WinUI.Dialogs;
/// </summary>
public sealed partial class PrintDialog : ContentDialog
{
/// <summary>
/// The ViewModel that handles the dialog's data binding and logic.
/// </summary>
public PrintDialogViewModel ViewModel { get; }
/// <summary>
/// Gets the configured print settings from the dialog.
/// </summary>
public WebView2PrintSettingsModel PrintSettings => ViewModel.PrintSettings;
public WebView2PrintSettingsModel PrintSettings { get; set; } = new WebView2PrintSettingsModel();
public PrintDialog()
{
this.InitializeComponent();
ViewModel = new PrintDialogViewModel();
ViewModel.Initialize();
}
/// <summary>
/// Initializes the dialog with existing print settings.
/// </summary>
/// <param name="printSettings">The initial print settings to load.</param>
public PrintDialog(WebView2PrintSettingsModel printSettings)
public PrintDialog(WebView2PrintSettingsModel printSettings = null)
{
if (printSettings != null) PrintSettings = printSettings;
this.InitializeComponent();
ViewModel = new PrintDialogViewModel();
ViewModel.Initialize(printSettings);
}
private void PrintDialog_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) => LoadSettingsToUI(PrintSettings);
private void OrientationRadio_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is RadioButtons radioButtons)
{
PrintSettings.Orientation = (PrintOrientation)radioButtons.SelectedIndex;
}
}
private void PrinterComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ComboBox comboBox && comboBox.SelectedItem != null)
{
PrintSettings.PrinterName = comboBox.SelectedItem.ToString();
}
}
/// <summary>
@@ -44,7 +56,85 @@ public sealed partial class PrintDialog : ContentDialog
/// <param name="printers">List of available printer names.</param>
public void SetAvailablePrinters(IEnumerable<string> printers)
{
ViewModel.SetAvailablePrinters(printers);
var printerList = printers?.ToList() ?? new List<string>();
if (this.FindName("PrinterComboBox") is ComboBox printerComboBox)
{
printerComboBox.ItemsSource = printerList;
if (printerList.Any())
{
// Set to first printer or to the one in settings
var targetPrinter = !string.IsNullOrEmpty(PrintSettings.PrinterName)
? PrintSettings.PrinterName
: printerList.First();
var index = printerList.IndexOf(targetPrinter);
printerComboBox.SelectedIndex = index >= 0 ? index : 0;
// Update the settings model with the selected printer
PrintSettings.PrinterName = printerComboBox.SelectedItem?.ToString() ?? string.Empty;
}
}
}
/// <summary>
/// Loads available printers asynchronously and sets them in the dialog.
/// </summary>
public async Task LoadAvailablePrintersAsync()
{
try
{
var printers = await Task.Run(() =>
{
var printerList = new List<string>();
// Get all installed printers using System.Drawing.Printing
foreach (string printerName in PrinterSettings.InstalledPrinters)
{
printerList.Add(printerName);
}
return printerList.AsEnumerable();
});
SetAvailablePrinters(printers);
}
catch (System.Exception ex)
{
// Log the exception if logging is available
Log.Error(ex, "Error getting available printers");
// Set empty list if printer discovery fails
SetAvailablePrinters(Enumerable.Empty<string>());
}
}
private void LoadSettingsToUI(WebView2PrintSettingsModel settings)
{
if (settings == null) return;
// Only handle orientation manually since other properties are bound via x:Bind
if (this.FindName("OrientationRadioButtons") is RadioButtons orientationRadio)
{
orientationRadio.SelectedIndex = (int)settings.Orientation;
}
}
private void UpdateSettingsFromUI()
{
// Most properties are bound via x:Bind, only handle orientation manually
if (this.FindName("OrientationRadioButtons") is RadioButtons orientationRadio)
{
PrintSettings.Orientation = (PrintOrientation)orientationRadio.SelectedIndex;
}
// Also update printer name from ComboBox since it uses ItemsSource binding
if (this.FindName("PrinterComboBox") is ComboBox printerComboBox &&
printerComboBox.SelectedItem != null)
{
PrintSettings.PrinterName = printerComboBox.SelectedItem.ToString();
}
}
/// <summary>
@@ -53,54 +143,31 @@ public sealed partial class PrintDialog : ContentDialog
/// <returns>True if settings are valid, false otherwise.</returns>
private bool ValidateSettings()
{
// Validate printer selection
if (string.IsNullOrWhiteSpace(PrintSettings.PrinterName))
// Check if a printer is selected
if (this.FindName("PrinterComboBox") is ComboBox printerComboBox &&
printerComboBox.SelectedItem == null)
{
// Show error message or set focus to printer selection
return false;
}
// Validate copies
// Copies validation is handled by the bound property with validation in the model
if (PrintSettings.Copies <= 0)
{
return false;
}
// Validate page ranges if custom range is specified
if (ViewModel.IsCustomPageRange && !string.IsNullOrWhiteSpace(PrintSettings.PageRanges))
{
// Basic validation for page ranges format
// More comprehensive validation could be added here
var pageRanges = PrintSettings.PageRanges.Trim();
if (string.IsNullOrEmpty(pageRanges))
{
return false;
}
}
// Validate margins
if (PrintSettings.MarginTop < 0 || PrintSettings.MarginBottom < 0 ||
PrintSettings.MarginLeft < 0 || PrintSettings.MarginRight < 0)
{
return false;
}
// Validate scale factor
if (PrintSettings.ScaleFactor < 0.1 || PrintSettings.ScaleFactor > 2.0)
{
return false;
}
return true;
}
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
// Update settings from UI before validation
UpdateSettingsFromUI();
// Validate settings before closing
if (!ValidateSettings())
{
args.Cancel = true;
// Could show error message here
}
}
@@ -108,4 +175,4 @@ public sealed partial class PrintDialog : ContentDialog
{
// Cancel was clicked, no validation needed
}
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ public static class XamlHelpers
public static Visibility ReverseBoolToVisibilityConverter(bool value) => value ? Visibility.Collapsed : Visibility.Visible;
public static Visibility ReverseVisibilityConverter(Visibility visibility) => visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
public static bool ReverseBoolConverter(bool value) => !value;
public static bool ShouldDisplayPreview(string text) => text.Any(x => char.IsLetter(x));
public static bool ShouldDisplayPreview(string text) => text == null ? false : text.Any(x => char.IsLetter(x));
public static bool CountToBooleanConverter(int value) => value > 0;
public static bool ObjectEquals(object obj1, object obj2) => object.Equals(obj1, obj2);
public static Visibility CountToVisibilityConverter(int value) => value > 0 ? Visibility.Visible : Visibility.Collapsed;
@@ -1,30 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Wino.Core.Domain.Models.Printing;
namespace Wino.Core.WinUI.Interfaces;
/// <summary>
/// Service interface for displaying the custom print dialog and managing print settings.
/// </summary>
public interface IPrintDialogService
{
/// <summary>
/// Shows the print dialog and returns the configured print settings.
/// </summary>
/// <param name="initialSettings">Initial print settings to populate the dialog with. If null, default settings will be used.</param>
/// <param name="availablePrinters">List of available printers to show in the dialog. If null or empty, the service should attempt to discover printers.</param>
/// <returns>
/// A task that resolves to the configured WebView2PrintSettingsModel if the user clicked Print,
/// or null if the user cancelled the dialog.
/// </returns>
Task<WebView2PrintSettingsModel> ShowPrintDialogAsync(
WebView2PrintSettingsModel initialSettings = null,
IEnumerable<string> availablePrinters = null);
/// <summary>
/// Gets the list of available printers on the system.
/// </summary>
/// <returns>A task that resolves to a list of available printer names.</returns>
Task<IEnumerable<string>> GetAvailablePrintersAsync();
}
@@ -14,6 +14,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.Common;
using Wino.Core.Domain.Models.Printing;
using Wino.Core.WinUI.Dialogs;
using Wino.Core.WinUI.Extensions;
using Wino.Dialogs;
@@ -294,4 +295,38 @@ public class DialogServiceBase : IDialogServiceBase
return dialog.Result;
}
public async Task<WebView2PrintSettingsModel> ShowPrintDialogAsync(WebView2PrintSettingsModel initialSettings = null)
{
try
{
// Create the print dialog
var dialog = initialSettings != null
? new PrintDialog(initialSettings)
: new PrintDialog();
// Set the XamlRoot for proper display
dialog.XamlRoot = GetXamlRoot();
// Get available printers asynchronously when the dialog is loaded
dialog.Loaded += async (sender, e) =>
{
await dialog.LoadAvailablePrintersAsync();
};
// Show the dialog
var result = await HandleDialogPresentationAsync(dialog);
// Return the settings if user clicked Print, otherwise null
return result == ContentDialogResult.Primary
? dialog.PrintSettings
: null;
}
catch (Exception ex)
{
// Log the exception if logging is available
Log.Error(ex, "Error showing print dialog");
return null;
}
}
}
@@ -1,93 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing.Printing;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Wino.Core.Domain.Models.Printing;
using Wino.Core.WinUI.Dialogs;
using Wino.Core.WinUI.Interfaces;
namespace Wino.Core.WinUI.Services;
/// <summary>
/// Service implementation for displaying the custom print dialog and managing print settings.
/// </summary>
public class PrintDialogService : IPrintDialogService
{
/// <summary>
/// Shows the print dialog and returns the configured print settings.
/// </summary>
/// <param name="initialSettings">Initial print settings to populate the dialog with. If null, default settings will be used.</param>
/// <param name="availablePrinters">List of available printers to show in the dialog. If null or empty, the service will discover printers.</param>
/// <returns>
/// A task that resolves to the configured WebView2PrintSettingsModel if the user clicked Print,
/// or null if the user cancelled the dialog.
/// </returns>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
Justification = "GetProperty is used for backward compatibility and gracefully handles failures")]
public async Task<WebView2PrintSettingsModel> ShowPrintDialogAsync(
WebView2PrintSettingsModel initialSettings = null,
IEnumerable<string> availablePrinters = null)
{
try
{
// Note: XamlRoot will be set by the calling code when showing the dialog
// Create the print dialog
var dialog = initialSettings != null
? new PrintDialog(initialSettings)
: new PrintDialog();
// The XamlRoot will be set by the calling code when showing the dialog
// Get available printers if not provided
var printers = availablePrinters ?? await GetAvailablePrintersAsync();
dialog.SetAvailablePrinters(printers);
// Show the dialog
var result = await dialog.ShowAsync();
// Return the settings if user clicked Print, otherwise null
return result == Microsoft.UI.Xaml.Controls.ContentDialogResult.Primary
? dialog.PrintSettings
: null;
}
catch (Exception ex)
{
// Log the exception if logging is available
System.Diagnostics.Debug.WriteLine($"Error showing print dialog: {ex.Message}");
return null;
}
}
/// <summary>
/// Gets the list of available printers on the system.
/// </summary>
/// <returns>A task that resolves to a list of available printer names.</returns>
public async Task<IEnumerable<string>> GetAvailablePrintersAsync()
{
return await Task.Run(() =>
{
try
{
var printers = new List<string>();
// Get all installed printers using System.Drawing.Printing
foreach (string printerName in PrinterSettings.InstalledPrinters)
{
printers.Add(printerName);
}
return printers.AsEnumerable();
}
catch (Exception ex)
{
// Log the exception if logging is available
System.Diagnostics.Debug.WriteLine($"Error getting available printers: {ex.Message}");
return Enumerable.Empty<string>();
}
});
}
}
@@ -1,6 +1,6 @@
using Microsoft.UI.Xaml;
using Wino.Core.WinUI;
using Wino.Core.ViewModels;
using Wino.Core.WinUI;
namespace Wino.Views.Abstract;
+14 -6
View File
@@ -26,9 +26,6 @@ public sealed partial class SettingsPage : SettingsPageAbstract, IRecipient<Brea
{
base.OnNavigatedTo(e);
// Re-register the breadcrumb navigation handler after base.OnNavigatedTo unregisters all handlers
WeakReferenceMessenger.Default.Register<BreadcrumbNavigationRequested>(this);
SettingsFrame.Navigate(typeof(SettingOptionsPage), null, new SuppressNavigationTransitionInfo());
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuSettings, WinoPage.SettingOptionsPage);
@@ -63,12 +60,23 @@ public sealed partial class SettingsPage : SettingsPageAbstract, IRecipient<Brea
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
// Explicitly unregister our message handlers before base.OnNavigatingFrom calls UnregisterAll
WeakReferenceMessenger.Default.Unregister<BreadcrumbNavigationRequested>(this);
base.OnNavigatingFrom(e);
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<BreadcrumbNavigationRequested>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<BreadcrumbNavigationRequested>(this);
}
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
{
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
+32
View File
@@ -1037,4 +1037,36 @@ public partial class AppShellViewModel : MailBaseViewModel,
{
await MenuItemInvokedOrSelectedAsync(SettingsItem, WinoPage.AppPreferencesPage);
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
Messenger.Register<NavigateManageAccountsRequested>(this);
Messenger.Register<MailtoProtocolMessageRequested>(this);
Messenger.Register<RefreshUnreadCountsMessage>(this);
Messenger.Register<AccountsMenuRefreshRequested>(this);
Messenger.Register<MergedInboxRenamed>(this);
Messenger.Register<LanguageChanged>(this);
Messenger.Register<AccountMenuItemsReordered>(this);
Messenger.Register<AccountSynchronizationProgressUpdatedMessage>(this);
Messenger.Register<NavigateAppPreferencesRequested>(this);
Messenger.Register<AccountFolderConfigurationUpdated>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
Messenger.Unregister<NavigateManageAccountsRequested>(this);
Messenger.Unregister<MailtoProtocolMessageRequested>(this);
Messenger.Unregister<RefreshUnreadCountsMessage>(this);
Messenger.Unregister<AccountsMenuRefreshRequested>(this);
Messenger.Unregister<MergedInboxRenamed>(this);
Messenger.Unregister<LanguageChanged>(this);
Messenger.Unregister<AccountMenuItemsReordered>(this);
Messenger.Unregister<AccountSynchronizationProgressUpdatedMessage>(this);
Messenger.Unregister<NavigateAppPreferencesRequested>(this);
Messenger.Unregister<AccountFolderConfigurationUpdated>(this);
}
}
+30
View File
@@ -39,4 +39,34 @@ public class MailBaseViewModel : CoreBaseViewModel,
void IRecipient<FolderRenamed>.Receive(FolderRenamed message) => OnFolderRenamed(message.MailItemFolder);
void IRecipient<FolderSynchronizationEnabled>.Receive(FolderSynchronizationEnabled message) => OnFolderSynchronizationEnabled(message.MailItemFolder);
protected override void RegisterRecipients()
{
base.RegisterRecipients();
Messenger.Register<MailAddedMessage>(this);
Messenger.Register<MailRemovedMessage>(this);
Messenger.Register<MailUpdatedMessage>(this);
Messenger.Register<MailDownloadedMessage>(this);
Messenger.Register<DraftCreated>(this);
Messenger.Register<DraftFailed>(this);
Messenger.Register<DraftMapped>(this);
Messenger.Register<FolderRenamed>(this);
Messenger.Register<FolderSynchronizationEnabled>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
Messenger.Unregister<MailAddedMessage>(this);
Messenger.Unregister<MailRemovedMessage>(this);
Messenger.Unregister<MailUpdatedMessage>(this);
Messenger.Unregister<MailDownloadedMessage>(this);
Messenger.Unregister<DraftCreated>(this);
Messenger.Unregister<DraftFailed>(this);
Messenger.Unregister<DraftMapped>(this);
Messenger.Unregister<FolderRenamed>(this);
Messenger.Unregister<FolderSynchronizationEnabled>(this);
}
}
@@ -1081,4 +1081,34 @@ public partial class MailListPageViewModel : MailBaseViewModel,
{
// MailCollection.UpdateThumbnails(message.Email);
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
Messenger.Register<MailItemNavigationRequested>(this);
Messenger.Register<ActiveMailFolderChangedEvent>(this);
Messenger.Register<MailItemSelectedEvent>(this);
Messenger.Register<MailItemSelectionRemovedEvent>(this);
Messenger.Register<AccountSynchronizationCompleted>(this);
Messenger.Register<NewMailSynchronizationRequested>(this);
Messenger.Register<AccountSynchronizerStateChanged>(this);
Messenger.Register<AccountCacheResetMessage>(this);
Messenger.Register<ThumbnailAdded>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
Messenger.Unregister<MailItemNavigationRequested>(this);
Messenger.Unregister<ActiveMailFolderChangedEvent>(this);
Messenger.Unregister<MailItemSelectedEvent>(this);
Messenger.Unregister<MailItemSelectionRemovedEvent>(this);
Messenger.Unregister<AccountSynchronizationCompleted>(this);
Messenger.Unregister<NewMailSynchronizationRequested>(this);
Messenger.Unregister<AccountSynchronizerStateChanged>(this);
Messenger.Unregister<AccountCacheResetMessage>(this);
Messenger.Unregister<ThumbnailAdded>(this);
}
}
@@ -20,6 +20,7 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Menus;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Printing;
using Wino.Core.Domain.Models.Reader;
using Wino.Core.Services;
using Wino.Mail.ViewModels.Data;
@@ -55,7 +56,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
// Used in 'Save as' and 'Print' functionality.
public Func<string, Task<bool>> SaveHTMLasPDFFunc { get; set; }
public Action ShowPrintUIAction { get; set; }
public Func<WebView2PrintSettingsModel, Task<PrintingResult>> DirectPrintFuncAsync { get; set; }
#region Properties
@@ -257,7 +258,22 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
}
else if (operation == MailOperation.Print)
{
ShowPrintUI();
var settings = await _dialogService.ShowPrintDialogAsync();
if (settings == null) return;
var printingResult = await DirectPrintFuncAsync.Invoke(settings);
// TODO: More detailed printing result handling.
if (printingResult == PrintingResult.Submitted)
{
_dialogService.InfoBarMessage(Translator.DialogMessage_PrintingSuccessTitle, Translator.DialogMessage_PrintingSuccessMessage, InfoBarMessageType.Success);
}
else if (printingResult == PrintingResult.Failed)
{
_dialogService.InfoBarMessage(Translator.DialogMessage_PrintingFailedTitle, Translator.DialogMessage_PrintingFailedMessage, InfoBarMessageType.Error);
}
}
else if (operation == MailOperation.ViewMessageSource)
{
@@ -682,19 +698,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
}
}
private void ShowPrintUI()
{
try
{
ShowPrintUIAction?.Invoke();
}
catch (Exception exception)
{
Log.Error(exception, "Failed to print mail.");
_dialogService.InfoBarMessage(string.Empty, exception.Message, InfoBarMessageType.Error);
}
}
private async Task SaveAsAsync()
{
try
@@ -793,4 +796,20 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
}
});
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
Messenger.Register<NewMailItemRenderingRequestedEvent>(this);
Messenger.Register<ThumbnailAdded>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
Messenger.Unregister<NewMailItemRenderingRequestedEvent>(this);
Messenger.Unregister<ThumbnailAdded>(this);
}
}
+8 -3
View File
@@ -15,7 +15,7 @@ namespace Wino.Mail.WinUI;
public partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
{
private ISynchronizationManager _synchronizationManager;
private ISynchronizationManager? _synchronizationManager;
public App()
{
@@ -23,7 +23,7 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
WeakReferenceMessenger.Default.Register<NewMailSynchronizationRequested>(this);
RegisterRecipients();
}
#region Dependency Injection
@@ -114,8 +114,13 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
MainWindow.Activate();
}
private void RegisterRecipients()
{
WeakReferenceMessenger.Default.Register<NewMailSynchronizationRequested>(this);
}
public void Receive(NewMailSynchronizationRequested message)
{
_synchronizationManager.SynchronizeMailAsync(message.Options);
_synchronizationManager?.SynchronizeMailAsync(message.Options);
}
}
+47 -11
View File
@@ -9,6 +9,7 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
@@ -20,6 +21,7 @@ using Wino.Core.Domain.Models.Navigation;
using Wino.Core.WinUI;
using Wino.Core.WinUI.Controls;
using Wino.Extensions;
using Wino.Mail.ViewModels.Data;
using Wino.MenuFlyouts;
using Wino.MenuFlyouts.Context;
using Wino.Messaging.Client.Accounts;
@@ -37,13 +39,27 @@ public sealed partial class AppShell : AppShellAbstract,
IRecipient<InfoBarMessageRequested>
{
[GeneratedDependencyProperty]
public partial UIElement TopShellContent { get; set; }
public partial UIElement? TopShellContent { get; set; }
public AppShell() : base()
{
InitializeComponent();
}
public Frame GetShellFrame() => ShellFrame;
//protected override void OnNavigatedTo(NavigationEventArgs e)
//{
// base.OnNavigatedTo(e);
// WeakReferenceMessenger.Default.Register<InfoBarMessageRequested>(this);
// WeakReferenceMessenger.Default.Register<AccountMenuItemExtended>(this);
// WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this);
// WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this);
//}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
}
private async void ItemDroppedOnFolder(object sender, DragEventArgs e)
{
@@ -66,14 +82,14 @@ public sealed partial class AppShell : AppShellAbstract,
foreach (var item in dragPackage.DraggingMails)
{
//if (item is MailItemViewModel singleMailItemViewModel)
//{
// mailCopies.Add(singleMailItemViewModel.MailCopy);
//}
//else if (item is ThreadMailItemViewModel threadViewModel)
//{
// mailCopies.AddRange(threadViewModel.GetMailCopies());
//}
if (item is MailItemViewModel singleMailItemViewModel)
{
mailCopies.Add(singleMailItemViewModel.MailCopy);
}
else if (item is ThreadMailItemViewModel threadViewModel)
{
mailCopies.AddRange(threadViewModel.ThreadEmails.Select(a => a.MailCopy));
}
}
await ViewModel.PerformMoveOperationAsync(mailCopies, draggingFolder);
@@ -114,7 +130,7 @@ public sealed partial class AppShell : AppShellAbstract,
// Check whether the moving item's account has at least one same as the target folder's account.
var draggedAccountIds = folderMenuItem.HandlingFolders.Select(a => a.MailAccountId);
if (!dragPackage.DraggingMails.Any(a => draggedAccountIds.Contains(a.AssignedAccount.Id))) return false;
if (!dragPackage.DraggingMails.Cast<MailCopy>().Any(a => draggedAccountIds.Contains(a.AssignedAccount.Id))) return false;
return true;
}
@@ -320,4 +336,24 @@ public sealed partial class AppShell : AppShellAbstract,
ShellFrame.Margin = new Thickness(0);
}
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<InfoBarMessageRequested>(this);
WeakReferenceMessenger.Default.Register<AccountMenuItemExtended>(this);
WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<InfoBarMessageRequested>(this);
WeakReferenceMessenger.Default.Unregister<AccountMenuItemExtended>(this);
WeakReferenceMessenger.Default.Unregister<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Unregister<NavigateMailFolderEvent>(this);
}
}
@@ -103,9 +103,19 @@ public sealed partial class NewImapSetupDialog : ContentDialog,
public void StartImapConnectionSetup(MailAccount account) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), account, new DrillInNavigationTransitionInfo());
public void StartImapConnectionSetup(AccountCreationDialogResult accountCreationDialogResult) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), accountCreationDialogResult, new DrillInNavigationTransitionInfo());
private void ImapSetupDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) => WeakReferenceMessenger.Default.UnregisterAll(this);
private void ImapSetupDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
WeakReferenceMessenger.Default.Unregister<ImapSetupNavigationRequested>(this);
WeakReferenceMessenger.Default.Unregister<ImapSetupBackNavigationRequested>(this);
WeakReferenceMessenger.Default.Unregister<ImapSetupDismissRequested>(this);
}
private void ImapSetupDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => WeakReferenceMessenger.Default.RegisterAll(this);
private void ImapSetupDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
WeakReferenceMessenger.Default.Register<ImapSetupNavigationRequested>(this);
WeakReferenceMessenger.Default.Register<ImapSetupBackNavigationRequested>(this);
WeakReferenceMessenger.Default.Register<ImapSetupDismissRequested>(this);
}
// Don't hide the dialog unless dismiss is requested from the inner pages specifically.
private void OnDialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args) => args.Cancel = !isDismissRequested;
+16 -3
View File
@@ -15,7 +15,7 @@ using WinUIEx;
namespace Wino.Mail.WinUI;
public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient<ApplicationThemeChanged>
public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient<ApplicationThemeChanged>, IRecipient<TitleBarShellContentUpdated>
{
public IStatePersistanceService StatePersistanceService { get; } = WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception("StatePersistanceService not registered in DI container.");
public IPreferencesService PreferencesService { get; } = WinoApplication.Current.Services.GetService<IPreferencesService>() ?? throw new Exception("PreferencesService not registered in DI container.");
@@ -23,8 +23,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
public ShellWindow()
{
WeakReferenceMessenger.Default.Register<TitleBarShellContentUpdated>(this);
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
RegisterRecipients();
InitializeComponent();
@@ -177,10 +176,24 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
// Clean up system tray
_systemTrayService?.Dispose();
UnregisterRecipients();
// Close the window
this.Close();
// Exit the application
Application.Current.Exit();
}
private void RegisterRecipients()
{
WeakReferenceMessenger.Default.Register<TitleBarShellContentUpdated>(this);
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
}
private void UnregisterRecipients()
{
WeakReferenceMessenger.Default.Unregister<TitleBarShellContentUpdated>(this);
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
}
}
+16 -10
View File
@@ -57,22 +57,12 @@ public sealed partial class MailListPage : MailListPageAbstract,
{
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
}
WeakReferenceMessenger.Default.Register<ClearMailSelectionsRequested>(this);
WeakReferenceMessenger.Default.Register<ActiveMailItemChangedEvent>(this);
WeakReferenceMessenger.Default.Register<SelectMailItemContainerEvent>(this);
WeakReferenceMessenger.Default.Register<DisposeRenderingFrameRequested>(this);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
WeakReferenceMessenger.Default.Unregister<ClearMailSelectionsRequested>(this);
WeakReferenceMessenger.Default.Unregister<ActiveMailItemChangedEvent>(this);
WeakReferenceMessenger.Default.Unregister<SelectMailItemContainerEvent>(this);
WeakReferenceMessenger.Default.Unregister<DisposeRenderingFrameRequested>(this);
// Dispose all WinoListView items.
// MailListView.Dispose();
@@ -455,6 +445,22 @@ public sealed partial class MailListPage : MailListPageAbstract,
ViewModel.NavigationService.Navigate(WinoPage.IdlePage, null, NavigationReferenceFrame.RenderingFrame, NavigationTransitionType.DrillIn);
}
protected override void RegisterRecipients()
{
WeakReferenceMessenger.Default.Register<ClearMailSelectionsRequested>(this);
WeakReferenceMessenger.Default.Register<ActiveMailItemChangedEvent>(this);
WeakReferenceMessenger.Default.Register<SelectMailItemContainerEvent>(this);
WeakReferenceMessenger.Default.Register<DisposeRenderingFrameRequested>(this);
}
protected override void UnregisterRecipients()
{
WeakReferenceMessenger.Default.Unregister<ClearMailSelectionsRequested>(this);
WeakReferenceMessenger.Default.Unregister<ActiveMailItemChangedEvent>(this);
WeakReferenceMessenger.Default.Unregister<SelectMailItemContainerEvent>(this);
WeakReferenceMessenger.Default.Unregister<DisposeRenderingFrameRequested>(this);
}
private void PageSizeChanged(object sender, SizeChangedEventArgs e)
{
ViewModel.MaxMailListLength = e.NewSize.Width - RENDERING_COLUMN_MIN_WIDTH;
+37 -19
View File
@@ -11,7 +11,9 @@ using Microsoft.UI.Xaml.Navigation;
using Microsoft.Web.WebView2.Core;
using Windows.System;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Printing;
using Wino.Core.WinUI.Extensions;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI;
@@ -43,7 +45,7 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF");
Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,msWebView2CodeCache");
ViewModel.ShowPrintUIAction = ShowPrintUI;
ViewModel.DirectPrintFuncAsync = DirectPrintAsync;
ViewModel.SaveHTMLasPDFFunc = new Func<string, Task<bool>>((path) =>
{
@@ -51,19 +53,25 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
});
}
private async void ShowPrintUI()
private async Task<PrintingResult> DirectPrintAsync(WebView2PrintSettingsModel settings)
{
if (Chromium.CoreWebView2 == null) return;
if (Chromium.CoreWebView2 == null) return PrintingResult.Failed;
// TODO: Footer still shows wino.mail/html, there is no way to change it currently.
// TODO: ShowPrintUI - System doesn't open.
// TODO: ShowPrintUI - System doesn't open.
try
{
var nativeSettings = settings.ToCoreWebView2PrintSettings(Chromium.CoreWebView2.Environment);
var res = await Chromium.CoreWebView2.PrintAsync(nativeSettings);
// Set the document title before printing. This title will be used in the print dialog and header.
await Chromium.CoreWebView2.ExecuteScriptAsync($"document.title = '{ViewModel.Subject}';");
var settings = Chromium.CoreWebView2.Environment.CreatePrintSettings();
Chromium.CoreWebView2?.ShowPrintUI(CoreWebView2PrintDialogKind.System);
return res switch
{
CoreWebView2PrintStatus.Succeeded => PrintingResult.Submitted,
_ => PrintingResult.Failed,
};
}
catch (Exception)
{
return PrintingResult.Failed;
}
}
public override async void OnEditorThemeChanged()
@@ -138,10 +146,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
{
base.OnNavigatedFrom(e);
WeakReferenceMessenger.Default.Unregister<HtmlRenderingRequested>(this);
WeakReferenceMessenger.Default.Unregister<CancelRenderingContentRequested>(this);
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
// Disposing the page.
// Make sure the WebView2 is disposed properly.
@@ -171,10 +175,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
{
base.OnNavigatedTo(e);
WeakReferenceMessenger.Default.Register<HtmlRenderingRequested>(this);
WeakReferenceMessenger.Default.Register<CancelRenderingContentRequested>(this);
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
var anim = ConnectedAnimationService.GetForCurrentView().GetAnimation("WebViewConnectedAnimation");
anim?.TryStart(Chromium);
@@ -297,4 +297,22 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
hyperlinkButton.ContextFlyout.ShowAt(hyperlinkButton);
}
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<HtmlRenderingRequested>(this);
WeakReferenceMessenger.Default.Register<CancelRenderingContentRequested>(this);
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<HtmlRenderingRequested>(this);
WeakReferenceMessenger.Default.Unregister<CancelRenderingContentRequested>(this);
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
}
}
+20
View File
@@ -332,4 +332,24 @@ public sealed partial class AppShell : AppShellAbstract,
ShellFrame.Margin = new Thickness(0);
}
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<InfoBarMessageRequested>(this);
WeakReferenceMessenger.Default.Register<AccountMenuItemExtended>(this);
WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<InfoBarMessageRequested>(this);
WeakReferenceMessenger.Default.Unregister<AccountMenuItemExtended>(this);
WeakReferenceMessenger.Default.Unregister<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Unregister<NavigateMailFolderEvent>(this);
}
}
+12 -2
View File
@@ -102,9 +102,19 @@ public sealed partial class NewImapSetupDialog : ContentDialog,
public void StartImapConnectionSetup(MailAccount account) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), account, new DrillInNavigationTransitionInfo());
public void StartImapConnectionSetup(AccountCreationDialogResult accountCreationDialogResult) => ImapFrame.Navigate(typeof(WelcomeImapSetupPage), accountCreationDialogResult, new DrillInNavigationTransitionInfo());
private void ImapSetupDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args) => WeakReferenceMessenger.Default.UnregisterAll(this);
private void ImapSetupDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
WeakReferenceMessenger.Default.Unregister<ImapSetupNavigationRequested>(this);
WeakReferenceMessenger.Default.Unregister<ImapSetupBackNavigationRequested>(this);
WeakReferenceMessenger.Default.Unregister<ImapSetupDismissRequested>(this);
}
private void ImapSetupDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => WeakReferenceMessenger.Default.RegisterAll(this);
private void ImapSetupDialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
WeakReferenceMessenger.Default.Register<ImapSetupNavigationRequested>(this);
WeakReferenceMessenger.Default.Register<ImapSetupBackNavigationRequested>(this);
WeakReferenceMessenger.Default.Register<ImapSetupDismissRequested>(this);
}
// Don't hide the dialog unless dismiss is requested from the inner pages specifically.
private void OnDialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args) => args.Cancel = !isDismissRequested;