Event details page navigation, handling of attendees in Outlook synchronizer, navigation changes for calendar.

This commit is contained in:
Burak Kaan Köse
2025-01-16 22:00:05 +01:00
parent 56d6c22d53
commit 7cfa5a57f5
21 changed files with 374 additions and 99 deletions

View File

@@ -194,7 +194,6 @@ namespace Wino.Calendar.ViewModels
public override void OnNavigatedFrom(NavigationMode mode, object parameters) public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{ {
;
// Do not call base method because that will unregister messenger recipient. // Do not call base method because that will unregister messenger recipient.
// This is a singleton view model and should not be unregistered. // This is a singleton view model and should not be unregistered.
} }
@@ -214,6 +213,9 @@ namespace Wino.Calendar.ViewModels
[RelayCommand] [RelayCommand]
private void NavigateSeries() private void NavigateSeries()
{ {
if (DisplayDetailsCalendarItemViewModel == null) return;
NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Series);
} }
[RelayCommand] [RelayCommand]
@@ -221,14 +223,13 @@ namespace Wino.Calendar.ViewModels
{ {
if (DisplayDetailsCalendarItemViewModel == null) return; if (DisplayDetailsCalendarItemViewModel == null) return;
NavigateEvent(DisplayDetailsCalendarItemViewModel); NavigateEvent(DisplayDetailsCalendarItemViewModel, CalendarEventTargetType.Single);
} }
[RelayCommand] private void NavigateEvent(CalendarItemViewModel calendarItemViewModel, CalendarEventTargetType calendarEventTargetType)
private void NavigateEvent(CalendarItemViewModel calendarItemViewModel)
{ {
// Double tap or clicked 'view details' of the event detail popup. var target = new CalendarItemTarget(calendarItemViewModel.CalendarItem, calendarEventTargetType);
_navigationService.Navigate(WinoPage.EventDetailsPage, calendarItemViewModel); _navigationService.Navigate(WinoPage.EventDetailsPage, target);
} }
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))] [RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
@@ -799,7 +800,7 @@ namespace Wino.Calendar.ViewModels
// Recurring events must be selected as a single instance. // Recurring events must be selected as a single instance.
// We need to find the day that the event is in, and then select the event. // We need to find the day that the event is in, and then select the event.
if (calendarItemViewModel.IsSingleExceptionalInstance) if (!calendarItemViewModel.IsRecurringEvent)
{ {
return [calendarItemViewModel]; return [calendarItemViewModel];
} }
@@ -845,7 +846,7 @@ namespace Wino.Calendar.ViewModels
DisplayDetailsCalendarItemViewModel = message.CalendarItemViewModel; DisplayDetailsCalendarItemViewModel = message.CalendarItemViewModel;
} }
public void Receive(CalendarItemDoubleTappedMessage message) => NavigateEvent(message.CalendarItemViewModel); public void Receive(CalendarItemDoubleTappedMessage message) => NavigateEvent(message.CalendarItemViewModel, CalendarEventTargetType.Single);
public void Receive(CalendarItemRightTappedMessage message) public void Receive(CalendarItemRightTappedMessage message)
{ {

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Itenso.TimePeriod; using Itenso.TimePeriod;
using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Calendar;
@@ -25,16 +26,16 @@ namespace Wino.Calendar.ViewModels.Data
public ITimePeriod Period => CalendarItem.Period; public ITimePeriod Period => CalendarItem.Period;
public bool IsAllDayEvent => CalendarItem.IsAllDayEvent; public bool IsAllDayEvent => CalendarItem.IsAllDayEvent;
public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent; public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent;
public bool IsRecurringEvent => CalendarItem.IsRecurringEvent; public bool IsRecurringEvent => CalendarItem.IsRecurringEvent;
public bool IsRecurringChild => CalendarItem.IsRecurringChild;
public bool IsSingleExceptionalInstance => CalendarItem.IsSingleExceptionalInstance; public bool IsRecurringParent => CalendarItem.IsRecurringParent;
[ObservableProperty] [ObservableProperty]
private bool _isSelected; private bool _isSelected;
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
public CalendarItemViewModel(CalendarItem calendarItem) public CalendarItemViewModel(CalendarItem calendarItem)
{ {
CalendarItem = calendarItem; CalendarItem = calendarItem;

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@@ -27,7 +28,10 @@ namespace Wino.Calendar.ViewModels
[NotifyPropertyChangedFor(nameof(CanViewSeries))] [NotifyPropertyChangedFor(nameof(CanViewSeries))]
private CalendarItemViewModel _currentEvent; private CalendarItemViewModel _currentEvent;
public bool CanViewSeries => CurrentEvent?.CalendarItem.RecurringCalendarItemId != null; [ObservableProperty]
private CalendarItemViewModel _seriesParent;
public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false;
#endregion #endregion
@@ -40,16 +44,40 @@ namespace Wino.Calendar.ViewModels
CurrentSettings = _preferencesService.GetCurrentCalendarSettings(); CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
} }
public override void OnNavigatedTo(NavigationMode mode, object parameters) public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{ {
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
Messenger.Send(new DetailsPageStateChangedMessage(true)); Messenger.Send(new DetailsPageStateChangedMessage(true));
if (parameters == null || parameters is not CalendarItemViewModel passedCalendarItem) if (parameters == null || parameters is not CalendarItemTarget args)
return; return;
CurrentEvent = passedCalendarItem; await LoadCalendarItemTargetAsync(args);
}
private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target)
{
try
{
var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target);
if (currentEventItem == null)
return;
CurrentEvent = new CalendarItemViewModel(currentEventItem);
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId);
foreach (var item in attendees)
{
CurrentEvent.Attendees.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
} }
public override void OnNavigatedFrom(NavigationMode mode, object parameters) public override void OnNavigatedFrom(NavigationMode mode, object parameters)

View File

@@ -42,7 +42,7 @@ namespace Wino.Calendar.Helpers
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel) public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
{ {
if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringEvent) return string.Empty; if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringChild) return string.Empty;
// Parse recurrence rules // Parse recurrence rules
var calendarEvent = new CalendarEvent var calendarEvent = new CalendarEvent

