diff --git a/Directory.Packages.props b/Directory.Packages.props index dde3211e..700a216d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -33,7 +33,7 @@ - + diff --git a/Wino.Core.Domain/Interfaces/IMenuOperation.cs b/Wino.Core.Domain/Interfaces/IMenuOperation.cs index 2d588edb..340260c3 100644 --- a/Wino.Core.Domain/Interfaces/IMenuOperation.cs +++ b/Wino.Core.Domain/Interfaces/IMenuOperation.cs @@ -4,4 +4,5 @@ public interface IMenuOperation { bool IsEnabled { get; } string Identifier { get; } + bool IsSecondaryMenuPreferred { get; } } diff --git a/Wino.Core.Domain/Interfaces/IMimeFileService.cs b/Wino.Core.Domain/Interfaces/IMimeFileService.cs index bf4a79f7..b27a9df8 100644 --- a/Wino.Core.Domain/Interfaces/IMimeFileService.cs +++ b/Wino.Core.Domain/Interfaces/IMimeFileService.cs @@ -69,6 +69,16 @@ public interface IMimeFileService /// Task SaveTranslatedHtmlAsync(Guid accountId, Guid fileId, string targetLanguage, string html, CancellationToken cancellationToken = default); + /// + /// Returns cached summary text for the given mime resource if it exists. + /// + Task GetSummaryTextAsync(Guid accountId, Guid fileId, CancellationToken cancellationToken = default); + + /// + /// Saves summary text for the given mime resource. + /// + Task SaveSummaryTextAsync(Guid accountId, Guid fileId, string summary, CancellationToken cancellationToken = default); + /// /// Prepares the final model containing rendering details. /// diff --git a/Wino.Core.Domain/Models/Folders/FolderOperationMenuItem.cs b/Wino.Core.Domain/Models/Folders/FolderOperationMenuItem.cs index ac81ad54..f0b2e5bc 100644 --- a/Wino.Core.Domain/Models/Folders/FolderOperationMenuItem.cs +++ b/Wino.Core.Domain/Models/Folders/FolderOperationMenuItem.cs @@ -1,13 +1,15 @@ -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Menus; namespace Wino.Core.Domain.Models.Folders; -public class FolderOperationMenuItem : MenuOperationItemBase, IMenuOperation +public class FolderOperationMenuItem : MenuOperationItemBase { - protected FolderOperationMenuItem(FolderOperation operation, bool isEnabled) : base(operation, isEnabled) { } + protected FolderOperationMenuItem(FolderOperation operation, bool isEnabled, bool isSecondaryMenuItem = false) : base(operation, isEnabled) + { + IsSecondaryMenuPreferred = isSecondaryMenuItem; + } - public static FolderOperationMenuItem Create(FolderOperation operation, bool isEnabled = true) - => new FolderOperationMenuItem(operation, isEnabled); + public static FolderOperationMenuItem Create(FolderOperation operation, bool isEnabled = true, bool isSecondaryMenuItem = false) + => new FolderOperationMenuItem(operation, isEnabled, isSecondaryMenuItem); } diff --git a/Wino.Core.Domain/Models/Menus/MailOperationMenuItem.cs b/Wino.Core.Domain/Models/Menus/MailOperationMenuItem.cs index 7c243f55..a3e228b7 100644 --- a/Wino.Core.Domain/Models/Menus/MailOperationMenuItem.cs +++ b/Wino.Core.Domain/Models/Menus/MailOperationMenuItem.cs @@ -1,15 +1,9 @@ -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Enums; namespace Wino.Core.Domain.Models.Menus; -public class MailOperationMenuItem : MenuOperationItemBase, IMenuOperation +public class MailOperationMenuItem : MenuOperationItemBase { - /// - /// Gets or sets whether this menu item should be placed in SecondaryCommands if used in CommandBar. - /// - public bool IsSecondaryMenuPreferred { get; set; } - protected MailOperationMenuItem(MailOperation operation, bool isEnabled, bool isSecondaryMenuItem = false) : base(operation, isEnabled) { IsSecondaryMenuPreferred = isSecondaryMenuItem; diff --git a/Wino.Core.Domain/Models/Menus/MenuOperationItemBase.cs b/Wino.Core.Domain/Models/Menus/MenuOperationItemBase.cs index 30f4a1a0..cc2bcdcb 100644 --- a/Wino.Core.Domain/Models/Menus/MenuOperationItemBase.cs +++ b/Wino.Core.Domain/Models/Menus/MenuOperationItemBase.cs @@ -1,9 +1,16 @@ -using System; +using System; +using CommunityToolkit.Mvvm.ComponentModel; +using Wino.Core.Domain.Interfaces; namespace Wino.Core.Domain.Models.Menus; -public class MenuOperationItemBase where TOperation : Enum +public class MenuOperationItemBase : ObservableObject, IMenuOperation where TOperation : Enum { + private TOperation _operation; + private string _identifier = string.Empty; + private bool _isEnabled; + private bool _isSecondaryMenuPreferred; + public MenuOperationItemBase(TOperation operation, bool isEnabled) { Operation = operation; @@ -11,7 +18,33 @@ public class MenuOperationItemBase where TOperation : Enum Identifier = operation.ToString(); } - public TOperation Operation { get; set; } - public string Identifier { get; set; } - public bool IsEnabled { get; set; } + public TOperation Operation + { + get => _operation; + set + { + if (SetProperty(ref _operation, value)) + { + Identifier = value.ToString(); + } + } + } + + public string Identifier + { + get => _identifier; + protected set => SetProperty(ref _identifier, value); + } + + public bool IsEnabled + { + get => _isEnabled; + set => SetProperty(ref _isEnabled, value); + } + + public bool IsSecondaryMenuPreferred + { + get => _isSecondaryMenuPreferred; + set => SetProperty(ref _isSecondaryMenuPreferred, value); + } } diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 33a1310b..a8fd04fa 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -1062,6 +1062,7 @@ "Composer_SmimeEncryption": "S/MIME Encryption", "Composer_EmailTemplatesPlaceholder": "E-mail templates", "Composer_AiSummarize": "Summarize with AI", + "Composer_AiSummarizeDescription": "Extract key points, action items, and decisions from this email.", "Composer_AiTranslate": "Translate with AI", "Composer_AiActions": "AI Actions", "Composer_AiRewrite": "Rewrite with AI", diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs index cfcd9632..a1f1771f 100644 --- a/Wino.Mail.ViewModels/MailListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs @@ -60,7 +60,7 @@ public partial class MailListPageViewModel : MailBaseViewModel, public WinoMailCollection MailCollection { get; set; } = new WinoMailCollection(); public ObservableCollection PivotFolders { get; set; } = []; - public ObservableCollection ActionItems { get; set; } = []; + public ObservableCollection ActionItems { get; set; } = []; private readonly SemaphoreSlim listManipulationSemepahore = new SemaphoreSlim(1); private CancellationTokenSource listManipulationCancellationTokenSource = new CancellationTokenSource(); @@ -439,11 +439,11 @@ public partial class MailListPageViewModel : MailBaseViewModel, public Task ExecuteHoverAction(MailOperationPreperationRequest request) => ExecuteMailOperationAsync(request); [RelayCommand] - private async Task ExecuteTopBarAction(MailOperationMenuItem menuItem) + private async Task ExecuteTopBarAction(IMenuOperation menuItem) { - if (menuItem == null || MailCollection.SelectedItemsCount == 0) return; + if (menuItem is not MailOperationMenuItem mailOperationMenuItem || MailCollection.SelectedItemsCount == 0) return; - await HandleMailOperation(menuItem.Operation, MailCollection.SelectedItems); + await HandleMailOperation(mailOperationMenuItem.Operation, MailCollection.SelectedItems); } /// diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs index 24611d8b..ee132fb3 100644 --- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs @@ -133,7 +133,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, public ObservableCollection CcItems { get; set; } = []; public ObservableCollection BccItems { get; set; } = []; public ObservableCollection Attachments { get; set; } = []; - public ObservableCollection MenuItems { get; set; } = []; + public ObservableCollection MenuItems { get; set; } = []; #endregion @@ -255,21 +255,18 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, } [RelayCommand] - private async Task OperationClicked(MailOperationMenuItem menuItem) + private async Task OperationClicked(IMenuOperation menuItem) { - if (menuItem == null) return; + if (menuItem is not MailOperationMenuItem mailOperationMenuItem) return; - await HandleMailOperationAsync(menuItem.Operation); + await HandleMailOperationAsync(mailOperationMenuItem.Operation); } private async Task HandleMailOperationAsync(MailOperation operation) { try { - // Toggle theme - if (operation == MailOperation.DarkEditor || operation == MailOperation.LightEditor) - IsDarkWebviewRenderer = !IsDarkWebviewRenderer; - else if (operation == MailOperation.SaveAs) + if (operation == MailOperation.SaveAs) { await SaveAsAsync(); } @@ -589,12 +586,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, { MenuItems.Clear(); - // Add light/dark editor theme switch. - if (IsDarkWebviewRenderer) - MenuItems.Add(MailOperationMenuItem.Create(MailOperation.LightEditor)); - else - MenuItems.Add(MailOperationMenuItem.Create(MailOperation.DarkEditor)); - // Save As PDF MenuItems.Add(MailOperationMenuItem.Create(MailOperation.SaveAs, true, true)); diff --git a/Wino.Mail.WinUI/App.xaml b/Wino.Mail.WinUI/App.xaml index 5d6ab8d9..21d04fe6 100644 --- a/Wino.Mail.WinUI/App.xaml +++ b/Wino.Mail.WinUI/App.xaml @@ -19,6 +19,7 @@ + diff --git a/Wino.Mail.WinUI/Behaviors/BindableCommandBarBehavior.cs b/Wino.Mail.WinUI/Behaviors/BindableCommandBarBehavior.cs deleted file mode 100644 index a8228679..00000000 --- a/Wino.Mail.WinUI/Behaviors/BindableCommandBarBehavior.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Specialized; -using System.Windows.Input; -using CommunityToolkit.WinUI; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.Xaml.Interactivity; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Menus; -using Wino.Helpers; -using Wino.Mail.WinUI; -using Wino.Mail.WinUI.Controls; - -namespace Wino.Behaviors; - -public partial class BindableCommandBarBehavior : Behavior -{ - private readonly IPreferencesService? _preferencesService = App.Current.Services.GetService(); - public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register( - "PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior), - new PropertyMetadata(null, UpdateCommands)); - - [GeneratedDependencyProperty] - public partial ICommand? ItemClickedCommand { get; set; } - - public object PrimaryCommands - { - get { return GetValue(PrimaryCommandsProperty); } - set { SetValue(PrimaryCommandsProperty, value); } - } - - protected override void OnDetaching() - { - base.OnDetaching(); - - AttachChanges(false); - - if (PrimaryCommands is IEnumerable enumerable) - { - foreach (var item in enumerable) - { - DetachCommandElement(item); - } - } - } - - private void DetachCommandElement(object item) - { - if (item is ButtonBase button) - { - button.Click -= Button_Click; - return; - } - - if (item is AppBarElementContainer container && container.Content is IDisposable disposable) - { - disposable.Dispose(); - } - } - - private void UpdatePrimaryCommands() - { - if (AssociatedObject == null) - return; - - if (PrimaryCommands == null) - return; - - if (AssociatedObject.PrimaryCommands is IEnumerable enumerableObjects) - { - foreach (var item in enumerableObjects) - { - DetachCommandElement(item); - } - } - - if (AssociatedObject.SecondaryCommands is IEnumerable secondaryObject) - { - foreach (var item in secondaryObject) - { - DetachCommandElement(item); - } - } - - AssociatedObject.PrimaryCommands.Clear(); - AssociatedObject.SecondaryCommands.Clear(); - - if (PrimaryCommands is not IEnumerable enumerable) return; - - foreach (var command in enumerable) - { - if (command is MailOperationMenuItem mailOperationMenuItem) - { - ICommandBarElement? menuItem = null; - - if (mailOperationMenuItem.Operation == Core.Domain.Enums.MailOperation.Seperator) - { - menuItem = new AppBarSeparator(); - } - else - { - var label = XamlHelpers.GetOperationString(mailOperationMenuItem.Operation); - var labelPosition = string.IsNullOrWhiteSpace(label) || _preferencesService == null || !_preferencesService.IsShowActionLabelsEnabled ? - CommandBarLabelPosition.Collapsed : CommandBarLabelPosition.Default; - - var iconGlyph = XamlHelpers.GetWinoIconGlyph(mailOperationMenuItem.Operation); - var glyphValue = ControlConstants.WinoIconFontDictionary.TryGetValue(iconGlyph, out var glyph) ? glyph : string.Empty; - - menuItem = new AppBarButton - { - Width = double.NaN, - MinWidth = 40, - Icon = new WinoFontIcon() { Glyph = glyphValue }, - Label = label, - LabelPosition = labelPosition, - DataContext = mailOperationMenuItem, - }; - - if (!string.IsNullOrWhiteSpace(label)) - { - var toolTip = new ToolTip - { - Content = label - }; - ToolTipService.SetToolTip((DependencyObject)menuItem, toolTip); - } - - ((AppBarButton)menuItem).Click -= Button_Click; - ((AppBarButton)menuItem).Click += Button_Click; - } - - if (mailOperationMenuItem.IsSecondaryMenuPreferred) - { - AssociatedObject.SecondaryCommands.Add(menuItem); - } - else - { - AssociatedObject.PrimaryCommands.Add(menuItem); - } - } - //if (dependencyObject is ICommandBarElement icommandBarElement) - //{ - // if (dependencyObject is ButtonBase button) - // { - // button.Click -= Button_Click; - // button.Click += Button_Click; - // } - - // if (command is MailOperationMenuItem mailOperationMenuItem) - // { - - // } - //} - } - } - - private void Button_Click(object sender, RoutedEventArgs e) - { - ItemClickedCommand?.Execute(((ButtonBase)sender).DataContext); - } - - protected override void OnAttached() - { - base.OnAttached(); - - AttachChanges(true); - - UpdatePrimaryCommands(); - } - - private void PrimaryCommandsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - UpdatePrimaryCommands(); - } - - private static void UpdateCommands(DependencyObject dependencyObject, - DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) - { - if (dependencyObject is not BindableCommandBarBehavior behavior) return; - - if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList) - { - oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged; - } - - behavior.AttachChanges(true); - behavior.UpdatePrimaryCommands(); - } - - private void AttachChanges(bool register) - { - if (PrimaryCommands is null) return; - - if (PrimaryCommands is INotifyCollectionChanged collectionChanged) - { - if (register) - { - collectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged; - collectionChanged.CollectionChanged += PrimaryCommandsCollectionChanged; - } - else - collectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged; - } - } -} diff --git a/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml b/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml index d308f517..b799c1e2 100644 --- a/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml +++ b/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml @@ -5,8 +5,8 @@ xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="using:Wino.Core.Domain" - xmlns:models="using:Wino.Core.Domain.Models.Ai" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:models="using:Wino.Core.Domain.Models.Ai" x:Name="root" Loaded="OnLoaded" Unloaded="OnUnloaded" @@ -20,11 +20,17 @@ CornerRadius="10"> - + - + - - + + @@ -47,7 +59,12 @@ - + - - + + - + - + - + @@ -110,121 +137,178 @@