From 76f6ae0a1e852db30af180fe423f17bbfe19209d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Wed, 8 Apr 2026 15:31:14 +0200 Subject: [PATCH] - Fix for gmail calendar event creation. - Proper junk API calls for gmail and outlook, not just moving the item. - Add ability to hide ai actions panel. --- Wino.Core.Domain/Enums/MailOperation.cs | 1 + .../Interfaces/IPreferencesService.cs | 5 ++ .../Translations/en_US/resources.json | 2 + .../Helpers/SynchronizationActionHelper.cs | 3 + .../Requests/Mail/ChangeJunkStateRequest.cs | 50 +++++++++++++++ Wino.Core/Services/WinoRequestProcessor.cs | 10 ++- Wino.Core/Synchronizers/GmailSynchronizer.cs | 59 ++++++++++++++++-- .../Synchronizers/OutlookSynchronizer.cs | 62 +++++++++++++++++-- Wino.Core/Synchronizers/WinoSynchronizer.cs | 4 ++ .../MailRenderingPageViewModel.cs | 6 ++ Wino.Mail.WinUI/Helpers/XamlHelpers.cs | 2 +- .../Services/PreferencesService.cs | 6 ++ Wino.Mail.WinUI/Views/Mail/ComposePage.xaml | 3 +- .../Views/Mail/ComposePage.xaml.cs | 5 +- .../Views/Mail/MailRenderingPage.xaml | 4 +- .../Views/Mail/MailRenderingPage.xaml.cs | 3 + .../Views/Settings/AppPreferencesPage.xaml | 27 +++++--- Wino.Services/ContextMenuItemService.cs | 5 ++ 18 files changed, 233 insertions(+), 24 deletions(-) create mode 100644 Wino.Core/Requests/Mail/ChangeJunkStateRequest.cs diff --git a/Wino.Core.Domain/Enums/MailOperation.cs b/Wino.Core.Domain/Enums/MailOperation.cs index 9d482958..1fb303c3 100644 --- a/Wino.Core.Domain/Enums/MailOperation.cs +++ b/Wino.Core.Domain/Enums/MailOperation.cs @@ -9,6 +9,7 @@ public enum MailSynchronizerOperation CreateDraft, Send, ChangeFlag, + ChangeJunkState, AlwaysMoveTo, MoveToFocused, Archive, diff --git a/Wino.Core.Domain/Interfaces/IPreferencesService.cs b/Wino.Core.Domain/Interfaces/IPreferencesService.cs index b4f93412..c9f58ba3 100644 --- a/Wino.Core.Domain/Interfaces/IPreferencesService.cs +++ b/Wino.Core.Domain/Interfaces/IPreferencesService.cs @@ -67,6 +67,11 @@ public interface IPreferencesService : INotifyPropertyChanged /// bool IsWinoAccountButtonHidden { get; set; } + /// + /// Setting: Whether AI actions panels and their toggle buttons should be hidden. + /// + bool IsAiActionsPanelHidden { get; set; } + /// /// Setting: Default target language code used for AI translation actions. /// diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 8f2fb7e3..727b8be3 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -736,6 +736,8 @@ "SettingsAppPreferences_HideWinoAccountButton_Description": "Hide the title bar profile button that opens the Wino account flyout.", "SettingsAppPreferences_StoreUpdateNotifications_Title": "Store update notifications", "SettingsAppPreferences_StoreUpdateNotifications_Description": "Show notifications and footer actions when a Microsoft Store update is available.", + "SettingsAppPreferences_HideAiActionsPanel_Title": "Hide AI actions panel", + "SettingsAppPreferences_HideAiActionsPanel_Description": "Hide AI actions in the mail composer and reader.", "SettingsAppPreferences_AiActions_Title": "AI actions", "SettingsAppPreferences_AiActions_Description": "Choose default AI languages and where summaries should be saved.", "SettingsAppPreferences_AiDefaultTranslationLanguage_Title": "Default translation language", diff --git a/Wino.Core/Helpers/SynchronizationActionHelper.cs b/Wino.Core/Helpers/SynchronizationActionHelper.cs index 1646a507..072a44dd 100644 --- a/Wino.Core/Helpers/SynchronizationActionHelper.cs +++ b/Wino.Core/Helpers/SynchronizationActionHelper.cs @@ -85,6 +85,7 @@ public static class SynchronizationActionHelper { MarkReadRequest r => r.IsRead ? "MarkRead" : "MarkUnread", ChangeFlagRequest r => r.IsFlagged ? "SetFlag" : "ClearFlag", + ChangeJunkStateRequest r => r.IsJunk ? "MarkJunk" : "MarkNotJunk", ArchiveRequest r => r.IsArchiving ? "Archive" : "Unarchive", _ => request.Operation.ToString() }; @@ -100,6 +101,8 @@ public static class SynchronizationActionHelper "MarkUnread" => string.Format(Translator.SyncAction_MarkingAsUnread, count), "Delete" => string.Format(Translator.SyncAction_Deleting, count), "Move" => string.Format(Translator.SyncAction_Moving, count), + "MarkJunk" => string.Format(Translator.SyncAction_Moving, count), + "MarkNotJunk" => string.Format(Translator.SyncAction_Moving, count), "Archive" => string.Format(Translator.SyncAction_Archiving, count), "Unarchive" => string.Format(Translator.SyncAction_Unarchiving, count), "SetFlag" => string.Format(Translator.SyncAction_SettingFlag, count), diff --git a/Wino.Core/Requests/Mail/ChangeJunkStateRequest.cs b/Wino.Core/Requests/Mail/ChangeJunkStateRequest.cs new file mode 100644 index 00000000..c301ed04 --- /dev/null +++ b/Wino.Core/Requests/Mail/ChangeJunkStateRequest.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.UI; + +namespace Wino.Core.Requests.Mail; + +public record ChangeJunkStateRequest(bool IsJunk, MailCopy Item, MailItemFolder FromFolder, MailItemFolder TargetFolder) + : MailRequestBase(Item), ICustomFolderSynchronizationRequest +{ + public List SynchronizationFolderIds + { + get + { + var folderIds = new List { FromFolder.Id }; + + if (TargetFolder != null) + { + folderIds.Add(TargetFolder.Id); + } + + return folderIds; + } + } + + public bool ExcludeMustHaveFolders => false; + + public override MailSynchronizerOperation Operation => MailSynchronizerOperation.ChangeJunkState; + + public override void ApplyUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item, EntityUpdateSource.ClientUpdated)); + } + + public override void RevertUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailAddedMessage(Item, EntityUpdateSource.ClientReverted)); + } +} + +public class BatchChangeJunkStateRequest : BatchCollection +{ + public BatchChangeJunkStateRequest(IEnumerable collection) : base(collection) + { + } +} diff --git a/Wino.Core/Services/WinoRequestProcessor.cs b/Wino.Core/Services/WinoRequestProcessor.cs index 87af29a0..22848bda 100644 --- a/Wino.Core/Services/WinoRequestProcessor.cs +++ b/Wino.Core/Services/WinoRequestProcessor.cs @@ -183,7 +183,10 @@ public class WinoRequestProcessor : IWinoRequestProcessor var inboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(mailItem.AssignedAccount.Id, SpecialFolderType.Inbox) ?? throw new UnavailableSpecialFolderException(SpecialFolderType.Inbox, mailItem.AssignedAccount.Id); - return new MoveRequest(mailItem, mailItem.AssignedFolder, inboxFolder); + if (mailItem.AssignedAccount.ProviderType == MailProviderType.IMAP4) + return new MoveRequest(mailItem, mailItem.AssignedFolder, inboxFolder); + + return new ChangeJunkStateRequest(false, mailItem, mailItem.AssignedFolder, inboxFolder); } else if (action == MailOperation.UnArchive) { @@ -204,7 +207,10 @@ public class WinoRequestProcessor : IWinoRequestProcessor var junkFolder = await _folderService.GetSpecialFolderByAccountIdAsync(mailItem.AssignedAccount.Id, SpecialFolderType.Junk) ?? throw new UnavailableSpecialFolderException(SpecialFolderType.Junk, mailItem.AssignedAccount.Id); - return new MoveRequest(mailItem, mailItem.AssignedFolder, junkFolder); + if (mailItem.AssignedAccount.ProviderType == MailProviderType.IMAP4) + return new MoveRequest(mailItem, mailItem.AssignedFolder, junkFolder); + + return new ChangeJunkStateRequest(true, mailItem, mailItem.AssignedFolder, junkFolder); } else if (action == MailOperation.AlwaysMoveToFocused || action == MailOperation.AlwaysMoveToOther) return new AlwaysMoveToRequest(mailItem, action == MailOperation.AlwaysMoveToFocused); diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index b515d4d7..67950529 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -1219,6 +1219,36 @@ public class GmailSynchronizer : WinoSynchronizer(networkCall, request)]; } + public override List> ChangeJunkState(BatchChangeJunkStateRequest request) + { + bool isJunk = request[0].IsJunk; + + var addLabelIds = new HashSet(); + var removeLabelIds = new HashSet(); + + if (isJunk) + { + addLabelIds.Add(ServiceConstants.SPAM_LABEL_ID); + removeLabelIds.Add(ServiceConstants.INBOX_LABEL_ID); + } + else + { + addLabelIds.Add(ServiceConstants.INBOX_LABEL_ID); + removeLabelIds.Add(ServiceConstants.SPAM_LABEL_ID); + } + + var batchModifyRequest = new BatchModifyMessagesRequest + { + Ids = request.Select(a => a.Item.Id.ToString()).ToList(), + AddLabelIds = addLabelIds.ToList(), + RemoveLabelIds = removeLabelIds.ToList() + }; + + var networkCall = _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); + + return [new HttpRequestBundle(networkCall, request)]; + } + public override List> MarkRead(BatchMarkReadRequest request) { bool readStatus = request[0].IsRead; @@ -1783,11 +1813,13 @@ public class GmailSynchronizer : WinoSynchronizer request is BatchDeleteRequest || request is BatchMoveRequest + || request is BatchChangeJunkStateRequest || request is BatchChangeFlagRequest || request is BatchMarkReadRequest || request is BatchArchiveRequest || request is DeleteRequest || request is MoveRequest + || request is ChangeJunkStateRequest || request is ChangeFlagRequest || request is MarkReadRequest || request is ArchiveRequest @@ -1803,6 +1835,8 @@ public class GmailSynchronizer : WinoSynchronizer request is BatchMarkReadRequest || request is MarkReadRequest + || request is BatchChangeJunkStateRequest + || request is ChangeJunkStateRequest || request is BatchChangeFlagRequest || request is ChangeFlagRequest; @@ -2504,25 +2538,28 @@ public class GmailSynchronizer : WinoSynchronizerPost request information. /// Content object to serialize. /// Updated post request information. - private RequestInformation PreparePostRequestInformation(RequestInformation requestInformation, Microsoft.Graph.Me.Messages.Item.Move.MovePostRequestBody content = null) + private RequestInformation PreparePostRequestInformation(RequestInformation requestInformation, string contentJson = "{}") { requestInformation.Headers.Clear(); - string contentJson = content == null ? "{}" : JsonSerializer.Serialize(content, OutlookSynchronizerJsonContext.Default.MovePostRequestBody); - requestInformation.Content = new MemoryStream(Encoding.UTF8.GetBytes(contentJson)); requestInformation.HttpMethod = Method.POST; requestInformation.Headers.Add("Content-Type", "application/json"); @@ -1371,6 +1369,26 @@ public class OutlookSynchronizer : WinoSynchronizer PreparePostRequestInformation(requestInformation, JsonSerializer.Serialize(content, OutlookSynchronizerJsonContext.Default.MovePostRequestBody)); + + private RequestInformation PrepareReportMessageRequestInformation(ChangeJunkStateRequest request) + { + var reportAction = request.IsJunk ? "junk" : "notJunk"; + var body = $$""" + { + "IsMessageMoveRequested": true, + "ReportAction": "{{reportAction}}" + } + """; + + return PreparePostRequestInformation(new RequestInformation + { + URI = new Uri($"https://graph.microsoft.com/beta/me/messages/{Uri.EscapeDataString(request.Item.Id)}/reportMessage"), + HttpMethod = Method.POST + }, body); + } + #region Mail Integration public override bool DelaySendOperationSynchronization() => true; @@ -1402,6 +1420,16 @@ public class OutlookSynchronizer : WinoSynchronizer> ChangeJunkState(BatchChangeJunkStateRequest request) + { + return request + .Select(item => (IRequestBundle)new HttpRequestBundle( + PrepareReportMessageRequestInformation(item), + item, + item)) + .ToList(); + } + public override List> MarkRead(BatchMarkReadRequest request) { return ForEachRequest(request, (item) => @@ -1747,8 +1775,32 @@ public class OutlookSynchronizer : WinoSynchronizer bundle.Request is ChangeJunkStateRequest) + .ToList(); + + foreach (var directRequest in directRequests) + { + try + { + await _graphClient.RequestAdapter.SendAsync( + directRequest.NativeRequest, + Message.CreateFromDiscriminatorValue, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch + { + directRequest.UIChangeRequest?.RevertUIChanges(); + throw; + } + } + // Now batch and execute the network requests. - var batchedGroups = batchedRequests.Batch((int)MaximumAllowedBatchRequestSize); + var batchEligibleRequests = batchedRequests + .Except(directRequests) + .ToList(); + + var batchedGroups = batchEligibleRequests.Batch((int)MaximumAllowedBatchRequestSize); foreach (var batch in batchedGroups) { @@ -1910,11 +1962,13 @@ public class OutlookSynchronizer : WinoSynchronizer request is BatchDeleteRequest || request is BatchMoveRequest + || request is BatchChangeJunkStateRequest || request is BatchChangeFlagRequest || request is BatchMarkReadRequest || request is BatchArchiveRequest || request is DeleteRequest || request is MoveRequest + || request is ChangeJunkStateRequest || request is ChangeFlagRequest || request is MarkReadRequest || request is ArchiveRequest diff --git a/Wino.Core/Synchronizers/WinoSynchronizer.cs b/Wino.Core/Synchronizers/WinoSynchronizer.cs index e1f7f513..d86d6619 100644 --- a/Wino.Core/Synchronizers/WinoSynchronizer.cs +++ b/Wino.Core/Synchronizers/WinoSynchronizer.cs @@ -181,6 +181,9 @@ public abstract class WinoSynchronizer()))); break; + case MailSynchronizerOperation.ChangeJunkState: + nativeRequests.AddRange(ChangeJunkState(new BatchChangeJunkStateRequest(group.Cast()))); + break; case MailSynchronizerOperation.AlwaysMoveTo: nativeRequests.AddRange(AlwaysMoveTo(new BatchAlwaysMoveToRequest(group.Cast()))); break; @@ -577,6 +580,7 @@ public abstract class WinoSynchronizer false; public virtual List> Move(BatchMoveRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> ChangeFlag(BatchChangeFlagRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> ChangeJunkState(BatchChangeJunkStateRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> MarkRead(BatchMarkReadRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> Delete(BatchDeleteRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> AlwaysMoveTo(BatchAlwaysMoveToRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs index 1d9373d9..70bea3cb 100644 --- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs @@ -637,6 +637,12 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, MenuItems.Add(MailOperationMenuItem.Create(MailOperation.MarkAsUnread, true, false)); else MenuItems.Add(MailOperationMenuItem.Create(MailOperation.MarkAsRead, true, false)); + + if (initializedMailItemViewModel.MailCopy.AssignedFolder.SpecialFolderType == SpecialFolderType.Junk) + MenuItems.Add(MailOperationMenuItem.Create(MailOperation.MarkAsNotJunk, true, true)); + else if (!initializedMailItemViewModel.IsDraft && + initializedMailItemViewModel.MailCopy.AssignedFolder.SpecialFolderType != SpecialFolderType.Sent) + MenuItems.Add(MailOperationMenuItem.Create(MailOperation.MoveToJunk, true, true)); } protected override async void OnMailUpdated(MailCopy updatedMail, EntityUpdateSource source, MailCopyChangeFlags changedProperties) diff --git a/Wino.Mail.WinUI/Helpers/XamlHelpers.cs b/Wino.Mail.WinUI/Helpers/XamlHelpers.cs index 60f0f076..b7a9a1e4 100644 --- a/Wino.Mail.WinUI/Helpers/XamlHelpers.cs +++ b/Wino.Mail.WinUI/Helpers/XamlHelpers.cs @@ -357,7 +357,7 @@ public static class XamlHelpers MailOperation.SoftDelete => Translator.MailOperation_Delete, MailOperation.HardDelete => Translator.MailOperation_Delete, MailOperation.Move => Translator.MailOperation_Move, - MailOperation.MoveToJunk => Translator.MailOperation_MoveJunk, + MailOperation.MoveToJunk => Translator.MailOperation_MarkAsJunk, MailOperation.MoveToFocused => Translator.MailOperation_MoveFocused, MailOperation.MoveToOther => Translator.MailOperation_MoveOther, MailOperation.AlwaysMoveToOther => Translator.MailOperation_AlwaysMoveOther, diff --git a/Wino.Mail.WinUI/Services/PreferencesService.cs b/Wino.Mail.WinUI/Services/PreferencesService.cs index 81b0fd6b..37e25675 100644 --- a/Wino.Mail.WinUI/Services/PreferencesService.cs +++ b/Wino.Mail.WinUI/Services/PreferencesService.cs @@ -383,6 +383,12 @@ public class PreferencesService(IConfigurationService configurationService) : Ob set => SetPropertyAndSave(nameof(IsWinoAccountButtonHidden), value); } + public bool IsAiActionsPanelHidden + { + get => _configurationService.Get(nameof(IsAiActionsPanelHidden), false); + set => SetPropertyAndSave(nameof(IsAiActionsPanelHidden), value); + } + public string AiDefaultTranslationLanguageCode { get => _configurationService.Get(nameof(AiDefaultTranslationLanguageCode), "en-US"); diff --git a/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml b/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml index 826cbc09..d2a5d332 100644 --- a/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml +++ b/Wino.Mail.WinUI/Views/Mail/ComposePage.xaml @@ -166,6 +166,7 @@ MinWidth="40" HorizontalContentAlignment="Center" LabelPosition="Collapsed" + Visibility="{x:Bind GetAiActionsToggleVisibility(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}" ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiActions}"> @@ -464,7 +465,7 @@ Margin="0,8,0,0" AvailableActions="Rewrite" HtmlHost="{x:Bind}" - Visibility="{x:Bind GetAiActionsPanelVisibility(ComposeAiActionsToggleButton.IsChecked), Mode=OneWay}" /> + Visibility="{x:Bind GetAiActionsPanelVisibility(ComposeAiActionsToggleButton.IsChecked, ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}" /> WebViewEditor.GetUnderlyingWebView(); - public Visibility GetAiActionsPanelVisibility(bool? isChecked) => isChecked == true ? Visibility.Visible : Visibility.Collapsed; + public Visibility GetAiActionsToggleVisibility(bool isHidden) => isHidden ? Visibility.Collapsed : Visibility.Visible; + + public Visibility GetAiActionsPanelVisibility(bool? isChecked, bool isHidden) + => !isHidden && isChecked == true ? Visibility.Visible : Visibility.Collapsed; private readonly List _disposables = []; diff --git a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml index 9a32f838..87f6bc99 100644 --- a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml +++ b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml @@ -269,7 +269,7 @@ Grid.Row="1" HorizontalContentAlignment="Stretch" DefaultLabelPosition="Right" - IsAIActionsPaneToggleVisible="True" + IsAIActionsPaneToggleVisible="{x:Bind GetAiActionsToggleVisible(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}" IsEditorThemeDark="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=TwoWay}" IsEditorThemeToggleVisible="True" ItemInvokedCommand="{x:Bind ViewModel.OperationClickedCommand}" @@ -415,7 +415,7 @@ Margin="0,8,0,0" AvailableActions="Translate, Summarize" HtmlHost="{x:Bind}" - Visibility="{x:Bind helpers:XamlHelpers.BoolToVisibilityConverter(RendererCommandBar.IsAIActionsEnabled), Mode=OneWay}" /> + Visibility="{x:Bind GetAiActionsPanelVisibility(RendererCommandBar.IsAIActionsEnabled, ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}" /> diff --git a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs index 24ac905c..dc70bca9 100644 --- a/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs +++ b/Wino.Mail.WinUI/Views/Mail/MailRenderingPage.xaml.cs @@ -41,6 +41,9 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract, private string _currentRenderedHtml = string.Empty; public WebView2 GetWebView() => Chromium; + public bool GetAiActionsToggleVisible(bool isHidden) => !isHidden; + public Visibility GetAiActionsPanelVisibility(bool isEnabled, bool isHidden) + => !isHidden && isEnabled ? Visibility.Visible : Visibility.Collapsed; public MailRenderingPage() { diff --git a/Wino.Mail.WinUI/Views/Settings/AppPreferencesPage.xaml b/Wino.Mail.WinUI/Views/Settings/AppPreferencesPage.xaml index 6530aa27..798e1c5c 100644 --- a/Wino.Mail.WinUI/Views/Settings/AppPreferencesPage.xaml +++ b/Wino.Mail.WinUI/Views/Settings/AppPreferencesPage.xaml @@ -62,12 +62,7 @@ - - - - - - + @@ -105,11 +100,11 @@ + ContentAlignment="Vertical" + Description="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarySavePath_Description}" + Header="{x:Bind domain:Translator.SettingsAppPreferences_AiSummarySavePath_Title}"> @@ -134,6 +129,20 @@ Visibility="{x:Bind ViewModel.HasInvalidSummarySavePath, Mode=OneWay}" /> + + + + + + + + + + diff --git a/Wino.Services/ContextMenuItemService.cs b/Wino.Services/ContextMenuItemService.cs index c50c8716..5dcb7668 100644 --- a/Wino.Services/ContextMenuItemService.cs +++ b/Wino.Services/ContextMenuItemService.cs @@ -175,6 +175,11 @@ public class ContextMenuItemService : IContextMenuItemService else actionList.Add(MailOperationMenuItem.Create(MailOperation.MarkAsRead, true, false)); + if (mailItem.AssignedFolder.SpecialFolderType == SpecialFolderType.Junk) + actionList.Add(MailOperationMenuItem.Create(MailOperation.MarkAsNotJunk, true, true)); + else if (!mailItem.IsDraft && mailItem.AssignedFolder.SpecialFolderType != SpecialFolderType.Sent) + actionList.Add(MailOperationMenuItem.Create(MailOperation.MoveToJunk, true, true)); + return actionList; } }