View File

@@ -14,6 +14,7 @@ namespace Wino.Calendar.Views
private const string STATE_VerticalCalendar = "VerticalCalendar"; private const string STATE_VerticalCalendar = "VerticalCalendar";
public Frame GetShellFrame() => ShellFrame; public Frame GetShellFrame() => ShellFrame;
public AppShell() public AppShell()
{ {
InitializeComponent(); InitializeComponent();

View File

@@ -380,7 +380,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Command="{x:Bind ViewModel.NavigateSeriesCommand}" Command="{x:Bind ViewModel.NavigateSeriesCommand}"
Content="{x:Bind domain:Translator.CalendarItem_DetailsPopup_ViewSeriesButton}" Content="{x:Bind domain:Translator.CalendarItem_DetailsPopup_ViewSeriesButton}"
Visibility="{x:Bind ViewModel.DisplayDetailsCalendarItemViewModel.CalendarItem.IsRecurringEvent, Mode=OneWay}" /> Visibility="{x:Bind ViewModel.DisplayDetailsCalendarItemViewModel.IsRecurringEvent, Mode=OneWay}" />
<Button <Button
Grid.Column="1" Grid.Column="1"

View File

@@ -49,6 +49,8 @@ namespace Wino.Calendar.Views
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.Back) return;
if (e.Parameter is CalendarPageNavigationArgs args) if (e.Parameter is CalendarPageNavigationArgs args)
{ {
if (args.RequestDefaultNavigation) if (args.RequestDefaultNavigation)

View File

@@ -4,6 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Calendar.Views.Abstract" xmlns:abstract="using:Wino.Calendar.Views.Abstract"
xmlns:calendar="using:Wino.Core.Domain.Entities.Calendar"
xmlns:calendarHelpers="using:Wino.Calendar.Helpers" xmlns:calendarHelpers="using:Wino.Calendar.Helpers"
xmlns:coreControls="using:Wino.Core.UWP.Controls" xmlns:coreControls="using:Wino.Core.UWP.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -179,10 +180,16 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Title --> <!-- Title -->
<TextBlock Style="{StaticResource SubheaderTextBlockStyle}" Text="{x:Bind ViewModel.CurrentEvent.Title, Mode=OneWay}" /> <TextBlock
Style="{StaticResource SubheaderTextBlockStyle}"
Text="{x:Bind ViewModel.CurrentEvent.Title, Mode=OneWay}"
TextWrapping="Wrap" />
<!-- Date and Duration --> <!-- Date and Duration -->
<TextBlock Grid.Row="2" Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetEventDetailsDateString(ViewModel.CurrentEvent, ViewModel.CurrentSettings), Mode=OneWay}" /> <TextBlock
Grid.Row="2"
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetEventDetailsDateString(ViewModel.CurrentEvent, ViewModel.CurrentSettings), Mode=OneWay}"
TextWrapping="Wrap" />
<!-- Recurrence Info --> <!-- Recurrence Info -->
<Grid <Grid
@@ -198,7 +205,8 @@
<TextBlock <TextBlock
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetRecurrenceString(ViewModel.CurrentEvent)}" /> Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetRecurrenceString(ViewModel.CurrentEvent), Mode=OneWay}"
TextWrapping="Wrap" />
</Grid> </Grid>
</Grid> </Grid>
@@ -222,15 +230,51 @@
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="People" /> <TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="People" />
<Grid Grid.Row="1"> <Grid Grid.Row="1" RowSpacing="12">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<AutoSuggestBox BorderThickness="0" PlaceholderText="Invite someone" /> <AutoSuggestBox
Margin="6,0"
BorderThickness="0"
PlaceholderText="Invite someone" />
<!-- TODO: Attendees --> <ListView
<ListView Grid.Row="1" /> Grid.Row="1"
IsItemClickEnabled="True"
ItemsSource="{x:Bind ViewModel.CurrentEvent.Attendees, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="calendar:CalendarEventAttendee">
<Grid Margin="0,6" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<PersonPicture
Width="40"
Height="40"
DisplayName="{x:Bind Name}" />
<!-- TODO: Organizer -->
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
<TextBlock
Grid.Row="1"
FontSize="13"
Text="{x:Bind Email}" />
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -55,21 +55,11 @@ namespace Wino.Core.Domain.Entities.Calendar
} }
/// <summary> /// <summary>
/// Events that are either an exceptional instance of a recurring event or a recurring event itself. /// Events that are either an exceptional instance of a recurring event or occurrences.
/// IsOccurrence is used to display occurrence instances of parent recurring events.
/// IsOccurrence == false && IsRecurringChild == true => exceptional single instance.
/// </summary> /// </summary>
public bool IsRecurringEvent public bool IsRecurringChild
{
get
{
return !string.IsNullOrEmpty(Recurrence) || RecurringCalendarItemId != null;
}
}
/// <summary>
/// Events that are belong to parent recurring event, but updated individually are considered single exceptional instances.
/// They will have different Id of their own.
/// </summary>
public bool IsSingleExceptionalInstance
{ {
get get
{ {
@@ -77,6 +67,22 @@ namespace Wino.Core.Domain.Entities.Calendar
} }
} }
/// <summary>
/// Events that are either an exceptional instance of a recurring event or occurrences.
/// </summary>
public bool IsRecurringEvent => IsRecurringChild || IsRecurringParent;
/// <summary>
/// Events that are the master event definition of recurrence events.
/// </summary>
public bool IsRecurringParent
{
get
{
return !string.IsNullOrEmpty(Recurrence) && RecurringCalendarItemId == null;
}
}
/// <summary> /// <summary>
/// Events that are not all-day events and last more than one day are considered multi-day events. /// Events that are not all-day events and last more than one day are considered multi-day events.
/// </summary> /// </summary>
@@ -124,6 +130,20 @@ namespace Wino.Core.Domain.Entities.Calendar
[Ignore] [Ignore]
public IAccountCalendar AssignedCalendar { get; set; } public IAccountCalendar AssignedCalendar { get; set; }
/// <summary>
/// Whether this item does not really exist in the database or not.
/// These are used to display occurrence instances of parent recurring events.
/// </summary>
[Ignore]
public bool IsOccurrence { get; set; }
/// <summary>
/// Id to load information related to this event.
/// Occurrences tracked by the parent recurring event if they are not exceptional instances.
/// Recurring children here are exceptional instances. They have their own info in the database including Id.
/// </summary>
public Guid EventTrackingId => IsOccurrence ? RecurringCalendarItemId.Value : Id;
public CalendarItem CreateRecurrence(DateTime startDate, double durationInSeconds) public CalendarItem CreateRecurrence(DateTime startDate, double durationInSeconds)
{ {
// Create a copy with the new start date and duration // Create a copy with the new start date and duration
@@ -153,6 +173,7 @@ namespace Wino.Core.Domain.Entities.Calendar
RemoteEventId = RemoteEventId, RemoteEventId = RemoteEventId,
IsHidden = IsHidden, IsHidden = IsHidden,
IsLocked = IsLocked, IsLocked = IsLocked,
IsOccurrence = true
}; };
} }
} }

