RSVP options.
This commit is contained in:
@@ -66,4 +66,5 @@ public partial class AccountCalendarViewModel : ObservableObject, IAccountCalend
|
|||||||
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
|
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
|
||||||
}
|
}
|
||||||
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
|
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
|
||||||
|
public MailAccount MailAccount { get => MailAccount; set => MailAccount = value; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
|||||||
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
||||||
[NotifyPropertyChangedFor(nameof(CanEditSeries))]
|
[NotifyPropertyChangedFor(nameof(CanEditSeries))]
|
||||||
[NotifyPropertyChangedFor(nameof(IsCurrentUserOrganizer))]
|
[NotifyPropertyChangedFor(nameof(IsCurrentUserOrganizer))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(CurrentRsvpText))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(CurrentRsvpStatus))]
|
||||||
public partial CalendarItemViewModel CurrentEvent { get; set; }
|
public partial CalendarItemViewModel CurrentEvent { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial CalendarItemViewModel SeriesParent { get; set; }
|
public partial CalendarItemViewModel SeriesParent { get; set; }
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -80,6 +83,45 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
|||||||
|
|
||||||
#endregion
|
#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<RsvpStatusOption> RsvpStatusOptions { get; } = new ObservableCollection<RsvpStatusOption>();
|
||||||
|
|
||||||
|
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,
|
public EventDetailsPageViewModel(ICalendarService calendarService,
|
||||||
INativeAppService nativeAppService,
|
INativeAppService nativeAppService,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService,
|
||||||
@@ -95,6 +137,11 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
|||||||
_navigationService = navigationService;
|
_navigationService = navigationService;
|
||||||
|
|
||||||
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
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)
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
@@ -109,6 +156,17 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
|||||||
await LoadCalendarItemTargetAsync(args);
|
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)
|
private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -265,11 +323,68 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[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;
|
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]
|
[RelayCommand]
|
||||||
@@ -323,3 +438,30 @@ public partial class ReminderOption : ObservableObject
|
|||||||
IsCustom = isCustom;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -258,18 +258,32 @@
|
|||||||
Height="40"
|
Height="40"
|
||||||
DisplayName="{x:Bind Name}" />
|
DisplayName="{x:Bind Name}" />
|
||||||
|
|
||||||
<!-- TODO: Organizer -->
|
<Grid Grid.Column="1" RowSpacing="4">
|
||||||
<Grid Grid.Column="1">
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
FontSize="13"
|
FontSize="13"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
Text="{x:Bind Email}" />
|
Text="{x:Bind Email}" />
|
||||||
|
<Border
|
||||||
|
Grid.Row="2"
|
||||||
|
Padding="6,2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Visibility="{x:Bind IsOrganizer}">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="11"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
|
||||||
|
Text="{x:Bind domain:Translator.CalendarEventDetails_Organizer}" />
|
||||||
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Entities.Calendar;
|
namespace Wino.Core.Domain.Entities.Calendar;
|
||||||
@@ -22,4 +23,7 @@ public class AccountCalendar : IAccountCalendar
|
|||||||
public string TextColorHex { get; set; }
|
public string TextColorHex { get; set; }
|
||||||
public string BackgroundColorHex { get; set; }
|
public string BackgroundColorHex { get; set; }
|
||||||
public string TimeZone { get; set; }
|
public string TimeZone { get; set; }
|
||||||
|
|
||||||
|
[Ignore]
|
||||||
|
public MailAccount MailAccount { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
public enum CalendarItemStatus
|
public enum CalendarItemStatus
|
||||||
{
|
{
|
||||||
NotResponded,
|
NotResponded,
|
||||||
Confirmed,
|
Accepted,
|
||||||
Tentative,
|
Tentative,
|
||||||
Cancelled,
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ public enum CalendarSynchronizerOperation
|
|||||||
CreateEvent,
|
CreateEvent,
|
||||||
UpdateEvent,
|
UpdateEvent,
|
||||||
DeleteEvent,
|
DeleteEvent,
|
||||||
|
AcceptEvent,
|
||||||
|
DeclineEvent,
|
||||||
|
TentativeEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI requests
|
// UI requests
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
@@ -12,4 +13,5 @@ public interface IAccountCalendar
|
|||||||
string RemoteCalendarId { get; set; }
|
string RemoteCalendarId { get; set; }
|
||||||
bool IsExtended { get; set; }
|
bool IsExtended { get; set; }
|
||||||
Guid Id { get; set; }
|
Guid Id { get; set; }
|
||||||
|
MailAccount MailAccount { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ namespace Wino.Core.Domain.Models.Calendar;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encapsulates the options for preparing calendar operation requests.
|
/// Encapsulates the options for preparing calendar operation requests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Operation">Calendar operation to execute (Create, Update, Delete).</param>
|
/// <param name="Operation">Calendar operation to execute (Create, Update, Delete, Accept, Decline, Tentative).</param>
|
||||||
/// <param name="CalendarItem">Calendar item to operate on.</param>
|
/// <param name="CalendarItem">Calendar item to operate on.</param>
|
||||||
/// <param name="Attendees">List of attendees for the calendar event.</param>
|
/// <param name="Attendees">List of attendees for the calendar event.</param>
|
||||||
public record CalendarOperationPreparationRequest(CalendarSynchronizerOperation Operation, CalendarItem CalendarItem, List<CalendarEventAttendee> Attendees);
|
/// <param name="ResponseMessage">Optional message to include with event responses (Accept, Decline, Tentative).</param>
|
||||||
|
public record CalendarOperationPreparationRequest(CalendarSynchronizerOperation Operation, CalendarItem CalendarItem, List<CalendarEventAttendee> Attendees, string ResponseMessage = null);
|
||||||
|
|||||||
@@ -84,14 +84,26 @@
|
|||||||
"CalendarDisplayOptions_Color": "Color",
|
"CalendarDisplayOptions_Color": "Color",
|
||||||
"CalendarDisplayOptions_Expand": "Expand",
|
"CalendarDisplayOptions_Expand": "Expand",
|
||||||
"CalendarEventResponse_Accept": "Accept",
|
"CalendarEventResponse_Accept": "Accept",
|
||||||
|
"CalendarEventResponse_AcceptedResponse": "Accepted",
|
||||||
"CalendarEventResponse_Decline": "Decline",
|
"CalendarEventResponse_Decline": "Decline",
|
||||||
|
"CalendarEventResponse_DeclinedResponse": "Declined",
|
||||||
|
"CalendarEventResponse_NotResponded": "Not Responded",
|
||||||
"CalendarEventResponse_Tentative": "Tentative",
|
"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_Attachments": "Attachments",
|
||||||
"CalendarEventDetails_Details": "Details",
|
"CalendarEventDetails_Details": "Details",
|
||||||
"CalendarEventDetails_EditSeries": "Edit Series",
|
"CalendarEventDetails_EditSeries": "Edit Series",
|
||||||
"CalendarEventDetails_Editing": "Editing",
|
"CalendarEventDetails_Editing": "Editing",
|
||||||
"CalendarEventDetails_InviteSomeone": "Invite someone",
|
"CalendarEventDetails_InviteSomeone": "Invite someone",
|
||||||
"CalendarEventDetails_JoinOnline": "Join Online",
|
"CalendarEventDetails_JoinOnline": "Join Online",
|
||||||
|
"CalendarEventDetails_Organizer": "Organizer",
|
||||||
"CalendarEventDetails_People": "People",
|
"CalendarEventDetails_People": "People",
|
||||||
"CalendarEventDetails_ReadOnlyEvent": "Read-only event",
|
"CalendarEventDetails_ReadOnlyEvent": "Read-only event",
|
||||||
"CalendarEventDetails_Reminder": "Reminder",
|
"CalendarEventDetails_Reminder": "Reminder",
|
||||||
|
|||||||
@@ -349,10 +349,10 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
|||||||
{
|
{
|
||||||
return status switch
|
return status switch
|
||||||
{
|
{
|
||||||
"confirmed" => CalendarItemStatus.Confirmed,
|
"confirmed" => CalendarItemStatus.Accepted,
|
||||||
"tentative" => CalendarItemStatus.Tentative,
|
"tentative" => CalendarItemStatus.Tentative,
|
||||||
"cancelled" => CalendarItemStatus.Cancelled,
|
"cancelled" => CalendarItemStatus.Cancelled,
|
||||||
_ => CalendarItemStatus.Confirmed
|
_ => CalendarItemStatus.Accepted
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
|
|||||||
break;
|
break;
|
||||||
case ResponseType.Accepted:
|
case ResponseType.Accepted:
|
||||||
case ResponseType.Organizer:
|
case ResponseType.Organizer:
|
||||||
savingItem.Status = CalendarItemStatus.Confirmed;
|
savingItem.Status = CalendarItemStatus.Accepted;
|
||||||
break;
|
break;
|
||||||
case ResponseType.Declined:
|
case ResponseType.Declined:
|
||||||
savingItem.Status = CalendarItemStatus.Cancelled;
|
savingItem.Status = CalendarItemStatus.Cancelled;
|
||||||
@@ -164,7 +164,7 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
savingItem.Status = CalendarItemStatus.Confirmed;
|
savingItem.Status = CalendarItemStatus.Accepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare attendees list
|
// Prepare attendees list
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request to accept a calendar event invitation on the server.
|
||||||
|
/// The calendar item status should be updated locally before queuing this request.
|
||||||
|
/// </summary>
|
||||||
|
public record AcceptEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item)
|
||||||
|
{
|
||||||
|
private readonly CalendarItemStatus _previousStatus = Item.Status;
|
||||||
|
|
||||||
|
public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.AcceptEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// After successful acceptance, we need to resync to get updated status.
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request to decline a calendar event invitation on the server.
|
||||||
|
/// The calendar item status should be updated locally before queuing this request.
|
||||||
|
/// </summary>
|
||||||
|
public record DeclineEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item)
|
||||||
|
{
|
||||||
|
private readonly CalendarItemStatus _previousStatus = Item.Status;
|
||||||
|
|
||||||
|
public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.DeclineEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// After successful decline, we need to resync to get updated status.
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public record OutlookDeclineEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item)
|
||||||
|
{
|
||||||
|
private readonly CalendarItemStatus _previousStatus = Item.Status;
|
||||||
|
|
||||||
|
public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.DeclineEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// After successful decline, we need to resync to confirm the event is removed.
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request to tentatively accept a calendar event invitation on the server.
|
||||||
|
/// The calendar item status should be updated locally before queuing this request.
|
||||||
|
/// </summary>
|
||||||
|
public record TentativeEventRequest(CalendarItem Item, string ResponseMessage = null) : CalendarRequestBase(Item)
|
||||||
|
{
|
||||||
|
private readonly CalendarItemStatus _previousStatus = Item.Status;
|
||||||
|
|
||||||
|
public override CalendarSynchronizerOperation Operation => CalendarSynchronizerOperation.TentativeEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// After successful tentative acceptance, we need to resync to get updated status.
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -141,6 +142,9 @@ public class WinoRequestDelegator : IWinoRequestDelegator
|
|||||||
{
|
{
|
||||||
CalendarSynchronizerOperation.CreateEvent => new CreateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees),
|
CalendarSynchronizerOperation.CreateEvent => new CreateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees),
|
||||||
CalendarSynchronizerOperation.DeleteEvent => new DeleteCalendarEventRequest(calendarPreparationRequest.CalendarItem),
|
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
|
// Future support for update operations
|
||||||
// CalendarSynchronizerOperation.UpdateEvent => new UpdateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees),
|
// CalendarSynchronizerOperation.UpdateEvent => new UpdateCalendarEventRequest(calendarPreparationRequest.CalendarItem, calendarPreparationRequest.Attendees),
|
||||||
_ => throw new NotImplementedException($"Calendar operation {calendarPreparationRequest.Operation} is not implemented yet.")
|
_ => 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);
|
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)
|
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
{
|
{
|
||||||
// Don't trigger synchronization for individual requests - we'll trigger it once for all requests
|
// Don't trigger synchronization for individual requests - we'll trigger it once for all requests
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Web;
|
using System.Web;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Google;
|
using Google;
|
||||||
|
using Google.Apis.Calendar.v3;
|
||||||
using Google.Apis.Calendar.v3.Data;
|
using Google.Apis.Calendar.v3.Data;
|
||||||
using Google.Apis.Gmail.v1;
|
using Google.Apis.Gmail.v1;
|
||||||
using Google.Apis.Gmail.v1.Data;
|
using Google.Apis.Gmail.v1.Data;
|
||||||
@@ -1648,7 +1649,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
Summary = calendarItem.Title,
|
Summary = calendarItem.Title,
|
||||||
Description = calendarItem.Description,
|
Description = calendarItem.Description,
|
||||||
Location = calendarItem.Location,
|
Location = calendarItem.Location,
|
||||||
Status = calendarItem.Status == CalendarItemStatus.Confirmed ? "confirmed" : "tentative"
|
Status = calendarItem.Status == CalendarItemStatus.Accepted ? "confirmed" : "tentative"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set start and end time
|
// Set start and end time
|
||||||
@@ -1696,6 +1697,125 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
return [new HttpRequestBundle<IClientServiceRequest>(insertRequest, request)];
|
return [new HttpRequestBundle<IClientServiceRequest>(insertRequest, request)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<IClientServiceRequest>> 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<EventAttendee>
|
||||||
|
{
|
||||||
|
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<IClientServiceRequest>(patchRequest, request)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<IClientServiceRequest>> 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<EventAttendee>
|
||||||
|
{
|
||||||
|
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<IClientServiceRequest>(patchRequest, request)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<IClientServiceRequest>> 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<EventAttendee>
|
||||||
|
{
|
||||||
|
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<IClientServiceRequest>(patchRequest, request)];
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public override async Task KillSynchronizerAsync()
|
public override async Task KillSynchronizerAsync()
|
||||||
|
|||||||
@@ -1680,6 +1680,9 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
|
|
||||||
foreach (var item in events)
|
foreach (var item in events)
|
||||||
{
|
{
|
||||||
|
// Declined events are returned as Deleted from the API.
|
||||||
|
// There is no way to distinguish unfortunately atm.
|
||||||
|
|
||||||
if (IsResourceDeleted(item.AdditionalData))
|
if (IsResourceDeleted(item.AdditionalData))
|
||||||
{
|
{
|
||||||
await _outlookChangeProcessor.DeleteCalendarItemAsync(item.Id, calendar.Id).ConfigureAwait(false);
|
await _outlookChangeProcessor.DeleteCalendarItemAsync(item.Id, calendar.Id).ConfigureAwait(false);
|
||||||
@@ -1886,6 +1889,80 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
return [new HttpRequestBundle<RequestInformation>(createRequest, request)];
|
return [new HttpRequestBundle<RequestInformation>(createRequest, request)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<RequestInformation>> 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<RequestInformation>(acceptRequestInfo, request)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<RequestInformation>> 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<RequestInformation>(declineRequestInfo, request)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<RequestInformation>> 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<RequestInformation>(tentativelyAcceptRequestInfo, request)];
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public override async Task KillSynchronizerAsync()
|
public override async Task KillSynchronizerAsync()
|
||||||
|
|||||||
@@ -364,6 +364,22 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
case CalendarSynchronizerOperation.CreateEvent:
|
case CalendarSynchronizerOperation.CreateEvent:
|
||||||
nativeRequests.AddRange(CreateCalendarEvent(group.ElementAt(0) as CreateCalendarEventRequest));
|
nativeRequests.AddRange(CreateCalendarEvent(group.ElementAt(0) as CreateCalendarEventRequest));
|
||||||
break;
|
break;
|
||||||
|
case CalendarSynchronizerOperation.AcceptEvent:
|
||||||
|
nativeRequests.AddRange(AcceptEvent(group.ElementAt(0) as AcceptEventRequest));
|
||||||
|
break;
|
||||||
|
case CalendarSynchronizerOperation.DeclineEvent:
|
||||||
|
if (Account.ProviderType == MailProviderType.Outlook)
|
||||||
|
{
|
||||||
|
nativeRequests.AddRange(OutlookDeclineEvent(group.ElementAt(0) as OutlookDeclineEventRequest));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nativeRequests.AddRange(DeclineEvent(group.ElementAt(0) as DeclineEventRequest));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CalendarSynchronizerOperation.TentativeEvent:
|
||||||
|
nativeRequests.AddRange(TentativeEvent(group.ElementAt(0) as TentativeEventRequest));
|
||||||
|
break;
|
||||||
case CalendarSynchronizerOperation.UpdateEvent:
|
case CalendarSynchronizerOperation.UpdateEvent:
|
||||||
// TODO: Implement UpdateCalendarEvent
|
// TODO: Implement UpdateCalendarEvent
|
||||||
break;
|
break;
|
||||||
@@ -494,6 +510,10 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
|||||||
#region Calendar Operations
|
#region Calendar Operations
|
||||||
|
|
||||||
public virtual List<IRequestBundle<TBaseRequest>> CreateCalendarEvent(CreateCalendarEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
|
public virtual List<IRequestBundle<TBaseRequest>> CreateCalendarEvent(CreateCalendarEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
|
||||||
|
public virtual List<IRequestBundle<TBaseRequest>> AcceptEvent(AcceptEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
|
||||||
|
public virtual List<IRequestBundle<TBaseRequest>> DeclineEvent(DeclineEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
|
||||||
|
public virtual List<IRequestBundle<TBaseRequest>> OutlookDeclineEvent(OutlookDeclineEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
|
||||||
|
public virtual List<IRequestBundle<TBaseRequest>> TentativeEvent(TentativeEventRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType()));
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -100,6 +100,15 @@ public static class ControlConstants
|
|||||||
{ WinoIconGlyph.EventJoinOnline, "\uE926" },
|
{ WinoIconGlyph.EventJoinOnline, "\uE926" },
|
||||||
{ WinoIconGlyph.ViewMessageSource, "\uE943" },
|
{ WinoIconGlyph.ViewMessageSource, "\uE943" },
|
||||||
{ WinoIconGlyph.Apple, "\uE92B" },
|
{ 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" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,15 @@ public enum WinoIconGlyph
|
|||||||
EventJoinOnline,
|
EventJoinOnline,
|
||||||
ViewMessageSource,
|
ViewMessageSource,
|
||||||
Apple,
|
Apple,
|
||||||
Yahoo
|
Yahoo,
|
||||||
|
People,
|
||||||
|
AttachmentNew,
|
||||||
|
CalendarSettings,
|
||||||
|
SettingsNew,
|
||||||
|
ManageAccounts,
|
||||||
|
SendNew,
|
||||||
|
CalendarShowAs,
|
||||||
|
Dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class WinoFontIcon : FontIcon
|
public partial class WinoFontIcon : FontIcon
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -8,16 +8,64 @@
|
|||||||
xmlns:calendarHelpers="using:Wino.Calendar.Helpers"
|
xmlns:calendarHelpers="using:Wino.Calendar.Helpers"
|
||||||
xmlns:calendarViewModels="using:Wino.Calendar.ViewModels"
|
xmlns:calendarViewModels="using:Wino.Calendar.ViewModels"
|
||||||
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
||||||
|
xmlns:ctControls="using:CommunityToolkit.WinUI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:domain="using:Wino.Core.Domain"
|
xmlns:domain="using:Wino.Core.Domain"
|
||||||
xmlns:enums="using:Wino.Core.Domain.Enums"
|
xmlns:enums="using:Wino.Core.Domain.Enums"
|
||||||
xmlns:helpers="using:Wino.Helpers"
|
xmlns:helpers="using:Wino.Helpers"
|
||||||
xmlns:local="using:Wino.Calendar.Views"
|
xmlns:local="using:Wino.Calendar.Views"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:selectors="using:Wino.Mail.WinUI.Selectors"
|
||||||
Style="{StaticResource PageStyle}"
|
Style="{StaticResource PageStyle}"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Page.Resources>
|
<Page.Resources>
|
||||||
|
<selectors:RsvpStatusIconTemplateSelector x:Key="RsvpStatusIconSelector">
|
||||||
|
<selectors:RsvpStatusIconTemplateSelector.NotRespondedTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<coreControls:WinoFontIcon
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="{ThemeResource SystemAccentColor}"
|
||||||
|
Icon="EventRespond" />
|
||||||
|
</DataTemplate>
|
||||||
|
</selectors:RsvpStatusIconTemplateSelector.NotRespondedTemplate>
|
||||||
|
<selectors:RsvpStatusIconTemplateSelector.ConfirmedTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<coreControls:WinoFontIcon
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="#527257"
|
||||||
|
Icon="EventAccept" />
|
||||||
|
</DataTemplate>
|
||||||
|
</selectors:RsvpStatusIconTemplateSelector.ConfirmedTemplate>
|
||||||
|
<selectors:RsvpStatusIconTemplateSelector.TentativeTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<coreControls:WinoFontIcon
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="#805682"
|
||||||
|
Icon="EventTentative" />
|
||||||
|
</DataTemplate>
|
||||||
|
</selectors:RsvpStatusIconTemplateSelector.TentativeTemplate>
|
||||||
|
<selectors:RsvpStatusIconTemplateSelector.CancelledTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<coreControls:WinoFontIcon
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="{ThemeResource DeleteBrush}"
|
||||||
|
Icon="EventDecline" />
|
||||||
|
</DataTemplate>
|
||||||
|
</selectors:RsvpStatusIconTemplateSelector.CancelledTemplate>
|
||||||
|
</selectors:RsvpStatusIconTemplateSelector>
|
||||||
|
|
||||||
|
<Style
|
||||||
|
x:Key="TransparentActionButtonStyle"
|
||||||
|
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||||
|
TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Padding" Value="12,8" />
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="MinHeight" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="ActionBarElementContainerStackStyle" TargetType="StackPanel">
|
<Style x:Key="ActionBarElementContainerStackStyle" TargetType="StackPanel">
|
||||||
<Setter Property="Spacing" Value="6" />
|
<Setter Property="Spacing" Value="6" />
|
||||||
<Setter Property="Padding" Value="10,0,4,0" />
|
<Setter Property="Padding" Value="10,0,4,0" />
|
||||||
@@ -25,10 +73,6 @@
|
|||||||
<Setter Property="Orientation" Value="Horizontal" />
|
<Setter Property="Orientation" Value="Horizontal" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style TargetType="AppBarElementContainer">
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="EventDetailsPanelGridStyle" TargetType="Grid">
|
<Style x:Key="EventDetailsPanelGridStyle" TargetType="Grid">
|
||||||
<Setter Property="Padding" Value="12,6" />
|
<Setter Property="Padding" Value="12,6" />
|
||||||
<Setter Property="Margin" Value="0,12" />
|
<Setter Property="Margin" Value="0,12" />
|
||||||
@@ -46,6 +90,7 @@
|
|||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
<Grid Padding="20">
|
<Grid Padding="20">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
@@ -57,92 +102,100 @@
|
|||||||
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="7">
|
CornerRadius="7">
|
||||||
<CommandBar
|
<Grid Padding="8">
|
||||||
HorizontalAlignment="Left"
|
<ctControls:WrapPanel
|
||||||
Background="Transparent"
|
x:Name="ActionBarWrapGrid"
|
||||||
DefaultLabelPosition="Right"
|
HorizontalAlignment="Left"
|
||||||
IsSticky="True"
|
HorizontalSpacing="4"
|
||||||
OverflowButtonVisibility="Auto">
|
VerticalSpacing="4">
|
||||||
<AppBarToggleButton
|
|
||||||
x:Name="ReadOnlyToggle"
|
|
||||||
Content="{x:Bind domain:Translator.CalendarEventDetails_ReadOnlyEvent}"
|
|
||||||
IsChecked="True" />
|
|
||||||
<AppBarButton Command="{x:Bind ViewModel.SaveCommand}" Label="{x:Bind domain:Translator.Buttons_Save}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<coreControls:WinoFontIcon Icon="Save" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
<AppBarButton Command="{x:Bind ViewModel.DeleteCommand}" Label="{x:Bind domain:Translator.Buttons_Delete}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<coreControls:WinoFontIcon Icon="Delete" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
<!-- Read Only Toggle -->
|
||||||
|
<ToggleButton
|
||||||
|
x:Name="ReadOnlyToggle"
|
||||||
|
IsChecked="True"
|
||||||
|
Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon FontSize="16" Icon="Blocked" />
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventDetails_ReadOnlyEvent}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ToggleButton>
|
||||||
|
|
||||||
<!-- Join Online -->
|
<!-- Save -->
|
||||||
<AppBarButton
|
<Button Command="{x:Bind ViewModel.SaveCommand}" Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
Command="{x:Bind ViewModel.JoinOnlineCommand}"
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
IsEnabled="{x:Bind calendarHelpers:CalendarXamlHelpers.HasOnlineMeetingLink(ViewModel.CurrentEvent), Mode=OneWay}"
|
<coreControls:WinoFontIcon FontSize="16" Icon="Save" />
|
||||||
Label="{x:Bind domain:Translator.CalendarEventDetails_JoinOnline}">
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Buttons_Save}" />
|
||||||
<AppBarButton.Icon>
|
</StackPanel>
|
||||||
<coreControls:WinoFontIcon Icon="EventJoinOnline" />
|
</Button>
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
<!-- Delete -->
|
||||||
|
<Button Command="{x:Bind ViewModel.DeleteCommand}" Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon FontSize="16" Icon="Delete" />
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Buttons_Delete}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<!-- Response Options -->
|
<Border
|
||||||
|
Width="1"
|
||||||
|
Height="24"
|
||||||
|
Margin="4,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||||
|
|
||||||
<AppBarButton
|
<!-- Join Online -->
|
||||||
Command="{x:Bind ViewModel.RespondCommand}"
|
<Button
|
||||||
CommandParameter="{x:Bind enums:CalendarItemStatus.Confirmed}"
|
Command="{x:Bind ViewModel.JoinOnlineCommand}"
|
||||||
Label="{x:Bind domain:Translator.CalendarEventResponse_Accept}">
|
IsEnabled="{x:Bind calendarHelpers:CalendarXamlHelpers.HasOnlineMeetingLink(ViewModel.CurrentEvent), Mode=OneWay}"
|
||||||
<AppBarButton.Icon>
|
Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
<coreControls:WinoFontIcon Foreground="#527257" Icon="EventAccept" />
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
</AppBarButton.Icon>
|
<coreControls:WinoFontIcon FontSize="16" Icon="EventJoinOnline" />
|
||||||
</AppBarButton>
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventDetails_JoinOnline}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<AppBarButton
|
<Border
|
||||||
Command="{x:Bind ViewModel.RespondCommand}"
|
Width="1"
|
||||||
CommandParameter="{x:Bind enums:CalendarItemStatus.Tentative}"
|
Height="24"
|
||||||
Label="{x:Bind domain:Translator.CalendarEventResponse_Tentative}">
|
Margin="4,0"
|
||||||
<AppBarButton.Icon>
|
VerticalAlignment="Center"
|
||||||
<coreControls:WinoFontIcon Foreground="#805682" Icon="EventTentative" />
|
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarButton
|
<!-- RSVP Button -->
|
||||||
Command="{x:Bind ViewModel.RespondCommand}"
|
<Button Command="{x:Bind ViewModel.ToggleRsvpPanelCommand}" Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
CommandParameter="{x:Bind enums:CalendarItemStatus.Cancelled}"
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
Label="{x:Bind domain:Translator.CalendarEventResponse_Decline}">
|
<ContentControl Content="{x:Bind ViewModel.CurrentRsvpStatus, Mode=OneWay}" ContentTemplateSelector="{StaticResource RsvpStatusIconSelector}" />
|
||||||
<AppBarButton.Icon>
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CurrentRsvpText, Mode=OneWay}" />
|
||||||
<coreControls:WinoFontIcon Foreground="#805682" Icon="EventRespond" />
|
</StackPanel>
|
||||||
</AppBarButton.Icon>
|
</Button>
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
<Border
|
||||||
|
Width="1"
|
||||||
|
Height="24"
|
||||||
|
Margin="4,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||||
|
|
||||||
<!-- Show as -->
|
<!-- Show as -->
|
||||||
<AppBarElementContainer>
|
<StackPanel
|
||||||
<StackPanel Style="{StaticResource ActionBarElementContainerStackStyle}">
|
Padding="12,0"
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventDetails_ShowAs}" />
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon FontSize="20" Icon="CalendarShowAs" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
Width="150"
|
Width="150"
|
||||||
|
VerticalAlignment="Center"
|
||||||
ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
|
ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
|
||||||
SelectedItem="{x:Bind ViewModel.SelectedShowAs, Mode=TwoWay}" />
|
SelectedItem="{x:Bind ViewModel.SelectedShowAs, Mode=TwoWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</AppBarElementContainer>
|
|
||||||
|
|
||||||
<!-- Reminder -->
|
<!-- Reminder -->
|
||||||
<AppBarElementContainer>
|
<Button Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
<Button>
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<Button.Content>
|
<coreControls:WinoFontIcon FontSize="16" Icon="Reminder" />
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventDetails_Reminder}" />
|
||||||
<coreControls:WinoFontIcon FontSize="16" Icon="Reminder" />
|
</StackPanel>
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventDetails_Reminder}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button.Content>
|
|
||||||
<Button.Flyout>
|
<Button.Flyout>
|
||||||
<Flyout>
|
<Flyout>
|
||||||
<ListView
|
<ListView
|
||||||
@@ -159,24 +212,124 @@
|
|||||||
</Flyout>
|
</Flyout>
|
||||||
</Button.Flyout>
|
</Button.Flyout>
|
||||||
</Button>
|
</Button>
|
||||||
</AppBarElementContainer>
|
|
||||||
|
|
||||||
<AppBarSeparator Visibility="{x:Bind ViewModel.CanEditSeries, Mode=OneWay}" />
|
<!-- Edit Series -->
|
||||||
|
<Border
|
||||||
|
Width="1"
|
||||||
|
Height="24"
|
||||||
|
Margin="4,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||||
|
Visibility="{x:Bind ViewModel.CanEditSeries, Mode=OneWay}" />
|
||||||
|
|
||||||
<!-- Edit Series -->
|
<Button
|
||||||
<AppBarButton
|
Command="{x:Bind ViewModel.ViewSeriesCommand}"
|
||||||
Command="{x:Bind ViewModel.ViewSeriesCommand}"
|
Style="{StaticResource TransparentActionButtonStyle}"
|
||||||
Label="{x:Bind domain:Translator.CalendarEventDetails_EditSeries}"
|
Visibility="{x:Bind ViewModel.CanEditSeries, Mode=OneWay}">
|
||||||
Visibility="{x:Bind ViewModel.CanEditSeries, Mode=OneWay}">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<AppBarButton.Icon>
|
<coreControls:WinoFontIcon FontSize="16" Icon="EventEditSeries" />
|
||||||
<coreControls:WinoFontIcon Icon="EventEditSeries" />
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventDetails_EditSeries}" />
|
||||||
</AppBarButton.Icon>
|
</StackPanel>
|
||||||
</AppBarButton>
|
</Button>
|
||||||
</CommandBar>
|
</ctControls:WrapPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- RSVP Panel -->
|
||||||
|
<Border
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
Padding="16"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||||
|
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="7"
|
||||||
|
Visibility="{x:Bind ViewModel.IsRsvpPanelVisible, Mode=OneWay}">
|
||||||
|
<Grid RowSpacing="6">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Close Button -->
|
||||||
|
<Button
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Command="{x:Bind ViewModel.CloseRsvpPanelCommand}"
|
||||||
|
Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
|
<coreControls:WinoFontIcon FontSize="16" Icon="Dismiss" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- RSVP Buttons -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
|
<!-- Accept -->
|
||||||
|
<Button
|
||||||
|
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
|
||||||
|
CommandParameter="{x:Bind enums:AttendeeStatus.Accepted}"
|
||||||
|
Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="#527257"
|
||||||
|
Icon="EventAccept" />
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventRsvpPanel_Accept, Mode=OneTime}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Tentative -->
|
||||||
|
<Button
|
||||||
|
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
|
||||||
|
CommandParameter="{x:Bind enums:AttendeeStatus.Tentative}"
|
||||||
|
Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="#805682"
|
||||||
|
Icon="EventTentative" />
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventRsvpPanel_Tentative, Mode=OneTime}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Decline -->
|
||||||
|
<Button
|
||||||
|
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
|
||||||
|
CommandParameter="{x:Bind enums:AttendeeStatus.Declined}"
|
||||||
|
Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="{ThemeResource DeleteBrush}"
|
||||||
|
Icon="EventDecline" />
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.CalendarEventRsvpPanel_Decline, Mode=OneTime}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Reply Message TextBox -->
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
PlaceholderText="{x:Bind domain:Translator.CalendarEventRsvpPanel_AddMessage, Mode=OneTime}"
|
||||||
|
Text="{x:Bind ViewModel.RsvpMessage, Mode=TwoWay}"
|
||||||
|
TextWrapping="Wrap">
|
||||||
|
<TextBox.Header>
|
||||||
|
<StackPanel Orientation="Horizontal" />
|
||||||
|
</TextBox.Header>
|
||||||
|
</TextBox>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Event details -->
|
<!-- Event details -->
|
||||||
<ScrollViewer Grid.Row="1">
|
<ScrollViewer Grid.Row="2">
|
||||||
<Grid ColumnSpacing="8">
|
<Grid ColumnSpacing="8">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="2*" />
|
<ColumnDefinition Width="2*" />
|
||||||
@@ -293,18 +446,32 @@
|
|||||||
Height="40"
|
Height="40"
|
||||||
DisplayName="{x:Bind Name}" />
|
DisplayName="{x:Bind Name}" />
|
||||||
|
|
||||||
<!-- TODO: Organizer -->
|
<Grid Grid.Column="1" RowSpacing="4">
|
||||||
<Grid Grid.Column="1">
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
FontSize="13"
|
FontSize="13"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
Text="{x:Bind Email}" />
|
Text="{x:Bind Email}" />
|
||||||
|
<Border
|
||||||
|
Grid.Row="2"
|
||||||
|
Padding="6,2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Visibility="{x:Bind IsOrganizer}">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="11"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
|
||||||
|
Text="{x:Bind domain:Translator.CalendarEventDetails_Organizer}" />
|
||||||
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
@@ -328,5 +495,50 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup x:Name="WindowWidthStates">
|
||||||
|
<VisualState x:Name="WideState">
|
||||||
|
<VisualState.StateTriggers>
|
||||||
|
<AdaptiveTrigger MinWindowWidth="1200" />
|
||||||
|
</VisualState.StateTriggers>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="MediumState">
|
||||||
|
<VisualState.StateTriggers>
|
||||||
|
<AdaptiveTrigger MinWindowWidth="800" />
|
||||||
|
</VisualState.StateTriggers>
|
||||||
|
<VisualState.Setters>
|
||||||
|
<!-- Details takes left side (full height), People and Attachments share right side vertically -->
|
||||||
|
<Setter Target="DetailsGrid.(Grid.Row)" Value="0" />
|
||||||
|
<Setter Target="DetailsGrid.(Grid.Column)" Value="0" />
|
||||||
|
<Setter Target="DetailsGrid.(Grid.RowSpan)" Value="2" />
|
||||||
|
<Setter Target="DetailsGrid.(Grid.ColumnSpan)" Value="1" />
|
||||||
|
<Setter Target="PeopleGrid.(Grid.Row)" Value="0" />
|
||||||
|
<Setter Target="PeopleGrid.(Grid.Column)" Value="1" />
|
||||||
|
<Setter Target="PeopleGrid.(Grid.ColumnSpan)" Value="2" />
|
||||||
|
<Setter Target="AttachmentsGrid.(Grid.Row)" Value="1" />
|
||||||
|
<Setter Target="AttachmentsGrid.(Grid.Column)" Value="1" />
|
||||||
|
<Setter Target="AttachmentsGrid.(Grid.ColumnSpan)" Value="2" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="NarrowState">
|
||||||
|
<VisualState.StateTriggers>
|
||||||
|
<AdaptiveTrigger MinWindowWidth="0" />
|
||||||
|
</VisualState.StateTriggers>
|
||||||
|
<VisualState.Setters>
|
||||||
|
<!-- Stack all panels vertically -->
|
||||||
|
<Setter Target="DetailsGrid.(Grid.Row)" Value="0" />
|
||||||
|
<Setter Target="DetailsGrid.(Grid.Column)" Value="0" />
|
||||||
|
<Setter Target="DetailsGrid.(Grid.ColumnSpan)" Value="3" />
|
||||||
|
<Setter Target="PeopleGrid.(Grid.Row)" Value="1" />
|
||||||
|
<Setter Target="PeopleGrid.(Grid.Column)" Value="0" />
|
||||||
|
<Setter Target="PeopleGrid.(Grid.ColumnSpan)" Value="3" />
|
||||||
|
<Setter Target="AttachmentsGrid.(Grid.Row)" Value="2" />
|
||||||
|
<Setter Target="AttachmentsGrid.(Grid.Column)" Value="0" />
|
||||||
|
<Setter Target="AttachmentsGrid.(Grid.ColumnSpan)" Value="3" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
</Grid>
|
</Grid>
|
||||||
</abstract:EventDetailsPageAbstract>
|
</abstract:EventDetailsPageAbstract>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Itenso.TimePeriod;
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
@@ -253,14 +254,33 @@ public class CalendarService : BaseDatabaseService, ICalendarService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId)
|
public async Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId)
|
||||||
=> Connection.GetAsync<AccountCalendar>(accountCalendarId);
|
|
||||||
|
|
||||||
public Task<CalendarItem> GetCalendarItemAsync(Guid id)
|
|
||||||
{
|
{
|
||||||
return Connection.FindWithQueryAsync<CalendarItem>(
|
var calendar = await Connection.GetAsync<AccountCalendar>(accountCalendarId);
|
||||||
|
if (calendar != null)
|
||||||
|
{
|
||||||
|
calendar.MailAccount = await Connection.GetAsync<MailAccount>(calendar.AccountId);
|
||||||
|
}
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CalendarItem> GetCalendarItemAsync(Guid id)
|
||||||
|
{
|
||||||
|
var calendarItem = await Connection.FindWithQueryAsync<CalendarItem>(
|
||||||
"SELECT * FROM CalendarItem WHERE Id = ?",
|
"SELECT * FROM CalendarItem WHERE Id = ?",
|
||||||
id);
|
id);
|
||||||
|
|
||||||
|
// Load assigned calendar and account.
|
||||||
|
if (calendarItem != null)
|
||||||
|
{
|
||||||
|
calendarItem.AssignedCalendar = await Connection.GetAsync<AccountCalendar>(calendarItem.CalendarId);
|
||||||
|
if (calendarItem.AssignedCalendar != null)
|
||||||
|
{
|
||||||
|
calendarItem.AssignedCalendar.MailAccount = await Connection.GetAsync<MailAccount>(calendarItem.AssignedCalendar.AccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calendarItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId)
|
public async Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId)
|
||||||
@@ -269,10 +289,14 @@ public class CalendarService : BaseDatabaseService, ICalendarService
|
|||||||
"SELECT * FROM CalendarItem WHERE CalendarId = ? AND RemoteEventId = ?",
|
"SELECT * FROM CalendarItem WHERE CalendarId = ? AND RemoteEventId = ?",
|
||||||
accountCalendarId, remoteEventId);
|
accountCalendarId, remoteEventId);
|
||||||
|
|
||||||
// Load assigned calendar.
|
// Load assigned calendar and account.
|
||||||
if (calendarItem != null)
|
if (calendarItem != null)
|
||||||
{
|
{
|
||||||
calendarItem.AssignedCalendar = await Connection.GetAsync<AccountCalendar>(calendarItem.CalendarId);
|
calendarItem.AssignedCalendar = await Connection.GetAsync<AccountCalendar>(calendarItem.CalendarId);
|
||||||
|
if (calendarItem.AssignedCalendar != null)
|
||||||
|
{
|
||||||
|
calendarItem.AssignedCalendar.MailAccount = await Connection.GetAsync<MailAccount>(calendarItem.AssignedCalendar.AccountId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calendarItem;
|
return calendarItem;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -10,6 +10,7 @@
|
|||||||
<File Path="Directory.Packages.props" />
|
<File Path="Directory.Packages.props" />
|
||||||
<File Path="nuget.config" />
|
<File Path="nuget.config" />
|
||||||
<File Path="Settings.XamlStyler" />
|
<File Path="Settings.XamlStyler" />
|
||||||
|
<File Path="WinoFontIcomoon.json" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/lib/">
|
<Folder Name="/lib/">
|
||||||
<Project Path="Wino.Authentication/Wino.Authentication.csproj">
|
<Project Path="Wino.Authentication/Wino.Authentication.csproj">
|
||||||
|
|||||||
Reference in New Issue
Block a user