Calendar item context flyout implementation

This commit is contained in:
Burak Kaan Köse
2026-04-08 23:46:55 +02:00
parent 3dc4ac03ec
commit d6049bbd88
14 changed files with 602 additions and 12 deletions
@@ -35,6 +35,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
IRecipient<CalendarItemTappedMessage>,
IRecipient<CalendarItemDoubleTappedMessage>,
IRecipient<CalendarItemRightTappedMessage>,
IRecipient<CalendarItemContextActionRequestedMessage>,
IRecipient<AccountRemovedMessage>,
IDisposable
{
@@ -229,6 +230,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
Messenger.Unregister<CalendarItemTappedMessage>(this);
Messenger.Unregister<CalendarItemDoubleTappedMessage>(this);
Messenger.Unregister<CalendarItemRightTappedMessage>(this);
Messenger.Unregister<CalendarItemContextActionRequestedMessage>(this);
Messenger.Unregister<AccountRemovedMessage>(this);
Messenger.Register<LoadCalendarMessage>(this);
@@ -236,6 +238,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
Messenger.Register<CalendarItemTappedMessage>(this);
Messenger.Register<CalendarItemDoubleTappedMessage>(this);
Messenger.Register<CalendarItemRightTappedMessage>(this);
Messenger.Register<CalendarItemContextActionRequestedMessage>(this);
Messenger.Register<AccountRemovedMessage>(this);
}
@@ -248,6 +251,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
Messenger.Unregister<CalendarItemTappedMessage>(this);
Messenger.Unregister<CalendarItemDoubleTappedMessage>(this);
Messenger.Unregister<CalendarItemRightTappedMessage>(this);
Messenger.Unregister<CalendarItemContextActionRequestedMessage>(this);
Messenger.Unregister<AccountRemovedMessage>(this);
}
@@ -802,6 +806,24 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
public void Receive(CalendarItemRightTappedMessage message)
{
if (message.CalendarItemViewModel == null)
return;
DisplayDetailsCalendarItemViewModel = message.CalendarItemViewModel;
}
public void Receive(CalendarItemContextActionRequestedMessage message)
{
if (message.CalendarItemViewModel == null)
return;
if (message.Action.ActionType == CalendarContextMenuActionType.Open)
{
NavigateEvent(message.CalendarItemViewModel, message.Action.TargetType ?? CalendarEventTargetType.Single);
return;
}
_ = ExecuteContextActionAsync(message.CalendarItemViewModel, message.Action);
}
public async void Receive(AccountRemovedMessage message)
@@ -1122,6 +1144,127 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
return trackedLocalItemId.HasValue && pendingCalendarItemIds.Contains(trackedLocalItemId.Value);
}
private async Task ExecuteContextActionAsync(CalendarItemViewModel calendarItemViewModel, CalendarContextMenuAction action)
{
switch (action.ActionType)
{
case CalendarContextMenuActionType.JoinOnline:
await JoinOnlineAsync(calendarItemViewModel).ConfigureAwait(false);
break;
case CalendarContextMenuActionType.Delete:
await DeleteCalendarItemAsync(calendarItemViewModel, action.TargetType ?? CalendarEventTargetType.Single).ConfigureAwait(false);
break;
case CalendarContextMenuActionType.ShowAs when action.ShowAs.HasValue:
await UpdateShowAsAsync(calendarItemViewModel, action.TargetType ?? CalendarEventTargetType.Single, action.ShowAs.Value).ConfigureAwait(false);
break;
case CalendarContextMenuActionType.Respond when action.ResponseStatus.HasValue:
await RespondToCalendarItemAsync(calendarItemViewModel, action.TargetType ?? CalendarEventTargetType.Single, action.ResponseStatus.Value).ConfigureAwait(false);
break;
}
}
private Task JoinOnlineAsync(CalendarItemViewModel calendarItemViewModel)
{
var htmlLink = calendarItemViewModel?.CalendarItem?.HtmlLink;
if (string.IsNullOrWhiteSpace(htmlLink))
return Task.CompletedTask;
return _nativeAppService.LaunchUriAsync(new Uri(htmlLink));
}
private async Task DeleteCalendarItemAsync(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType targetType)
{
var targetItem = await ResolveCalendarItemTargetAsync(calendarItemViewModel, targetType).ConfigureAwait(false);
if (targetItem == null)
return;
if (targetItem.IsRecurringParent)
{
var confirmed = await _dialogService.ShowConfirmationDialogAsync(
Translator.DialogMessage_DeleteRecurringSeriesMessage,
Translator.DialogMessage_DeleteRecurringSeriesTitle,
Translator.Buttons_Delete).ConfigureAwait(false);
if (!confirmed)
return;
}
var preparationRequest = new CalendarOperationPreparationRequest(
CalendarSynchronizerOperation.DeleteEvent,
targetItem,
null);
await _winoRequestDelegator.ExecuteAsync(preparationRequest).ConfigureAwait(false);
}
private async Task UpdateShowAsAsync(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType targetType, CalendarItemShowAs showAs)
{
var targetItem = await ResolveCalendarItemTargetAsync(calendarItemViewModel, targetType).ConfigureAwait(false);
if (targetItem == null || targetItem.ShowAs == showAs)
return;
var originalItem = await _calendarService.GetCalendarItemAsync(targetItem.Id).ConfigureAwait(false);
var attendees = await _calendarService.GetAttendeesAsync(targetItem.Id).ConfigureAwait(false);
targetItem.ShowAs = showAs;
await _calendarService.UpdateCalendarItemAsync(targetItem, attendees).ConfigureAwait(false);
var preparationRequest = new CalendarOperationPreparationRequest(
CalendarSynchronizerOperation.UpdateEvent,
targetItem,
attendees,
ResponseMessage: null,
OriginalItem: originalItem,
OriginalAttendees: attendees);
await _winoRequestDelegator.ExecuteAsync(preparationRequest).ConfigureAwait(false);
}
private async Task RespondToCalendarItemAsync(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType targetType, CalendarItemStatus responseStatus)
{
var targetItem = await ResolveCalendarItemTargetAsync(calendarItemViewModel, targetType).ConfigureAwait(false);
if (targetItem == null)
return;
var operation = responseStatus switch
{
CalendarItemStatus.Accepted => CalendarSynchronizerOperation.AcceptEvent,
CalendarItemStatus.Tentative => CalendarSynchronizerOperation.TentativeEvent,
CalendarItemStatus.Cancelled => CalendarSynchronizerOperation.DeclineEvent,
_ => throw new InvalidOperationException($"Unsupported calendar response status '{responseStatus}'.")
};
var preparationRequest = new CalendarOperationPreparationRequest(
operation,
targetItem,
null);
await _winoRequestDelegator.ExecuteAsync(preparationRequest).ConfigureAwait(false);
}
private async Task<CalendarItem> ResolveCalendarItemTargetAsync(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType targetType)
{
if (calendarItemViewModel?.CalendarItem == null)
return null;
var target = new CalendarItemTarget(calendarItemViewModel.CalendarItem, targetType);
var targetItem = await _calendarService.GetCalendarItemTargetAsync(target).ConfigureAwait(false);
targetItem ??= calendarItemViewModel.CalendarItem;
if (targetItem == calendarItemViewModel.CalendarItem || targetItem.AssignedCalendar == null)
{
targetItem.AssignedCalendar = await _calendarService.GetAccountCalendarAsync(targetItem.CalendarId).ConfigureAwait(false)
?? calendarItemViewModel.AssignedCalendar
?? ResolveAssignedCalendar(targetItem.CalendarId);
}
return targetItem;
}
private AccountCalendarViewModel ResolveAssignedCalendar(Guid calendarId)
=> AccountCalendarStateService.AllCalendars.FirstOrDefault(calendar => calendar.Id == calendarId);
@@ -0,0 +1,8 @@
using Wino.Calendar.ViewModels.Data;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Calendar.ViewModels.Messages;
public sealed record CalendarItemContextActionRequestedMessage(
CalendarItemViewModel CalendarItemViewModel,
CalendarContextMenuAction Action);
@@ -0,0 +1,10 @@
namespace Wino.Core.Domain.Enums;
public enum CalendarContextMenuActionType
{
Open,
JoinOnline,
Delete,
ShowAs,
Respond
}
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Interfaces;
public interface ICalendarContextMenuItemService
{
IReadOnlyList<CalendarContextMenuItem> GetContextMenuItems(CalendarItem calendarItem);
}
@@ -0,0 +1,9 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar;
public sealed record CalendarContextMenuAction(
CalendarContextMenuActionType ActionType,
CalendarEventTargetType? TargetType = null,
CalendarItemShowAs? ShowAs = null,
CalendarItemStatus? ResponseStatus = null);
@@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.Calendar;
public sealed record CalendarContextMenuItem(
CalendarContextMenuAction Action,
bool IsPrimary = false,
bool IsEnabled = true,
IReadOnlyList<CalendarContextMenuItem> ChildItems = null)
{
public IReadOnlyList<CalendarContextMenuItem> Children { get; init; } = ChildItems ?? [];
public bool HasChildren => Children.Count > 0;
}
@@ -192,6 +192,7 @@
"CalendarEventDetails_Organizer": "Organizer",
"CalendarEventDetails_People": "People",
"CalendarEventDetails_ReadOnlyEvent": "Read-only event",
"CalendarContextMenu_Respond": "Respond",
"CalendarEventDetails_Reminder": "Reminder",
"CalendarReminder_StartedHoursAgo": "Started {0} hours ago",
"CalendarReminder_StartedMinutesAgo": "Started {0} minutes ago",
@@ -207,7 +208,9 @@
"CalendarShowAs_Busy": "Busy",
"CalendarShowAs_OutOfOffice": "Out of Office",
"CalendarShowAs_WorkingElsewhere": "Working Elsewhere",
"CalendarContextMenu_AllEventsInSeries": "All events in the series",
"CalendarItem_DetailsPopup_JoinOnline": "Join online",
"CalendarContextMenu_ThisEventOnly": "This event only",
"CalendarItem_DetailsPopup_ViewEventButton": "View event",
"CalendarItem_DetailsPopup_ViewSeriesButton": "View series",
"CalendarDragDropMoveNotAllowedMessage": "Only events you own and can edit can be moved.",
+32 -4
View File
@@ -18,6 +18,7 @@ using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.Navigation;
using Wino.Messaging.Client.Calendar;
using Xunit;
using Wino.Calendar.ViewModels.Messages;
namespace Wino.Core.Tests;
@@ -311,6 +312,22 @@ public class CalendarPageViewModelTests
accountCalendarViewModel.MailAccount.Should().BeSameAs(account);
}
[Fact]
public void ReceiveCalendarItemRightTappedMessage_SelectsItemForDetails()
{
var settings = CreateSettings();
var preferencesService = CreatePreferencesService(settings);
var calendarService = new Mock<ICalendarService>();
var calendar = CreateCalendar(CreateAccount(), "Calendar");
var itemViewModel = new CalendarItemViewModel(CreateCalendarItem(calendar.Id, new DateTime(2026, 3, 20, 9, 0, 0), "Tapped"));
var viewModel = CreateViewModel(calendarService.Object, preferencesService.Object, new DateOnly(2026, 3, 20));
viewModel.Receive(new CalendarItemRightTappedMessage(itemViewModel));
viewModel.DisplayDetailsCalendarItemViewModel.Should().BeSameAs(itemViewModel);
}
private static CalendarPageViewModel CreateViewModel(
ICalendarService calendarService,
IPreferencesService preferencesService,
@@ -330,6 +347,17 @@ public class CalendarPageViewModelTests
IPreferencesService preferencesService,
DateOnly today,
IAccountCalendarStateService accountCalendarStateService)
=> CreateViewModel(calendarService, preferencesService, today, accountCalendarStateService, navigationService: Mock.Of<INavigationService>());
private static CalendarPageViewModel CreateViewModel(
ICalendarService calendarService,
IPreferencesService preferencesService,
DateOnly today,
IAccountCalendarStateService accountCalendarStateService,
INavigationService? navigationService = null,
INativeAppService? nativeAppService = null,
IWinoRequestDelegator? requestDelegator = null,
IMailDialogService? dialogService = null)
{
var statePersistenceService = new Mock<IStatePersistanceService>();
statePersistenceService.SetupAllProperties();
@@ -339,13 +367,13 @@ public class CalendarPageViewModelTests
return new CalendarPageViewModel(
statePersistenceService.Object,
calendarService,
Mock.Of<INavigationService>(),
navigationService ?? Mock.Of<INavigationService>(),
Mock.Of<IKeyPressService>(),
Mock.Of<INativeAppService>(),
nativeAppService ?? Mock.Of<INativeAppService>(),
accountCalendarStateService,
preferencesService,
Mock.Of<IWinoRequestDelegator>(),
Mock.Of<IMailDialogService>(),
requestDelegator ?? Mock.Of<IWinoRequestDelegator>(),
dialogService ?? Mock.Of<IMailDialogService>(),
new TestDateContextProvider("en-US", today),
new CalendarRangeTextFormatter());
}
@@ -0,0 +1,72 @@
using System.Linq;
using FluentAssertions;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Enums;
using Wino.Services;
using Xunit;
namespace Wino.Core.Tests.Services;
public class CalendarContextMenuItemServiceTests
{
private readonly CalendarContextMenuItemService _service = new();
[Fact]
public void GetContextMenuItems_ForEditableSingleEvent_ReturnsOpenShowAsAndDeleteAsPrimary()
{
var calendarItem = new CalendarItem
{
Id = Guid.NewGuid(),
Title = "Editable single event",
ShowAs = CalendarItemShowAs.Busy
};
var items = _service.GetContextMenuItems(calendarItem);
items.Should().HaveCount(3);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.Open && item.IsPrimary);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.ShowAs && item.IsPrimary);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.Delete && item.IsPrimary);
items.Should().NotContain(item => item.Action.ActionType == CalendarContextMenuActionType.Respond);
var showAsItem = items.Single(item => item.Action.ActionType == CalendarContextMenuActionType.ShowAs);
showAsItem.Children.Should().HaveCount(5);
showAsItem.Children.Select(child => child.Action.ShowAs).Should().BeEquivalentTo(
[
CalendarItemShowAs.Free,
CalendarItemShowAs.Tentative,
CalendarItemShowAs.Busy,
CalendarItemShowAs.OutOfOffice,
CalendarItemShowAs.WorkingElsewhere
]);
}
[Fact]
public void GetContextMenuItems_ForLockedRecurringChild_ReturnsRespondDeleteViewSeriesAndJoinOnline()
{
var calendarItem = new CalendarItem
{
Id = Guid.NewGuid(),
Title = "Recurring invite",
IsLocked = true,
RecurringCalendarItemId = Guid.NewGuid(),
HtmlLink = "https://contoso.example/meeting"
};
var items = _service.GetContextMenuItems(calendarItem);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.Open && item.IsPrimary);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.Respond && item.IsPrimary);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.Delete && item.IsPrimary);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.Open && item.Action.TargetType == CalendarEventTargetType.Series && !item.IsPrimary);
items.Should().ContainSingle(item => item.Action.ActionType == CalendarContextMenuActionType.JoinOnline && !item.IsPrimary);
var respondItem = items.Single(item => item.Action.ActionType == CalendarContextMenuActionType.Respond);
respondItem.Children.Should().HaveCount(2);
respondItem.Children.Select(child => child.Action.TargetType).Should().BeEquivalentTo([CalendarEventTargetType.Single, CalendarEventTargetType.Series]);
respondItem.Children.Should().OnlyContain(child => child.Children.Count == 3);
var deleteItem = items.Single(item => item.Action.ActionType == CalendarContextMenuActionType.Delete);
deleteItem.Children.Select(child => child.Action.TargetType).Should().BeEquivalentTo([CalendarEventTargetType.Single, CalendarEventTargetType.Series]);
}
}
@@ -1,11 +1,23 @@
using Microsoft.UI.Xaml;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Calendar.ViewModels.Messages;
using Wino.Calendar.ViewModels.Data;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
using Wino.Mail.WinUI;
using Wino.Mail.WinUI.Controls;
namespace Wino.Calendar.Controls;
public partial class CalendarItemCommandBarFlyout : CommandBarFlyout
{
private readonly ICalendarContextMenuItemService _contextMenuItemService;
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
public CalendarItemViewModel Item
@@ -14,6 +26,11 @@ public partial class CalendarItemCommandBarFlyout : CommandBarFlyout
set { SetValue(ItemProperty, value); }
}
public CalendarItemCommandBarFlyout()
{
_contextMenuItemService = WinoApplication.Current.Services.GetRequiredService<ICalendarContextMenuItemService>();
Opening += FlyoutOpening;
}
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
@@ -23,8 +40,148 @@ public partial class CalendarItemCommandBarFlyout : CommandBarFlyout
}
}
private void FlyoutOpening(object sender, object e) => UpdateMenuItems();
private void UpdateMenuItems()
{
PrimaryCommands.Clear();
SecondaryCommands.Clear();
if (Item?.CalendarItem == null)
return;
var menuItems = _contextMenuItemService.GetContextMenuItems(Item.CalendarItem);
foreach (var menuItem in menuItems)
{
var appBarButton = BuildAppBarButton(menuItem);
if (menuItem.IsPrimary)
PrimaryCommands.Add(appBarButton);
else
SecondaryCommands.Add(appBarButton);
}
}
private AppBarButton BuildAppBarButton(CalendarContextMenuItem menuItem)
{
var button = new AppBarButton
{
Label = GetActionLabel(menuItem.Action),
IsEnabled = menuItem.IsEnabled,
Icon = new WinoFontIcon
{
Icon = GetActionIcon(menuItem.Action),
FontSize = 16
}
};
if (menuItem.HasChildren)
{
var flyout = new MenuFlyout();
PopulateMenuFlyoutItems(flyout.Items, menuItem.Children);
button.Flyout = flyout;
}
else
{
button.Click += (_, _) => ExecuteAction(menuItem.Action);
}
return button;
}
private void PopulateMenuFlyoutItems(IList<MenuFlyoutItemBase> items, IReadOnlyList<CalendarContextMenuItem> menuItems)
{
foreach (var menuItem in menuItems)
{
if (menuItem.HasChildren)
{
var subItem = new MenuFlyoutSubItem
{
Text = GetActionLabel(menuItem.Action),
IsEnabled = menuItem.IsEnabled
};
PopulateMenuFlyoutItems(subItem.Items, menuItem.Children);
items.Add(subItem);
}
else
{
var flyoutItem = new MenuFlyoutItem
{
Text = GetActionLabel(menuItem.Action),
IsEnabled = menuItem.IsEnabled
};
flyoutItem.Click += (_, _) => ExecuteAction(menuItem.Action);
items.Add(flyoutItem);
}
}
}
private void ExecuteAction(CalendarContextMenuAction action)
{
if (Item == null)
return;
WeakReferenceMessenger.Default.Send(new CalendarItemContextActionRequestedMessage(Item, action));
Hide();
}
private static string GetActionLabel(CalendarContextMenuAction action)
{
if (action.ShowAs.HasValue)
{
return action.ShowAs.Value switch
{
CalendarItemShowAs.Free => Translator.CalendarShowAs_Free,
CalendarItemShowAs.Tentative => Translator.CalendarShowAs_Tentative,
CalendarItemShowAs.Busy => Translator.CalendarShowAs_Busy,
CalendarItemShowAs.OutOfOffice => Translator.CalendarShowAs_OutOfOffice,
CalendarItemShowAs.WorkingElsewhere => Translator.CalendarShowAs_WorkingElsewhere,
_ => Translator.CalendarShowAs_Busy
};
}
if (action.ResponseStatus.HasValue)
{
return action.ResponseStatus.Value switch
{
CalendarItemStatus.Accepted => Translator.CalendarEventResponse_Accept,
CalendarItemStatus.Tentative => Translator.CalendarEventResponse_Tentative,
CalendarItemStatus.Cancelled => Translator.CalendarEventResponse_Decline,
_ => Translator.CalendarEventResponse_Accept
};
}
if (action.TargetType.HasValue && action.ActionType is CalendarContextMenuActionType.Delete or CalendarContextMenuActionType.ShowAs or CalendarContextMenuActionType.Respond)
{
return action.TargetType == CalendarEventTargetType.Single
? Translator.CalendarContextMenu_ThisEventOnly
: Translator.CalendarContextMenu_AllEventsInSeries;
}
return action.ActionType switch
{
CalendarContextMenuActionType.Open when action.TargetType == CalendarEventTargetType.Series => Translator.CalendarItem_DetailsPopup_ViewSeriesButton,
CalendarContextMenuActionType.Open => Translator.Buttons_Open,
CalendarContextMenuActionType.JoinOnline => Translator.CalendarItem_DetailsPopup_JoinOnline,
CalendarContextMenuActionType.Delete => Translator.Buttons_Delete,
CalendarContextMenuActionType.ShowAs => Translator.CalendarEventDetails_ShowAs,
CalendarContextMenuActionType.Respond => Translator.CalendarContextMenu_Respond,
_ => Translator.Buttons_Open
};
}
private static WinoIconGlyph GetActionIcon(CalendarContextMenuAction action)
=> action.ActionType switch
{
CalendarContextMenuActionType.Open when action.TargetType == CalendarEventTargetType.Series => WinoIconGlyph.EventEditSeries,
CalendarContextMenuActionType.Open => WinoIconGlyph.OpenInNewWindow,
CalendarContextMenuActionType.JoinOnline => WinoIconGlyph.EventJoinOnline,
CalendarContextMenuActionType.Delete => WinoIconGlyph.Delete,
CalendarContextMenuActionType.ShowAs => WinoIconGlyph.CalendarShowAs,
CalendarContextMenuActionType.Respond => WinoIconGlyph.EventRespond,
_ => WinoIconGlyph.More
};
}
@@ -29,12 +29,9 @@
</Grid.ColumnDefinitions>
<Grid.ContextFlyout>
<local:CalendarItemCommandBarFlyout Placement="Top">
<local:CalendarItemCommandBarFlyout.PrimaryCommands>
<AppBarButton Icon="Save" Label="save" />
<AppBarButton Icon="Delete" Label="Delet" />
</local:CalendarItemCommandBarFlyout.PrimaryCommands>
</local:CalendarItemCommandBarFlyout>
<local:CalendarItemCommandBarFlyout
Item="{x:Bind CalendarItem, Mode=OneWay}"
Placement="Top" />
</Grid.ContextFlyout>
@@ -130,7 +130,14 @@ public sealed partial class CalendarItemControl : UserControl
private void ControlRightTapped(object sender, RightTappedRoutedEventArgs e)
{
if (CalendarItem == null) return;
if (CalendarItem == null)
return;
if (CalendarItem.IsBusy)
{
e.Handled = true;
return;
}
WeakReferenceMessenger.Default.Send(new CalendarItemRightTappedMessage(CalendarItem));
}
@@ -0,0 +1,131 @@
using System.Collections.Generic;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Services;
public class CalendarContextMenuItemService : ICalendarContextMenuItemService
{
private static readonly IReadOnlyList<CalendarItemShowAs> ShowAsOptions =
[
CalendarItemShowAs.Free,
CalendarItemShowAs.Tentative,
CalendarItemShowAs.Busy,
CalendarItemShowAs.OutOfOffice,
CalendarItemShowAs.WorkingElsewhere
];
private static readonly IReadOnlyList<CalendarItemStatus> ResponseOptions =
[
CalendarItemStatus.Accepted,
CalendarItemStatus.Tentative,
CalendarItemStatus.Cancelled
];
public IReadOnlyList<CalendarContextMenuItem> GetContextMenuItems(CalendarItem calendarItem)
{
if (calendarItem == null)
return [];
var items = new List<CalendarContextMenuItem>
{
new(new CalendarContextMenuAction(CalendarContextMenuActionType.Open, CalendarEventTargetType.Single), IsPrimary: true)
};
if (calendarItem.IsLocked)
{
items.Add(CreateRespondItem(calendarItem.IsRecurringChild));
}
else
{
items.Add(CreateShowAsItem(calendarItem.IsRecurringChild));
}
items.Add(CreateDeleteItem(calendarItem.IsRecurringChild));
if (calendarItem.IsRecurringChild)
{
items.Add(new CalendarContextMenuItem(
new CalendarContextMenuAction(CalendarContextMenuActionType.Open, CalendarEventTargetType.Series)));
}
if (!string.IsNullOrWhiteSpace(calendarItem.HtmlLink))
{
items.Add(new CalendarContextMenuItem(
new CalendarContextMenuAction(CalendarContextMenuActionType.JoinOnline)));
}
return items;
}
private static CalendarContextMenuItem CreateDeleteItem(bool isRecurringChild)
=> isRecurringChild
? new CalendarContextMenuItem(
new CalendarContextMenuAction(CalendarContextMenuActionType.Delete),
IsPrimary: true,
ChildItems:
[
CreateScopeLeaf(CalendarContextMenuActionType.Delete, CalendarEventTargetType.Single),
CreateScopeLeaf(CalendarContextMenuActionType.Delete, CalendarEventTargetType.Series)
])
: new CalendarContextMenuItem(
new CalendarContextMenuAction(CalendarContextMenuActionType.Delete, CalendarEventTargetType.Single),
IsPrimary: true);
private static CalendarContextMenuItem CreateShowAsItem(bool isRecurringChild)
=> new(
new CalendarContextMenuAction(CalendarContextMenuActionType.ShowAs),
IsPrimary: true,
ChildItems: isRecurringChild
? [CreateScopedShowAsMenu(CalendarEventTargetType.Single), CreateScopedShowAsMenu(CalendarEventTargetType.Series)]
: CreateShowAsLeaves(CalendarEventTargetType.Single));
private static CalendarContextMenuItem CreateRespondItem(bool isRecurringChild)
=> new(
new CalendarContextMenuAction(CalendarContextMenuActionType.Respond),
IsPrimary: true,
ChildItems: isRecurringChild
? [CreateScopedResponseMenu(CalendarEventTargetType.Single), CreateScopedResponseMenu(CalendarEventTargetType.Series)]
: CreateResponseLeaves(CalendarEventTargetType.Single));
private static CalendarContextMenuItem CreateScopedShowAsMenu(CalendarEventTargetType targetType)
=> new(
new CalendarContextMenuAction(CalendarContextMenuActionType.ShowAs, targetType),
ChildItems: CreateShowAsLeaves(targetType));
private static CalendarContextMenuItem CreateScopedResponseMenu(CalendarEventTargetType targetType)
=> new(
new CalendarContextMenuAction(CalendarContextMenuActionType.Respond, targetType),
ChildItems: CreateResponseLeaves(targetType));
private static IReadOnlyList<CalendarContextMenuItem> CreateShowAsLeaves(CalendarEventTargetType targetType)
{
var items = new List<CalendarContextMenuItem>(ShowAsOptions.Count);
foreach (var showAs in ShowAsOptions)
{
items.Add(new CalendarContextMenuItem(
new CalendarContextMenuAction(CalendarContextMenuActionType.ShowAs, targetType, showAs)));
}
return items;
}
private static IReadOnlyList<CalendarContextMenuItem> CreateResponseLeaves(CalendarEventTargetType targetType)
{
var items = new List<CalendarContextMenuItem>(ResponseOptions.Count);
foreach (var responseStatus in ResponseOptions)
{
items.Add(new CalendarContextMenuItem(
new CalendarContextMenuAction(CalendarContextMenuActionType.Respond, targetType, ResponseStatus: responseStatus)));
}
return items;
}
private static CalendarContextMenuItem CreateScopeLeaf(CalendarContextMenuActionType actionType, CalendarEventTargetType targetType)
=> new(new CalendarContextMenuAction(actionType, targetType));
}
+1
View File
@@ -25,6 +25,7 @@ public static class ServicesContainerSetup
services.AddTransient<ISignatureService, SignatureService>();
services.AddTransient<IEmailTemplateService, EmailTemplateService>();
services.AddTransient<IContextMenuItemService, ContextMenuItemService>();
services.AddTransient<ICalendarContextMenuItemService, CalendarContextMenuItemService>();
services.AddTransient<ISpecialImapProviderConfigResolver, SpecialImapProviderConfigResolver>();
services.AddTransient<IKeyboardShortcutService, KeyboardShortcutService>();
services.AddSingleton<IWinoAccountApiClient, WinoAccountApiClient>();