View File

@@ -0,0 +1,8 @@
namespace Wino.Core.Domain.Enums
{
public enum CalendarEventTargetType
{
Single, // Show details for a single event.
Series // Show the series event. Parent of all recurring events.
}
}

View File

@@ -13,9 +13,11 @@ namespace Wino.Core.Domain.Interfaces
double DurationInSeconds { get; set; } double DurationInSeconds { get; set; }
ITimePeriod Period { get; } ITimePeriod Period { get; }
bool IsRecurringEvent { get; }
bool IsAllDayEvent { get; } bool IsAllDayEvent { get; }
bool IsMultiDayEvent { get; } bool IsMultiDayEvent { get; }
bool IsSingleExceptionalInstance { get; }
bool IsRecurringChild { get; }
bool IsRecurringParent { get; }
bool IsRecurringEvent { get; }
} }
} }

View File

@@ -19,5 +19,14 @@ namespace Wino.Core.Domain.Interfaces
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel); Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel);
Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId); Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId);
Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken); Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken);
/// <summary>
/// Returns the correct calendar item based on the target details.
/// </summary>
/// <param name="targetDetails">Target details.</param>
Task<CalendarItem> GetCalendarItemTargetAsync(CalendarItemTarget targetDetails);
Task<CalendarItem> GetCalendarItemAsync(Guid id);
Task<List<CalendarEventAttendee>> GetAttendeesAsync(Guid calendarEventTrackingId);
Task<List<CalendarEventAttendee>> ManageEventAttendeesAsync(Guid calendarItemId, List<CalendarEventAttendee> allAttendees);
} }
} }

