Event details page navigation, handling of attendees in Outlook synchronizer, navigation changes for calendar.
This commit is contained in:
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
Wino.Core.Domain/Enums/CalendarEventTargetType.cs
Normal file
8
Wino.Core.Domain/Enums/CalendarEventTargetType.cs
Normal 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
Wino.Core.Domain/Models/Calendar/CalendarItemTarget.cs
Normal file
7
Wino.Core.Domain/Models/Calendar/CalendarItemTarget.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
//{
|
//{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user