From 9877656eeac2a031d3a6933c3ae6e6e391db342f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Sat, 3 Jan 2026 19:33:36 +0100 Subject: [PATCH] RSVP options. --- .../Data/AccountCalendarViewModel.cs | 1 + .../EventDetailsPageViewModel.cs | 146 ++++++- Wino.Calendar/Views/EventDetailsPage.xaml | 18 +- .../Entities/Calendar/AccountCalendar.cs | 4 + Wino.Core.Domain/Enums/CalendarItemStatus.cs | 2 +- Wino.Core.Domain/Enums/MailOperation.cs | 3 + .../Interfaces/IAccountCalendar.cs | 2 + .../CalendarOperationPreparationRequest.cs | 5 +- .../Translations/en_US/resources.json | 12 + .../Processors/GmailChangeProcessor.cs | 4 +- .../Processors/OutlookChangeProcessor.cs | 4 +- .../Requests/Calendar/AcceptEventRequest.cs | 39 ++ .../Requests/Calendar/DeclineEventRequest.cs | 39 ++ .../Calendar/OutlookDeclineEventRequest.cs | 38 ++ .../Calendar/TentativeEventRequest.cs | 39 ++ Wino.Core/Services/WinoRequestDelegator.cs | 16 + Wino.Core/Synchronizers/GmailSynchronizer.cs | 122 +++++- .../Synchronizers/OutlookSynchronizer.cs | 77 ++++ Wino.Core/Synchronizers/WinoSynchronizer.cs | 20 + Wino.Mail.WinUI/Assets/WinoIcons.ttf | Bin 37128 -> 42820 bytes Wino.Mail.WinUI/Controls/ControlConstants.cs | 11 +- Wino.Mail.WinUI/Controls/WinoFontIcon.cs | 10 +- .../RsvpStatusIconTemplateSelector.cs | 30 ++ Wino.Mail.WinUI/Styles/DataTemplates.xaml | 10 +- .../Views/Calendar/EventDetailsPage.xaml | 392 ++++++++++++++---- Wino.Services/CalendarService.cs | 36 +- WinoFontIcomoon.json | 2 +- WinoMail.slnx | 1 + 28 files changed, 968 insertions(+), 115 deletions(-) create mode 100644 Wino.Core/Requests/Calendar/AcceptEventRequest.cs create mode 100644 Wino.Core/Requests/Calendar/DeclineEventRequest.cs create mode 100644 Wino.Core/Requests/Calendar/OutlookDeclineEventRequest.cs create mode 100644 Wino.Core/Requests/Calendar/TentativeEventRequest.cs create mode 100644 Wino.Mail.WinUI/Selectors/RsvpStatusIconTemplateSelector.cs diff --git a/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs b/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs index df55f72f..8d36afc4 100644 --- a/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs +++ b/Wino.Calendar.ViewModels/Data/AccountCalendarViewModel.cs @@ -66,4 +66,5 @@ public partial class AccountCalendarViewModel : ObservableObject, IAccountCalend set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r); } public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; } + public MailAccount MailAccount { get => MailAccount; set => MailAccount = value; } } diff --git a/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs b/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs index 068e85b2..fe33de02 100644 --- a/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs +++ b/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs @@ -36,7 +36,10 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel [NotifyPropertyChangedFor(nameof(CanViewSeries))] [NotifyPropertyChangedFor(nameof(CanEditSeries))] [NotifyPropertyChangedFor(nameof(IsCurrentUserOrganizer))] + [NotifyPropertyChangedFor(nameof(CurrentRsvpText))] + [NotifyPropertyChangedFor(nameof(CurrentRsvpStatus))] public partial CalendarItemViewModel CurrentEvent { get; set; } + [ObservableProperty] public partial CalendarItemViewModel SeriesParent { get; set; } [ObservableProperty] @@ -80,6 +83,45 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel #endregion + #region RSVP Panel + + [ObservableProperty] + public partial bool IsRsvpPanelVisible { get; set; } + + public bool IncludeRsvpMessage => !string.IsNullOrEmpty(RsvpMessage); + + [ObservableProperty] + public partial string RsvpMessage { get; set; } = string.Empty; + + public ObservableCollection RsvpStatusOptions { get; } = new ObservableCollection(); + + public CalendarItemStatus CurrentRsvpStatus + { + get + { + return CurrentEvent?.CalendarItem?.Status ?? CalendarItemStatus.NotResponded; + } + } + + public string CurrentRsvpText + { + get + { + if (CurrentEvent?.CalendarItem == null) return Translator.CalendarEventResponse_Accept; + + return CurrentEvent.CalendarItem.Status switch + { + CalendarItemStatus.Accepted => Translator.CalendarEventResponse_AcceptedResponse, + CalendarItemStatus.Tentative => Translator.CalendarEventResponse_TentativeResponse, + CalendarItemStatus.Cancelled => Translator.CalendarEventResponse_DeclinedResponse, + CalendarItemStatus.NotResponded => Translator.CalendarEventResponse_NotResponded, + _ => throw new NotImplementedException() + }; + } + } + + #endregion + public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService, @@ -95,6 +137,11 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel _navigationService = navigationService; CurrentSettings = _preferencesService.GetCurrentCalendarSettings(); + + // Initialize RSVP status options + RsvpStatusOptions.Add(new RsvpStatusOption(CalendarItemStatus.Accepted)); + RsvpStatusOptions.Add(new RsvpStatusOption(CalendarItemStatus.Tentative)); + RsvpStatusOptions.Add(new RsvpStatusOption(CalendarItemStatus.Cancelled)); } public override async void OnNavigatedTo(NavigationMode mode, object parameters) @@ -109,6 +156,17 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel await LoadCalendarItemTargetAsync(args); } + protected override void OnCalendarItemDeleted(CalendarItem calendarItem) + { + base.OnCalendarItemDeleted(calendarItem); + + // If the current event was deleted, navigate back + if (CurrentEvent?.CalendarItem?.Id == calendarItem.Id || CurrentEvent?.CalendarItem.RecurringCalendarItemId == calendarItem.Id) + { + _navigationService.GoBack(); + } + } + private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target) { try @@ -265,11 +323,68 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel } [RelayCommand] - private async Task RespondAsync(CalendarItemStatus status) + private void ToggleRsvpPanel() + { + IsRsvpPanelVisible = !IsRsvpPanelVisible; + + if (IsRsvpPanelVisible && CurrentEvent?.CalendarItem != null) + { + // Initialize selection based on current status + foreach (var item in RsvpStatusOptions) + { + item.IsSelected = CurrentEvent?.CalendarItem?.Status == item.Status; + } + } + } + + [RelayCommand] + private void CloseRsvpPanel() + { + IsRsvpPanelVisible = false; + RsvpMessage = string.Empty; + } + + [RelayCommand] + private async Task SendRsvpResponse(AttendeeStatus status) { if (CurrentEvent == null) return; - // TODO: Implement response + try + { + // Get the optional response message if user wants to include it + var responseMessage = IncludeRsvpMessage ? RsvpMessage : null; + + // Map status to operation + CalendarSynchronizerOperation operation = status switch + { + AttendeeStatus.Accepted => CalendarSynchronizerOperation.AcceptEvent, + AttendeeStatus.Tentative => CalendarSynchronizerOperation.TentativeEvent, + AttendeeStatus.Declined => CalendarSynchronizerOperation.DeclineEvent, + _ => throw new InvalidOperationException($"Invalid RSVP status: {status}") + }; + + // Create preparation request with the optional message + var preparationRequest = new CalendarOperationPreparationRequest( + operation, + CurrentEvent.CalendarItem, + null, + responseMessage); + + await _winoRequestDelegator.ExecuteAsync(preparationRequest); + + OnPropertyChanged(nameof(CurrentRsvpText)); + OnPropertyChanged(nameof(CurrentRsvpStatus)); + + CloseRsvpPanel(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error sending RSVP response: {ex.Message}"); + _dialogService.InfoBarMessage( + Translator.Info_AttachmentSaveFailedTitle, + ex.Message, + InfoBarMessageType.Error); + } } [RelayCommand] @@ -323,3 +438,30 @@ public partial class ReminderOption : ObservableObject IsCustom = isCustom; } } + +public partial class RsvpStatusOption : ObservableObject +{ + public CalendarItemStatus Status { get; } + + public string StatusText + { + get + { + return Status switch + { + CalendarItemStatus.Accepted => Translator.CalendarEventResponse_Accept, + CalendarItemStatus.Tentative => Translator.CalendarEventResponse_Tentative, + CalendarItemStatus.Cancelled => Translator.CalendarEventResponse_Decline, + _ => Translator.CalendarEventResponse_Accept + }; + } + } + + [ObservableProperty] + public partial bool IsSelected { get; set; } + + public RsvpStatusOption(CalendarItemStatus status) + { + Status = status; + } +} diff --git a/Wino.Calendar/Views/EventDetailsPage.xaml b/Wino.Calendar/Views/EventDetailsPage.xaml index 875b5689..c82ca17d 100644 --- a/Wino.Calendar/Views/EventDetailsPage.xaml +++ b/Wino.Calendar/Views/EventDetailsPage.xaml @@ -258,18 +258,32 @@ Height="40" DisplayName="{x:Bind Name}" /> - - + + + + + diff --git a/Wino.Core.Domain/Entities/Calendar/AccountCalendar.cs b/Wino.Core.Domain/Entities/Calendar/AccountCalendar.cs index d8d81f7d..1b8d06bd 100644 --- a/Wino.Core.Domain/Entities/Calendar/AccountCalendar.cs +++ b/Wino.Core.Domain/Entities/Calendar/AccountCalendar.cs @@ -1,5 +1,6 @@ using System; using SQLite; +using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Interfaces; namespace Wino.Core.Domain.Entities.Calendar; @@ -22,4 +23,7 @@ public class AccountCalendar : IAccountCalendar public string TextColorHex { get; set; } public string BackgroundColorHex { get; set; } public string TimeZone { get; set; } + + [Ignore] + public MailAccount MailAccount { get; set; } } diff --git a/Wino.Core.Domain/Enums/CalendarItemStatus.cs b/Wino.Core.Domain/Enums/CalendarItemStatus.cs index e8605573..c6d08b52 100644 --- a/Wino.Core.Domain/Enums/CalendarItemStatus.cs +++ b/Wino.Core.Domain/Enums/CalendarItemStatus.cs @@ -3,7 +3,7 @@ public enum CalendarItemStatus { NotResponded, - Confirmed, + Accepted, Tentative, Cancelled, } diff --git a/Wino.Core.Domain/Enums/MailOperation.cs b/Wino.Core.Domain/Enums/MailOperation.cs index e90f22a7..3e6f285f 100644 --- a/Wino.Core.Domain/Enums/MailOperation.cs +++ b/Wino.Core.Domain/Enums/MailOperation.cs @@ -26,6 +26,9 @@ public enum CalendarSynchronizerOperation CreateEvent, UpdateEvent, DeleteEvent, + AcceptEvent, + DeclineEvent, + TentativeEvent, } // UI requests diff --git a/Wino.Core.Domain/Interfaces/IAccountCalendar.cs b/Wino.Core.Domain/Interfaces/IAccountCalendar.cs index 85a340c3..3c4e8c4a 100644 --- a/Wino.Core.Domain/Interfaces/IAccountCalendar.cs +++ b/Wino.Core.Domain/Interfaces/IAccountCalendar.cs @@ -1,4 +1,5 @@ using System; +using Wino.Core.Domain.Entities.Shared; namespace Wino.Core.Domain.Interfaces; @@ -12,4 +13,5 @@ public interface IAccountCalendar string RemoteCalendarId { get; set; } bool IsExtended { get; set; } Guid Id { get; set; } + MailAccount MailAccount { get; set; } } diff --git a/Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs b/Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs index 0705681e..b5bba4e7 100644 --- a/Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs +++ b/Wino.Core.Domain/Models/Calendar/CalendarOperationPreparationRequest.cs @@ -7,7 +7,8 @@ namespace Wino.Core.Domain.Models.Calendar; /// /// Encapsulates the options for preparing calendar operation requests. /// -/// Calendar operation to execute (Create, Update, Delete). +/// Calendar operation to execute (Create, Update, Delete, Accept, Decline, Tentative). /// Calendar item to operate on. /// List of attendees for the calendar event. -public record CalendarOperationPreparationRequest(CalendarSynchronizerOperation Operation, CalendarItem CalendarItem, List Attendees); +/// Optional message to include with event responses (Accept, Decline, Tentative). +public record CalendarOperationPreparationRequest(CalendarSynchronizerOperation Operation, CalendarItem CalendarItem, List Attendees, string ResponseMessage = null); diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index d7fa3316..8168f1b6 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -84,14 +84,26 @@ "CalendarDisplayOptions_Color": "Color", "CalendarDisplayOptions_Expand": "Expand", "CalendarEventResponse_Accept": "Accept", + "CalendarEventResponse_AcceptedResponse": "Accepted", "CalendarEventResponse_Decline": "Decline", + "CalendarEventResponse_DeclinedResponse": "Declined", + "CalendarEventResponse_NotResponded": "Not Responded", "CalendarEventResponse_Tentative": "Tentative", + "CalendarEventResponse_TentativeResponse": "Tentative", + "CalendarEventRsvpPanel_Accept": "Accept", + "CalendarEventRsvpPanel_AddMessage": "Add a message to your response... (optional)", + "CalendarEventRsvpPanel_Decline": "Decline", + "CalendarEventRsvpPanel_Message": "Message", + "CalendarEventRsvpPanel_SendReplyMessage": "Send a reply message", + "CalendarEventRsvpPanel_Tentative": "Tentative", + "CalendarEventRsvpPanel_Title": "Response Options", "CalendarEventDetails_Attachments": "Attachments", "CalendarEventDetails_Details": "Details", "CalendarEventDetails_EditSeries": "Edit Series", "CalendarEventDetails_Editing": "Editing", "CalendarEventDetails_InviteSomeone": "Invite someone", "CalendarEventDetails_JoinOnline": "Join Online", + "CalendarEventDetails_Organizer": "Organizer", "CalendarEventDetails_People": "People", "CalendarEventDetails_ReadOnlyEvent": "Read-only event", "CalendarEventDetails_Reminder": "Reminder", diff --git a/Wino.Core/Integration/Processors/GmailChangeProcessor.cs b/Wino.Core/Integration/Processors/GmailChangeProcessor.cs index 9329752c..e975aba7 100644 --- a/Wino.Core/Integration/Processors/GmailChangeProcessor.cs +++ b/Wino.Core/Integration/Processors/GmailChangeProcessor.cs @@ -349,10 +349,10 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso { return status switch { - "confirmed" => CalendarItemStatus.Confirmed, + "confirmed" => CalendarItemStatus.Accepted, "tentative" => CalendarItemStatus.Tentative, "cancelled" => CalendarItemStatus.Cancelled, - _ => CalendarItemStatus.Confirmed + _ => CalendarItemStatus.Accepted }; } diff --git a/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs b/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs index 176ac7dc..60ce4103 100644 --- a/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs +++ b/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs @@ -152,7 +152,7 @@ public class OutlookChangeProcessor(IDatabaseService databaseService, break; case ResponseType.Accepted: case ResponseType.Organizer: - savingItem.Status = CalendarItemStatus.Confirmed; + savingItem.Status = CalendarItemStatus.Accepted; break; case ResponseType.Declined: savingItem.Status = CalendarItemStatus.Cancelled; @@ -164,7 +164,7 @@ public class OutlookChangeProcessor(IDatabaseService databaseService, } else { - savingItem.Status = CalendarItemStatus.Confirmed; + savingItem.Status = CalendarItemStatus.Accepted; } // Prepare attendees list diff --git a/Wino.Core/Requests/Calendar/AcceptEventRequest.cs b/Wino.Core/Requests/Calendar/AcceptEventRequest.cs new file mode 100644 index 00000000..aa43a3a3 --- /dev/null +++ b/Wino.Core/Requests/Calendar/AcceptEventRequest.cs @@ -0,0 +1,39 @@ +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Calendar; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Core.Requests.Calendar; + +/// +/// Request to accept a calendar event invitation on the server. +/// The calendar item status should be updated locally before queuing this request. +/// +public record AcceptEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item) +{ + private readonly CalendarItemStatus _previousStatus = Item.Status; + + public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.AcceptEvent; + + /// + /// After successful acceptance, we need to resync to get updated status. + /// + public override int ResynchronizationDelay => 2000; + + public override void ApplyUIChanges() + { + // Update the item status locally + Item.Status = CalendarItemStatus.Accepted; + + // Notify UI that the event status was updated + WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item)); + } + + public override void RevertUIChanges() + { + // If acceptance fails, revert to the previous status + Item.Status = _previousStatus; + WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item)); + } +} diff --git a/Wino.Core/Requests/Calendar/DeclineEventRequest.cs b/Wino.Core/Requests/Calendar/DeclineEventRequest.cs new file mode 100644 index 00000000..34ac0257 --- /dev/null +++ b/Wino.Core/Requests/Calendar/DeclineEventRequest.cs @@ -0,0 +1,39 @@ +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Calendar; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Core.Requests.Calendar; + +/// +/// Request to decline a calendar event invitation on the server. +/// The calendar item status should be updated locally before queuing this request. +/// +public record DeclineEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item) +{ + private readonly CalendarItemStatus _previousStatus = Item.Status; + + public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.DeclineEvent; + + /// + /// After successful decline, we need to resync to get updated status. + /// + public override int ResynchronizationDelay => 2000; + + public override void ApplyUIChanges() + { + // Update the item status locally + Item.Status = CalendarItemStatus.Cancelled; + + // Notify UI that the event status was updated + WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item)); + } + + public override void RevertUIChanges() + { + // If decline fails, revert to the previous status + Item.Status = _previousStatus; + WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item)); + } +} diff --git a/Wino.Core/Requests/Calendar/OutlookDeclineEventRequest.cs b/Wino.Core/Requests/Calendar/OutlookDeclineEventRequest.cs new file mode 100644 index 00000000..c7e87147 --- /dev/null +++ b/Wino.Core/Requests/Calendar/OutlookDeclineEventRequest.cs @@ -0,0 +1,38 @@ +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Calendar; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Core.Requests.Calendar; + +/// +/// Outlook-specific request to decline a calendar event invitation. +/// In Outlook, declined events are removed from the calendar by the API after synchronization, +/// so this request sends a delete notification to remove the event from the UI. +/// +public record OutlookDeclineEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item) +{ + private readonly CalendarItemStatus _previousStatus = Item.Status; + + public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.DeclineEvent; + + /// + /// After successful decline, we need to resync to confirm the event is removed. + /// + public override int ResynchronizationDelay => 2000; + + public override void ApplyUIChanges() + { + // In Outlook, declined events are deleted from the calendar after sync + // Send deleted message to remove from UI immediately + WeakReferenceMessenger.Default.Send(new CalendarItemDeleted(Item)); + } + + public override void RevertUIChanges() + { + // If decline fails, restore the previous status and re-add the event + Item.Status = _previousStatus; + WeakReferenceMessenger.Default.Send(new CalendarItemAdded(Item)); + } +} diff --git a/Wino.Core/Requests/Calendar/TentativeEventRequest.cs b/Wino.Core/Requests/Calendar/TentativeEventRequest.cs new file mode 100644 index 00000000..43f90d0d --- /dev/null +++ b/Wino.Core/Requests/Calendar/TentativeEventRequest.cs @@ -0,0 +1,39 @@ +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities.Calendar; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; +using Wino.Messaging.Client.Calendar; + +namespace Wino.Core.Requests.Calendar; + +/// +/// Request to tentatively accept a calendar event invitation on the server. +/// The calendar item status should be updated locally before queuing this request. +/// +public record TentativeEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item) +{ + private readonly CalendarItemStatus _previousStatus = Item.Status; + + public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.TentativeEvent; + + /// + /// After successful tentative acceptance, we need to resync to get updated status. + /// + public override int ResynchronizationDelay => 2000; + + public override void ApplyUIChanges() + { + // Update the item status locally + Item.Status = CalendarItemStatus.Tentative; + + // Notify UI that the event status was updated + WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item)); + } + + public override void RevertUIChanges() + { + // If tentative acceptance fails, revert to the previous status + Item.Status = _previousStatus; + WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item)); + } +} diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index 2edf164c..fa4a881e 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using Serilog; using Wino.Core.Domain; +using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Exceptions; using Wino.Core.Domain.Interfaces; @@ -141,6 +142,9 @@ public class WinoRequestDelegator : IWinoRequestDelegator { CalendarSynchronizerOperation.CreateEvent => new CreateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees), CalendarSynchronizerOperation.DeleteEvent => new DeleteCalendarEventRequest(calendarPreparationRequest.CalendarItem), + CalendarSynchronizerOperation.AcceptEvent => new AcceptEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.ResponseMessage), + CalendarSynchronizerOperation.DeclineEvent => CreateDeclineRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.ResponseMessage), + CalendarSynchronizerOperation.TentativeEvent => new TentativeEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.ResponseMessage), // Future support for update operations // CalendarSynchronizerOperation.UpdateEvent => new UpdateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees), _ => throw new NotImplementedException($"Calendar operation {calendarPreparationRequest.Operation} is not implemented yet.") @@ -150,6 +154,18 @@ public class WinoRequestDelegator : IWinoRequestDelegator await QueueCalendarSynchronizationAsync(calendarPreparationRequest.CalendarItem.AssignedCalendar.AccountId); } + private IRequestBase CreateDeclineRequest(CalendarItem calendarItem, string responseMessage) + { + // For Outlook accounts, declined events are deleted by the server after synchronization. + // Use OutlookDeclineEventRequest to handle UI removal. + if (calendarItem.AssignedCalendar?.MailAccount?.ProviderType == MailProviderType.Outlook) + { + return new OutlookDeclineEventRequest(calendarItem, responseMessage); + } + + return new DeclineEventRequest(calendarItem, responseMessage); + } + private async Task QueueRequestAsync(IRequestBase request, Guid accountId) { // Don't trigger synchronization for individual requests - we'll trigger it once for all requests diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index ab01ffe3..a169aee3 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.Web; using CommunityToolkit.Mvvm.Messaging; using Google; +using Google.Apis.Calendar.v3; using Google.Apis.Calendar.v3.Data; using Google.Apis.Gmail.v1; using Google.Apis.Gmail.v1.Data; @@ -1648,7 +1649,7 @@ public class GmailSynchronizer : WinoSynchronizer(insertRequest, request)]; } + public override List> AcceptEvent(AcceptEventRequest request) + { + var calendarItem = request.Item; + var calendar = calendarItem.AssignedCalendar; + + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + if (string.IsNullOrEmpty(calendarItem.RemoteEventId)) + { + throw new InvalidOperationException("Cannot accept event without remote event ID"); + } + + // For Gmail, we need to patch the event with the user's response status + // Get the current user's email from the account + var userEmail = Account.Address; + + // Create a patch event to update only the attendee response + var patchEvent = new Event(); + + // We need to get the event first to update the specific attendee + // However, for efficiency, we'll use the patch method with sendUpdates parameter + var patchRequest = _calendarService.Events.Patch(new Event + { + // The API will handle updating the current user's attendee status + Attendees = new List + { + new EventAttendee + { + Email = userEmail, + ResponseStatus = "accepted" + } + } + }, calendar.RemoteCalendarId, calendarItem.RemoteEventId); + + // Send updates to other attendees if there's a message + patchRequest.SendUpdates = !string.IsNullOrEmpty(request.ResponseMessage) + ? Google.Apis.Calendar.v3.EventsResource.PatchRequest.SendUpdatesEnum.All + : Google.Apis.Calendar.v3.EventsResource.PatchRequest.SendUpdatesEnum.None; + + return [new HttpRequestBundle(patchRequest, request)]; + } + + public override List> DeclineEvent(DeclineEventRequest request) + { + var calendarItem = request.Item; + var calendar = calendarItem.AssignedCalendar; + + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + if (string.IsNullOrEmpty(calendarItem.RemoteEventId)) + { + throw new InvalidOperationException("Cannot decline event without remote event ID"); + } + + var userEmail = Account.Address; + + var patchRequest = _calendarService.Events.Patch(new Event + { + Attendees = new List + { + new EventAttendee + { + Email = userEmail, + ResponseStatus = "declined", + Comment = request.ResponseMessage + } + } + }, calendar.RemoteCalendarId, calendarItem.RemoteEventId); + + patchRequest.SendUpdates = !string.IsNullOrEmpty(request.ResponseMessage) + ? Google.Apis.Calendar.v3.EventsResource.PatchRequest.SendUpdatesEnum.All + : Google.Apis.Calendar.v3.EventsResource.PatchRequest.SendUpdatesEnum.None; + + return [new HttpRequestBundle(patchRequest, request)]; + } + + public override List> TentativeEvent(TentativeEventRequest request) + { + var calendarItem = request.Item; + var calendar = calendarItem.AssignedCalendar; + + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + if (string.IsNullOrEmpty(calendarItem.RemoteEventId)) + { + throw new InvalidOperationException("Cannot tentatively accept event without remote event ID"); + } + + var userEmail = Account.Address; + + var patchRequest = _calendarService.Events.Patch(new Event + { + Attendees = new List + { + new EventAttendee + { + Email = userEmail, + ResponseStatus = "tentative", + Comment = request.ResponseMessage + } + } + }, calendar.RemoteCalendarId, calendarItem.RemoteEventId); + + patchRequest.SendUpdates = !string.IsNullOrEmpty(request.ResponseMessage) + ? Google.Apis.Calendar.v3.EventsResource.PatchRequest.SendUpdatesEnum.All + : Google.Apis.Calendar.v3.EventsResource.PatchRequest.SendUpdatesEnum.None; + + return [new HttpRequestBundle(patchRequest, request)]; + } + #endregion public override async Task KillSynchronizerAsync() diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index 89a91639..9aa09f0c 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -1680,6 +1680,9 @@ public class OutlookSynchronizer : WinoSynchronizer(createRequest, request)]; } + public override List> AcceptEvent(AcceptEventRequest request) + { + var calendarItem = request.Item; + var calendar = calendarItem.AssignedCalendar; + + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + if (string.IsNullOrEmpty(calendarItem.RemoteEventId)) + { + throw new InvalidOperationException("Cannot accept event without remote event ID"); + } + + var acceptRequestInfo = _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events[calendarItem.RemoteEventId].Accept.ToPostRequestInformation(new Microsoft.Graph.Me.Calendars.Item.Events.Item.Accept.AcceptPostRequestBody + { + Comment = request.ResponseMessage, + SendResponse = !string.IsNullOrEmpty(request.ResponseMessage) + }); + + return [new HttpRequestBundle(acceptRequestInfo, request)]; + } + + public override List> OutlookDeclineEvent(OutlookDeclineEventRequest request) + { + var responseMessage = request.ResponseMessage; + + var calendarItem = request.Item; + var calendar = calendarItem.AssignedCalendar; + + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + if (string.IsNullOrEmpty(calendarItem.RemoteEventId)) + { + throw new InvalidOperationException("Cannot decline event without remote event ID"); + } + + var declineRequestInfo = _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events[calendarItem.RemoteEventId].Decline.ToPostRequestInformation(new Microsoft.Graph.Me.Calendars.Item.Events.Item.Decline.DeclinePostRequestBody + { + Comment = responseMessage, + SendResponse = !string.IsNullOrEmpty(responseMessage) + }); + + return [new HttpRequestBundle(declineRequestInfo, request)]; + } + + public override List> TentativeEvent(TentativeEventRequest request) + { + var calendarItem = request.Item; + var calendar = calendarItem.AssignedCalendar; + + if (calendar == null) + { + throw new InvalidOperationException("Calendar item must have an assigned calendar"); + } + + if (string.IsNullOrEmpty(calendarItem.RemoteEventId)) + { + throw new InvalidOperationException("Cannot tentatively accept event without remote event ID"); + } + + var tentativelyAcceptRequestInfo = _graphClient.Me.Calendars[calendar.RemoteCalendarId].Events[calendarItem.RemoteEventId].TentativelyAccept.ToPostRequestInformation(new Microsoft.Graph.Me.Calendars.Item.Events.Item.TentativelyAccept.TentativelyAcceptPostRequestBody + { + Comment = request.ResponseMessage, + SendResponse = !string.IsNullOrEmpty(request.ResponseMessage) + }); + + return [new HttpRequestBundle(tentativelyAcceptRequestInfo, request)]; + } + #endregion public override async Task KillSynchronizerAsync() diff --git a/Wino.Core/Synchronizers/WinoSynchronizer.cs b/Wino.Core/Synchronizers/WinoSynchronizer.cs index eef70a74..54562779 100644 --- a/Wino.Core/Synchronizers/WinoSynchronizer.cs +++ b/Wino.Core/Synchronizers/WinoSynchronizer.cs @@ -364,6 +364,22 @@ public abstract class WinoSynchronizer> CreateCalendarEvent(CreateCalendarEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> AcceptEvent(AcceptEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> DeclineEvent(DeclineEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> OutlookDeclineEvent(OutlookDeclineEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> TentativeEvent(TentativeEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); #endregion diff --git a/Wino.Mail.WinUI/Assets/WinoIcons.ttf b/Wino.Mail.WinUI/Assets/WinoIcons.ttf index ef8acbf8d343ef6c400ec76c0e3a07c7937cc8cd..cd38942cc77ad9a02421e8a17e9fa3bba0424864 100644 GIT binary patch literal 42820 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 diff --git a/Wino.Mail.WinUI/Controls/ControlConstants.cs b/Wino.Mail.WinUI/Controls/ControlConstants.cs index 32a774a5..0e7d8887 100644 --- a/Wino.Mail.WinUI/Controls/ControlConstants.cs +++ b/Wino.Mail.WinUI/Controls/ControlConstants.cs @@ -100,6 +100,15 @@ public static class ControlConstants { WinoIconGlyph.EventJoinOnline, "\uE926" }, { WinoIconGlyph.ViewMessageSource, "\uE943" }, { WinoIconGlyph.Apple, "\uE92B" }, - { WinoIconGlyph.Yahoo, "\uE92C" } + { WinoIconGlyph.Yahoo, "\uE92C" }, + { WinoIconGlyph.People, "\uF007" }, + { WinoIconGlyph.AttachmentNew, "\uF006" }, + { WinoIconGlyph.CalendarSettings, "\uF005" }, + { WinoIconGlyph.SettingsNew, "\uF004" }, + { WinoIconGlyph.ManageAccounts, "\uF003" }, + { WinoIconGlyph.SendNew, "\uF002" }, + { WinoIconGlyph.CalendarShowAs, "\uF001" }, + { WinoIconGlyph.EventDecline, "\uF000" }, + { WinoIconGlyph.Dismiss, "\uF008" }, }; } diff --git a/Wino.Mail.WinUI/Controls/WinoFontIcon.cs b/Wino.Mail.WinUI/Controls/WinoFontIcon.cs index 3901ef45..b934ef2a 100644 --- a/Wino.Mail.WinUI/Controls/WinoFontIcon.cs +++ b/Wino.Mail.WinUI/Controls/WinoFontIcon.cs @@ -101,7 +101,15 @@ public enum WinoIconGlyph EventJoinOnline, ViewMessageSource, Apple, - Yahoo + Yahoo, + People, + AttachmentNew, + CalendarSettings, + SettingsNew, + ManageAccounts, + SendNew, + CalendarShowAs, + Dismiss } public partial class WinoFontIcon : FontIcon diff --git a/Wino.Mail.WinUI/Selectors/RsvpStatusIconTemplateSelector.cs b/Wino.Mail.WinUI/Selectors/RsvpStatusIconTemplateSelector.cs new file mode 100644 index 00000000..fd07abd1 --- /dev/null +++ b/Wino.Mail.WinUI/Selectors/RsvpStatusIconTemplateSelector.cs @@ -0,0 +1,30 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Wino.Core.Domain.Enums; + +namespace Wino.Mail.WinUI.Selectors; + +public partial class RsvpStatusIconTemplateSelector : DataTemplateSelector +{ + public DataTemplate NotRespondedTemplate { get; set; } + public DataTemplate ConfirmedTemplate { get; set; } + public DataTemplate TentativeTemplate { get; set; } + public DataTemplate CancelledTemplate { get; set; } + + protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) + { + if (item is CalendarItemStatus status) + { + return status switch + { + CalendarItemStatus.NotResponded => NotRespondedTemplate, + CalendarItemStatus.Accepted => ConfirmedTemplate, + CalendarItemStatus.Tentative => TentativeTemplate, + CalendarItemStatus.Cancelled => CancelledTemplate, + _ => NotRespondedTemplate + }; + } + + return base.SelectTemplateCore(item, container); + } +} diff --git a/Wino.Mail.WinUI/Styles/DataTemplates.xaml b/Wino.Mail.WinUI/Styles/DataTemplates.xaml index 27725268..e4ec232c 100644 --- a/Wino.Mail.WinUI/Styles/DataTemplates.xaml +++ b/Wino.Mail.WinUI/Styles/DataTemplates.xaml @@ -28,10 +28,11 @@ - + @@ -55,14 +56,15 @@ - + + diff --git a/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml b/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml index be4932d5..23967693 100644 --- a/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml +++ b/Wino.Mail.WinUI/Views/Calendar/EventDetailsPage.xaml @@ -8,16 +8,64 @@ xmlns:calendarHelpers="using:Wino.Calendar.Helpers" xmlns:calendarViewModels="using:Wino.Calendar.ViewModels" xmlns:coreControls="using:Wino.Mail.WinUI.Controls" + xmlns:ctControls="using:CommunityToolkit.WinUI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="using:Wino.Core.Domain" xmlns:enums="using:Wino.Core.Domain.Enums" xmlns:helpers="using:Wino.Helpers" xmlns:local="using:Wino.Calendar.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:selectors="using:Wino.Mail.WinUI.Selectors" Style="{StaticResource PageStyle}" mc:Ignorable="d"> + + + + + + + + + + + + + + + + + + + + + + + + + - -