View File

@@ -0,0 +1,7 @@
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar
{
public record CalendarItemTarget(CalendarItem Item, CalendarEventTargetType TargetType);
}

View File

@@ -45,7 +45,8 @@
BorderBrush="Transparent" BorderBrush="Transparent"
Click="PaneClicked" Click="PaneClicked"
FocusVisualPrimaryThickness="0" FocusVisualPrimaryThickness="0"
FocusVisualSecondaryThickness="0"> FocusVisualSecondaryThickness="0"
Visibility="{x:Bind IsMenuButtonVisible, Mode=OneWay}">
<muxc:AnimatedIcon Width="16"> <muxc:AnimatedIcon Width="16">
<muxc:AnimatedIcon.Source> <muxc:AnimatedIcon.Source>
<animatedvisuals:AnimatedGlobalNavigationButtonVisualSource /> <animatedvisuals:AnimatedGlobalNavigationButtonVisualSource />

View File

@@ -25,7 +25,7 @@ namespace Wino.Core.UWP.Controls
public static readonly DependencyProperty ShrinkShellContentOnExpansionProperty = DependencyProperty.Register(nameof(ShrinkShellContentOnExpansion), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true)); public static readonly DependencyProperty ShrinkShellContentOnExpansionProperty = DependencyProperty.Register(nameof(ShrinkShellContentOnExpansion), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public static readonly DependencyProperty IsDragAreaProperty = DependencyProperty.Register(nameof(IsDragArea), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, new PropertyChangedCallback(OnIsDragAreaChanged))); public static readonly DependencyProperty IsDragAreaProperty = DependencyProperty.Register(nameof(IsDragArea), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(false, new PropertyChangedCallback(OnIsDragAreaChanged)));
public static readonly DependencyProperty IsShellFrameContentVisibleProperty = DependencyProperty.Register(nameof(IsShellFrameContentVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true)); public static readonly DependencyProperty IsShellFrameContentVisibleProperty = DependencyProperty.Register(nameof(IsShellFrameContentVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public static readonly DependencyProperty IsMenuButtonVisibleProperty = DependencyProperty.Register(nameof(IsMenuButtonVisible), typeof(bool), typeof(WinoAppTitleBar), new PropertyMetadata(true));
public bool IsShellFrameContentVisible public bool IsShellFrameContentVisible
{ {
@@ -94,6 +94,15 @@ namespace Wino.Core.UWP.Controls
set { SetValue(OpenPaneLengthProperty, value); } set { SetValue(OpenPaneLengthProperty, value); }
} }
public bool IsMenuButtonVisible
{
get { return (bool)GetValue(IsMenuButtonVisibleProperty); }
set { SetValue(IsMenuButtonVisibleProperty, value); }
}
public bool IsBackButtonVisible public bool IsBackButtonVisible
{ {
get { return (bool)GetValue(IsBackButtonVisibleProperty); } get { return (bool)GetValue(IsBackButtonVisibleProperty); }
@@ -206,11 +215,17 @@ namespace Wino.Core.UWP.Controls
} }
else else
{ {
// EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel); if (ShrinkShellContentOnExpansion)
{
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Pixel);
}
else
{
EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Star); EmptySpaceWidth.Width = new GridLength(ReadingPaneLength, GridUnitType.Star);
} }
} }
} }
}
public WinoAppTitleBar() public WinoAppTitleBar()
{ {

View File

@@ -247,6 +247,38 @@ namespace Wino.Core.Extensions
} }
} }
private static AttendeeStatus GetAttendeeStatus(ResponseType? responseType)
{
return responseType switch
{
ResponseType.None => AttendeeStatus.NeedsAction,
ResponseType.NotResponded => AttendeeStatus.NeedsAction,
ResponseType.Organizer => AttendeeStatus.Accepted,
ResponseType.TentativelyAccepted => AttendeeStatus.Tentative,
ResponseType.Accepted => AttendeeStatus.Accepted,
ResponseType.Declined => AttendeeStatus.Declined,
_ => AttendeeStatus.NeedsAction
};
}
public static CalendarEventAttendee CreateAttendee(this Attendee attendee, Guid calendarItemId)
{
bool isOrganizer = attendee?.Status?.Response == ResponseType.Organizer;
var eventAttendee = new CalendarEventAttendee()
{
CalendarItemId = calendarItemId,
Id = Guid.NewGuid(),
Email = attendee.EmailAddress?.Address,
Name = attendee.EmailAddress?.Name,
AttendenceStatus = GetAttendeeStatus(attendee.Status.Response),
IsOrganizer = isOrganizer,
IsOptionalAttendee = attendee.Type == AttendeeType.Optional,
};
return eventAttendee;
}
#region Mime to Outlook Message Helpers #region Mime to Outlook Message Helpers
private static IEnumerable<Recipient> GetRecipients(this InternetAddressList internetAddresses) private static IEnumerable<Recipient> GetRecipients(this InternetAddressList internetAddresses)

View File

@@ -163,59 +163,59 @@ namespace Wino.Core.Integration.Processors
// Attendees // Attendees
var attendees = new List<CalendarEventAttendee>(); var attendees = new List<CalendarEventAttendee>();
//if (calendarEvent.Attendees == null) if (calendarEvent.Attendees == null)
//{ {
// // Self-only event. // Self-only event.
// attendees.Add(new CalendarEventAttendee() attendees.Add(new CalendarEventAttendee()
// { {
// CalendarItemId = calendarItem.Id, CalendarItemId = calendarItem.Id,
// IsOrganizer = true, IsOrganizer = true,
// Email = organizerAccount.Address, Email = organizerAccount.Address,
// Name = organizerAccount.SenderName, Name = organizerAccount.SenderName,
// AttendenceStatus = AttendeeStatus.Accepted, AttendenceStatus = AttendeeStatus.Accepted,
// Id = Guid.NewGuid(), Id = Guid.NewGuid(),
// IsOptionalAttendee = false, IsOptionalAttendee = false,
// }); });
//} }
//else else
//{ {
// foreach (var attendee in calendarEvent.Attendees) foreach (var attendee in calendarEvent.Attendees)
// { {
// if (attendee.Self == true) if (attendee.Self == true)
// { {
// // TODO: // TODO:
// } }
// else if (!string.IsNullOrEmpty(attendee.Email)) else if (!string.IsNullOrEmpty(attendee.Email))
// { {
// AttendeeStatus GetAttendenceStatus(string responseStatus) AttendeeStatus GetAttendenceStatus(string responseStatus)
// { {
// return responseStatus switch return responseStatus switch
// { {
// "accepted" => AttendeeStatus.Accepted, "accepted" => AttendeeStatus.Accepted,
// "declined" => AttendeeStatus.Declined, "declined" => AttendeeStatus.Declined,
// "tentative" => AttendeeStatus.Tentative, "tentative" => AttendeeStatus.Tentative,
// "needsAction" => AttendeeStatus.NeedsAction, "needsAction" => AttendeeStatus.NeedsAction,
// _ => AttendeeStatus.NeedsAction _ => AttendeeStatus.NeedsAction
// }; };
// } }
// var eventAttendee = new CalendarEventAttendee() var eventAttendee = new CalendarEventAttendee()
// { {
// CalendarItemId = calendarItem.Id, CalendarItemId = calendarItem.Id,
// IsOrganizer = attendee.Organizer ?? false, IsOrganizer = attendee.Organizer ?? false,
// Comment = attendee.Comment, Comment = attendee.Comment,
// Email = attendee.Email, Email = attendee.Email,
// Name = attendee.DisplayName, Name = attendee.DisplayName,
// AttendenceStatus = GetAttendenceStatus(attendee.ResponseStatus), AttendenceStatus = GetAttendenceStatus(attendee.ResponseStatus),
// Id = Guid.NewGuid(), Id = Guid.NewGuid(),
// IsOptionalAttendee = attendee.Optional ?? false, IsOptionalAttendee = attendee.Optional ?? false,
// }; };
// attendees.Add(eventAttendee); attendees.Add(eventAttendee);
// } }
// } }
//} }
await CalendarService.CreateNewCalendarItemAsync(calendarItem, attendees); await CalendarService.CreateNewCalendarItemAsync(calendarItem, attendees);
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Graph.Models; using Microsoft.Graph.Models;
using Serilog; using Serilog;
@@ -44,9 +45,6 @@ namespace Wino.Core.Integration.Processors
public async Task ManageCalendarEventAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount) public async Task ManageCalendarEventAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount)
{ {
// TODO: Make sure to call this method ordered by type:SeriesMaster first.
// otherwise we might lose exceptions.s
// We parse the occurrences based on the parent event. // We parse the occurrences based on the parent event.
// There is literally no point to store them because // There is literally no point to store them because
// type=Exception events are the exceptional childs of recurrency parent event. // type=Exception events are the exceptional childs of recurrency parent event.
@@ -140,6 +138,14 @@ namespace Wino.Core.Integration.Processors
// Upsert the event. // Upsert the event.
await Connection.InsertOrReplaceAsync(savingItem); await Connection.InsertOrReplaceAsync(savingItem);
// Manage attendees.
if (calendarEvent.Attendees != null)
{
// Clear all attendees for this event.
var attendees = calendarEvent.Attendees.Select(a => a.CreateAttendee(savingItemId)).ToList();
await CalendarService.ManageEventAttendeesAsync(savingItemId, attendees).ConfigureAwait(false);
}
} }
} }
} }

View File

@@ -964,7 +964,7 @@ namespace Wino.Core.Synchronizers.Mail
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false); await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false);
var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false); var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);

View File

@@ -136,6 +136,8 @@ namespace Wino.Services
return false; return false;
} }
public void GoBack() => throw new NotImplementedException("GoBack method is not implemented in Wino Mail.");
// Standalone EML viewer. // Standalone EML viewer.
//public void NavigateRendering(MimeMessageInformation mimeMessageInformation, NavigationTransitionType transition = NavigationTransitionType.None) //public void NavigateRendering(MimeMessageInformation mimeMessageInformation, NavigationTransitionType transition = NavigationTransitionType.None)
//{ //{

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -9,6 +10,7 @@ using Ical.Net.DataTypes;
using SqlKata; using SqlKata;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Calendar;
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;
using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Calendar;
@@ -178,6 +180,16 @@ namespace Wino.Services
public Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId) public Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId)
=> Connection.GetAsync<AccountCalendar>(accountCalendarId); => Connection.GetAsync<AccountCalendar>(accountCalendarId);
public Task<CalendarItem> GetCalendarItemAsync(Guid id)
{
var query = new Query()
.From(nameof(CalendarItem))
.Where(nameof(CalendarItem.Id), id);
var rawQuery = query.GetRawQuery();
return Connection.FindWithQueryAsync<CalendarItem>(rawQuery);
}
public async Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId) public async Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId)
{ {
var query = new Query() var query = new Query()
@@ -207,5 +219,88 @@ namespace Wino.Services
return Connection.ExecuteAsync(query.GetRawQuery()); return Connection.ExecuteAsync(query.GetRawQuery());
} }
public Task<List<CalendarEventAttendee>> GetAttendeesAsync(Guid calendarEventTrackingId)
=> Connection.Table<CalendarEventAttendee>().Where(x => x.CalendarItemId == calendarEventTrackingId).ToListAsync();
public async Task<List<CalendarEventAttendee>> ManageEventAttendeesAsync(Guid calendarItemId, List<CalendarEventAttendee> allAttendees)
{
await Connection.RunInTransactionAsync((connection) =>
{
// Clear all attendees.
var query = new Query()
.From(nameof(CalendarEventAttendee))
.Where(nameof(CalendarEventAttendee.CalendarItemId), calendarItemId)
.AsDelete();
connection.Execute(query.GetRawQuery());
// Insert new attendees.
connection.InsertAll(allAttendees);
});
return await Connection.Table<CalendarEventAttendee>().Where(a => a.CalendarItemId == calendarItemId).ToListAsync();
}
public async Task<CalendarItem> GetCalendarItemTargetAsync(CalendarItemTarget targetDetails)
{
var eventId = targetDetails.Item.Id;
// Get the event by Id first.
var item = await GetCalendarItemAsync(eventId).ConfigureAwait(false);
bool isRecurringChild = targetDetails.Item.IsRecurringChild;
bool isRecurringParent = targetDetails.Item.IsRecurringParent;
if (targetDetails.TargetType == CalendarEventTargetType.Single)
{
if (isRecurringChild)
{
if (item == null)
{
// This is an occurrence of a recurring event.
// They don't exist in db.
return targetDetails.Item;
}
else
{
// Single exception occurrence of recurring event.
// Return the item.
return item;
}
}
else if (isRecurringParent)
{
// Parent recurring events are never listed.
Debugger.Break();
return null;
}
else
{
// Single event.
return item;
}
}
else
{
// Series.
if (isRecurringChild)
{
// Return the parent.
return await GetCalendarItemAsync(targetDetails.Item.RecurringCalendarItemId.Value).ConfigureAwait(false);
}
else if (isRecurringParent)
return item;
else
{
// NA. Single events don't have series.
Debugger.Break();
return null;
}
}
}
} }
} }