Compare commits
1 Commits
feature/Ca
...
feature/Da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba7539e7d |
@@ -19,7 +19,6 @@
|
|||||||
<PackageVersion Include="CommunityToolkit.Uwp.Extensions" Version="8.2.250402" />
|
<PackageVersion Include="CommunityToolkit.Uwp.Extensions" Version="8.2.250402" />
|
||||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Primitives" Version="8.2.250129-preview2" />
|
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Primitives" Version="8.2.250129-preview2" />
|
||||||
<PackageVersion Include="EmailValidation" Version="1.3.0" />
|
<PackageVersion Include="EmailValidation" Version="1.3.0" />
|
||||||
<PackageVersion Include="gravatar-dotnet" Version="0.1.3" />
|
|
||||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.0" />
|
<PackageVersion Include="HtmlAgilityPack" Version="1.12.0" />
|
||||||
<PackageVersion Include="Ical.Net" Version="4.3.1" />
|
<PackageVersion Include="Ical.Net" Version="4.3.1" />
|
||||||
<PackageVersion Include="IsExternalInit" Version="1.0.3" />
|
<PackageVersion Include="IsExternalInit" Version="1.0.3" />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
@@ -142,16 +141,8 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
Type = CalendarSynchronizationType.CalendarMetadata
|
Type = CalendarSynchronizationType.CalendarMetadata
|
||||||
};
|
};
|
||||||
|
|
||||||
var timer = new Stopwatch();
|
|
||||||
|
|
||||||
var synchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(new NewCalendarSynchronizationRequested(synchronizationOptions, SynchronizationSource.Client));
|
var synchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(new NewCalendarSynchronizationRequested(synchronizationOptions, SynchronizationSource.Client));
|
||||||
|
accountCreationDialog.Complete(cancel: false);
|
||||||
|
|
||||||
timer.Stop();
|
|
||||||
|
|
||||||
Debug.WriteLine("Synchronization completed in {timer.ElapsedMilliseconds} ms");
|
|
||||||
|
|
||||||
// TODO: Properly handle synchronization errors.
|
|
||||||
|
|
||||||
accountCreationDialog.Complete(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
private readonly ICalendarService _calendarService;
|
private readonly ICalendarService _calendarService;
|
||||||
private readonly INavigationService _navigationService;
|
private readonly INavigationService _navigationService;
|
||||||
private readonly IKeyPressService _keyPressService;
|
private readonly IKeyPressService _keyPressService;
|
||||||
private readonly ICalendarServiceEx _calendarServiceEx;
|
|
||||||
private readonly IPreferencesService _preferencesService;
|
private readonly IPreferencesService _preferencesService;
|
||||||
|
|
||||||
// Store latest rendered options.
|
// Store latest rendered options.
|
||||||
@@ -147,7 +146,6 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
ICalendarService calendarService,
|
ICalendarService calendarService,
|
||||||
INavigationService navigationService,
|
INavigationService navigationService,
|
||||||
IKeyPressService keyPressService,
|
IKeyPressService keyPressService,
|
||||||
ICalendarServiceEx calendarServiceEx,
|
|
||||||
IAccountCalendarStateService accountCalendarStateService,
|
IAccountCalendarStateService accountCalendarStateService,
|
||||||
IPreferencesService preferencesService)
|
IPreferencesService preferencesService)
|
||||||
{
|
{
|
||||||
@@ -157,7 +155,6 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
_calendarService = calendarService;
|
_calendarService = calendarService;
|
||||||
_navigationService = navigationService;
|
_navigationService = navigationService;
|
||||||
_keyPressService = keyPressService;
|
_keyPressService = keyPressService;
|
||||||
_calendarServiceEx = calendarServiceEx;
|
|
||||||
_preferencesService = preferencesService;
|
_preferencesService = preferencesService;
|
||||||
|
|
||||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||||
@@ -238,6 +235,23 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
|
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
|
||||||
private async Task SaveQuickEventAsync()
|
private async Task SaveQuickEventAsync()
|
||||||
{
|
{
|
||||||
|
var durationSeconds = (QuickEventEndTime - QuickEventStartTime).TotalSeconds;
|
||||||
|
|
||||||
|
//var testCalendarItem = new CalendarItem
|
||||||
|
//{
|
||||||
|
// CalendarId = SelectedQuickEventAccountCalendar.Id,
|
||||||
|
// StartDate = QuickEventStartTime,
|
||||||
|
// DurationInSeconds = durationSeconds,
|
||||||
|
// CreatedAt = DateTime.UtcNow,
|
||||||
|
// Description = string.Empty,
|
||||||
|
// Location = Location,
|
||||||
|
// Title = EventName,
|
||||||
|
// Id = Guid.NewGuid()
|
||||||
|
//};
|
||||||
|
|
||||||
|
//IsQuickEventDialogOpen = false;
|
||||||
|
//await _calendarService.CreateNewCalendarItemAsync(testCalendarItem, null);
|
||||||
|
|
||||||
// TODO: Create the request with the synchronizer.
|
// TODO: Create the request with the synchronizer.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,13 +635,10 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
// Check all the events for the given date range and calendar.
|
// Check all the events for the given date range and calendar.
|
||||||
// Then find the day representation for all the events returned, and add to the collection.
|
// Then find the day representation for all the events returned, and add to the collection.
|
||||||
|
|
||||||
var events = await _calendarServiceEx.GetExpandedEventsInDateRangeWithExceptionsAsync(dayRangeRenderModel.Period.Start, dayRangeRenderModel.Period.End, calendarViewModel.AccountCalendar).ConfigureAwait(false);
|
var events = await _calendarService.GetCalendarEventsAsync(calendarViewModel, dayRangeRenderModel).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var @event in events)
|
foreach (var @event in events)
|
||||||
{
|
{
|
||||||
// TODO: Do it in the service.
|
|
||||||
@event.AssignedCalendar = calendarViewModel.AccountCalendar;
|
|
||||||
|
|
||||||
// Find the days that the event falls into.
|
// Find the days that the event falls into.
|
||||||
var allDaysForEvent = dayRangeRenderModel.CalendarDays.Where(a => a.Period.OverlapsWith(@event.Period));
|
var allDaysForEvent = dayRangeRenderModel.CalendarDays.Where(a => a.Period.OverlapsWith(@event.Period));
|
||||||
|
|
||||||
@@ -789,18 +800,19 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
// 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 == null) return Enumerable.Empty<CalendarItemViewModel>();
|
if (!calendarItemViewModel.IsRecurringEvent)
|
||||||
|
{
|
||||||
// If the calendar item is not recurring, we can just return it.
|
return [calendarItemViewModel];
|
||||||
|
}
|
||||||
if (calendarItemViewModel.ItemType == CalendarItemType.Timed || calendarItemViewModel.ItemType == CalendarItemType.RecurringException) return new[] { calendarItemViewModel };
|
else
|
||||||
|
{
|
||||||
return DayRanges
|
return DayRanges
|
||||||
.SelectMany(a => a.CalendarDays)
|
.SelectMany(a => a.CalendarDays)
|
||||||
.Select(b => b.EventsCollection.GetCalendarItem(calendarItemViewModel.Id))
|
.Select(b => b.EventsCollection.GetCalendarItem(calendarItemViewModel.Id))
|
||||||
.Where(c => c != null)
|
.Where(c => c != null)
|
||||||
.Cast<CalendarItemViewModel>()
|
.Cast<CalendarItemViewModel>()
|
||||||
.Distinct();
|
.Distinct();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UnselectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null)
|
private void UnselectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null)
|
||||||
@@ -809,8 +821,6 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
|
|
||||||
var itemsToUnselect = GetCalendarItems(calendarItemViewModel, calendarDay);
|
var itemsToUnselect = GetCalendarItems(calendarItemViewModel, calendarDay);
|
||||||
|
|
||||||
if (itemsToUnselect == null) return;
|
|
||||||
|
|
||||||
foreach (var item in itemsToUnselect)
|
foreach (var item in itemsToUnselect)
|
||||||
{
|
{
|
||||||
item.IsSelected = false;
|
item.IsSelected = false;
|
||||||
@@ -823,8 +833,6 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
|
|
||||||
var itemsToSelect = GetCalendarItems(calendarItemViewModel, calendarDay);
|
var itemsToSelect = GetCalendarItems(calendarItemViewModel, calendarDay);
|
||||||
|
|
||||||
if (itemsToSelect == null) return;
|
|
||||||
|
|
||||||
foreach (var item in itemsToSelect)
|
foreach (var item in itemsToSelect)
|
||||||
{
|
{
|
||||||
item.IsSelected = true;
|
item.IsSelected = true;
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
|
||||||
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;
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Data;
|
namespace Wino.Calendar.ViewModels.Data;
|
||||||
@@ -19,73 +17,28 @@ public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, IC
|
|||||||
|
|
||||||
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
||||||
|
|
||||||
public DateTime StartDateTime { get => CalendarItem.StartDateTime; set => CalendarItem.StartDateTime = value; }
|
public DateTime StartDate => CalendarItem.StartDate;
|
||||||
|
|
||||||
public DateTime EndDateTime => CalendarItem.EndDateTime;
|
public DateTime EndDate => CalendarItem.EndDate;
|
||||||
|
|
||||||
/// <summary>
|
public double DurationInSeconds => CalendarItem.DurationInSeconds;
|
||||||
/// Gets the start date and time in the local time zone for display purposes.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime LocalStartDateTime => ConvertToLocalTime();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the end date and time in the local time zone for display purposes.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime LocalEndDateTime => ConvertToLocalTime();
|
|
||||||
|
|
||||||
public ITimePeriod Period => CalendarItem.Period;
|
public ITimePeriod Period => CalendarItem.Period;
|
||||||
|
|
||||||
public bool IsRecurringEvent => !string.IsNullOrEmpty(CalendarItem.RecurrenceRules) || !string.IsNullOrEmpty(CalendarItem.RecurringEventId);
|
public bool IsAllDayEvent => CalendarItem.IsAllDayEvent;
|
||||||
|
public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent;
|
||||||
|
public bool IsRecurringEvent => CalendarItem.IsRecurringEvent;
|
||||||
|
public bool IsRecurringChild => CalendarItem.IsRecurringChild;
|
||||||
|
public bool IsRecurringParent => CalendarItem.IsRecurringParent;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isSelected;
|
private bool _isSelected;
|
||||||
|
|
||||||
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
|
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
|
||||||
|
|
||||||
public CalendarItemType ItemType => CalendarItem.ItemType;
|
|
||||||
|
|
||||||
public CalendarItemViewModel(CalendarItem calendarItem)
|
public CalendarItemViewModel(CalendarItem calendarItem)
|
||||||
{
|
{
|
||||||
CalendarItem = calendarItem;
|
CalendarItem = calendarItem;
|
||||||
|
|
||||||
Debug.WriteLine($"{Title} : {ItemType}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a DateTime to local time based on the provided timezone.
|
|
||||||
/// If timezone is empty or null, assumes the DateTime is in UTC.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dateTime">The DateTime to convert</param>
|
|
||||||
/// <param name="timeZone">The timezone string. If empty/null, assumes UTC.</param>
|
|
||||||
/// <returns>DateTime converted to local time</returns>
|
|
||||||
private DateTime ConvertToLocalTime()
|
|
||||||
{
|
|
||||||
// All day events ignore time zones and are treated as local time.
|
|
||||||
if (ItemType == CalendarItemType.AllDay || ItemType == CalendarItemType.MultiDayAllDay || ItemType == CalendarItemType.RecurringAllDay)
|
|
||||||
return CalendarItem.StartDateTime;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(CalendarItem.TimeZone))
|
|
||||||
{
|
|
||||||
// If no timezone specified, assume it's UTC and convert to local time
|
|
||||||
return DateTime.SpecifyKind(CalendarItem.StartDateTime, DateTimeKind.Utc).ToLocalTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Parse the timezone and convert to local time
|
|
||||||
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(CalendarItem.TimeZone);
|
|
||||||
return TimeZoneInfo.ConvertTimeToUtc(CalendarItem.StartDateTime, sourceTimeZone).ToLocalTime();
|
|
||||||
}
|
|
||||||
catch (TimeZoneNotFoundException)
|
|
||||||
{
|
|
||||||
// If timezone is not found, fallback to treating as UTC
|
|
||||||
return DateTime.SpecifyKind(CalendarItem.StartDateTime, DateTimeKind.Utc).ToLocalTime();
|
|
||||||
}
|
|
||||||
catch (InvalidTimeZoneException)
|
|
||||||
{
|
|
||||||
// If timezone is invalid, fallback to treating as UTC
|
|
||||||
return DateTime.SpecifyKind(CalendarItem.StartDateTime, DateTimeKind.Utc).ToLocalTime();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => CalendarItem.Title;
|
public override string ToString() => CalendarItem.Title;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private CalendarItemViewModel _seriesParent;
|
private CalendarItemViewModel _seriesParent;
|
||||||
|
|
||||||
public bool CanViewSeries => false; //CurrentEvent?.IsRecurringChild ?? false; // TODO: Implement this properly
|
public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
|||||||
|
|
||||||
CurrentEvent = new CalendarItemViewModel(currentEventItem);
|
CurrentEvent = new CalendarItemViewModel(currentEventItem);
|
||||||
|
|
||||||
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.Id);
|
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId);
|
||||||
|
|
||||||
foreach (var item in attendees)
|
foreach (var item in attendees)
|
||||||
{
|
{
|
||||||
|
|||||||
308
Wino.Calendar.sln
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.12.35424.110
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core.Domain", "Wino.Core.Domain\Wino.Core.Domain.csproj", "{814400B6-5A05-4596-B451-3A116A147DC1}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Core.UWP", "Wino.Core.UWP\Wino.Core.UWP.csproj", "{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core.ViewModels", "Wino.Core.ViewModels\Wino.Core.ViewModels.csproj", "{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Messaging", "Wino.Messages\Wino.Messaging.csproj", "{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Server", "Wino.Server\Wino.Server.csproj", "{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core", "Wino.Core\Wino.Core.csproj", "{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Calendar", "Wino.Calendar\Wino.Calendar.csproj", "{600F4979-DB7E-409D-B7DA-B60BE4C55C35}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.SourceGenerators", "Wino.SourceGenerators\Wino.SourceGenerators.csproj", "{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}"
|
||||||
|
EndProject
|
||||||
|
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Wino.Calendar.Packaging", "Wino.Calendar.Packaging\Wino.Calendar.Packaging.wapproj", "{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Calendar.ViewModels", "Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj", "{CF850F8C-5042-4376-9CBA-C8F2BB554083}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Services", "Wino.Services\Wino.Services.csproj", "{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Authentication", "Wino.Authentication\Wino.Authentication.csproj", "{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|ARM = Debug|ARM
|
||||||
|
Debug|ARM64 = Debug|ARM64
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|ARM = Release|ARM
|
||||||
|
Release|ARM64 = Release|ARM64
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x64.Build.0 = Release|x64
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x86.Build.0 = Release|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.Build.0 = Release|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.Build.0 = Release|x86
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x64.Build.0 = Release|x64
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x86.Build.0 = Release|x86
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x64.Build.0 = Release|x64
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x86.Build.0 = Release|x86
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM.ActiveCfg = Debug|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM.Build.0 = Debug|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|Any CPU.Build.0 = Release|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM.ActiveCfg = Release|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM.Build.0 = Release|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x64.Build.0 = Release|x64
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x86.Build.0 = Release|x86
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x64.Build.0 = Release|x64
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x86.Build.0 = Release|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.Deploy.0 = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.Build.0 = Debug|ARM
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Deploy.0 = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Deploy.0 = Debug|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.Build.0 = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.Deploy.0 = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.ActiveCfg = Release|ARM
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.Build.0 = Release|ARM
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.Deploy.0 = Release|ARM
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Build.0 = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Deploy.0 = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Build.0 = Release|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Deploy.0 = Release|x86
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.Build.0 = Debug|ARM
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Deploy.0 = Debug|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Deploy.0 = Debug|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.ActiveCfg = Release|ARM
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.Build.0 = Release|ARM
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.Deploy.0 = Release|ARM
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Build.0 = Release|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Deploy.0 = Release|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Build.0 = Release|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Deploy.0 = Release|x86
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x64.Build.0 = Release|x64
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x86.Build.0 = Release|x86
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x64.Build.0 = Release|x64
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x86.Build.0 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
<!--<controls:WinoFontIcon
|
<controls:WinoFontIcon
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||||
Icon="CalendarEventRepeat"
|
Icon="CalendarEventRepeat"
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
FontSize="12"
|
FontSize="12"
|
||||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||||
Icon="CalendarEventMuiltiDay"
|
Icon="CalendarEventMuiltiDay"
|
||||||
Visibility="{x:Bind CalendarItem.IsMultiDayEvent, Mode=OneWay}" />-->
|
Visibility="{x:Bind CalendarItem.IsMultiDayEvent, Mode=OneWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<VisualStateManager.VisualStateGroups>
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Windows.UI.Xaml.Input;
|
|||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
using Wino.Calendar.ViewModels.Messages;
|
using Wino.Calendar.ViewModels.Messages;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls;
|
namespace Wino.Calendar.Controls;
|
||||||
@@ -91,9 +90,7 @@ public sealed partial class CalendarItemControl : UserControl
|
|||||||
if (CalendarItem == null) return;
|
if (CalendarItem == null) return;
|
||||||
if (DisplayingDate == null) return;
|
if (DisplayingDate == null) return;
|
||||||
|
|
||||||
bool isMultiDayEvent = CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDay || CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay;
|
if (CalendarItem.IsMultiDayEvent)
|
||||||
|
|
||||||
if (isMultiDayEvent)
|
|
||||||
{
|
{
|
||||||
// Multi day events are divided into 3 categories:
|
// Multi day events are divided into 3 categories:
|
||||||
// 1. All day events
|
// 1. All day events
|
||||||
@@ -106,14 +103,14 @@ public sealed partial class CalendarItemControl : UserControl
|
|||||||
periodRelation == PeriodRelation.EnclosingStartTouching)
|
periodRelation == PeriodRelation.EnclosingStartTouching)
|
||||||
{
|
{
|
||||||
// hour -> title
|
// hour -> title
|
||||||
CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDateTime.TimeOfDay)} -> {CalendarItem.Title}";
|
CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDate.TimeOfDay)} -> {CalendarItem.Title}";
|
||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
periodRelation == PeriodRelation.EndInside ||
|
periodRelation == PeriodRelation.EndInside ||
|
||||||
periodRelation == PeriodRelation.EnclosingEndTouching)
|
periodRelation == PeriodRelation.EnclosingEndTouching)
|
||||||
{
|
{
|
||||||
// title <- hour
|
// title <- hour
|
||||||
CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDateTime.TimeOfDay)}";
|
CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDate.TimeOfDay)}";
|
||||||
}
|
}
|
||||||
else if (periodRelation == PeriodRelation.Enclosing)
|
else if (periodRelation == PeriodRelation.Enclosing)
|
||||||
{
|
{
|
||||||
@@ -142,11 +139,11 @@ public sealed partial class CalendarItemControl : UserControl
|
|||||||
{
|
{
|
||||||
if (CalendarItem == null) return;
|
if (CalendarItem == null) return;
|
||||||
|
|
||||||
if (CalendarItem.CalendarItem.ItemType == CalendarItemType.AllDay)
|
if (CalendarItem.IsAllDayEvent)
|
||||||
{
|
{
|
||||||
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
||||||
}
|
}
|
||||||
else if (CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay || CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDay)
|
else if (CalendarItem.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
if (IsCustomEventArea)
|
if (IsCustomEventArea)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using Windows.UI.Xaml;
|
|||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Calendar.Models;
|
using Wino.Calendar.Models;
|
||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls;
|
namespace Wino.Calendar.Controls;
|
||||||
@@ -45,9 +44,9 @@ public partial class WinoCalendarPanel : Panel
|
|||||||
|
|
||||||
private void ResetMeasurements() => _measurements.Clear();
|
private void ResetMeasurements() => _measurements.Clear();
|
||||||
|
|
||||||
private double GetChildTopMargin(ICalendarItemViewModel calendarItemViewModel, double availableHeight)
|
private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight)
|
||||||
{
|
{
|
||||||
var childStart = calendarItemViewModel.LocalStartDateTime;
|
var childStart = calendarItemViewModel.StartDate;
|
||||||
|
|
||||||
if (childStart <= Period.Start)
|
if (childStart <= Period.Start)
|
||||||
{
|
{
|
||||||
@@ -69,10 +68,10 @@ public partial class WinoCalendarPanel : Panel
|
|||||||
private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
||||||
=> availableWidth * calendarItemMeasurement.Left;
|
=> availableWidth * calendarItemMeasurement.Left;
|
||||||
|
|
||||||
private double GetChildHeight(ICalendarItemViewModel child)
|
private double GetChildHeight(ICalendarItem child)
|
||||||
{
|
{
|
||||||
// All day events are not measured.
|
// All day events are not measured.
|
||||||
if (child.ItemType == CalendarItemType.AllDay) return 0;
|
if (child.IsAllDayEvent) return 0;
|
||||||
|
|
||||||
double childDurationInMinutes = 0d;
|
double childDurationInMinutes = 0d;
|
||||||
double availableHeight = HourHeight * 24;
|
double availableHeight = HourHeight * 24;
|
||||||
@@ -81,7 +80,7 @@ public partial class WinoCalendarPanel : Panel
|
|||||||
|
|
||||||
// Debug.WriteLine($"Render relation of {child.Title} ({child.Period.Start} - {child.Period.End}) is {periodRelation} with {Period.Start.Day}");
|
// Debug.WriteLine($"Render relation of {child.Title} ({child.Period.Start} - {child.Period.End}) is {periodRelation} with {Period.Start.Day}");
|
||||||
|
|
||||||
if (child.ItemType != CalendarItemType.MultiDay || child.ItemType != CalendarItemType.MultiDayAllDay)
|
if (!child.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
childDurationInMinutes = child.Period.Duration.TotalMinutes;
|
childDurationInMinutes = child.Period.Duration.TotalMinutes;
|
||||||
}
|
}
|
||||||
@@ -128,7 +127,7 @@ public partial class WinoCalendarPanel : Panel
|
|||||||
foreach (var control in calendarControls)
|
foreach (var control in calendarControls)
|
||||||
{
|
{
|
||||||
// We can't arrange this child.
|
// We can't arrange this child.
|
||||||
if (!(control.Content is ICalendarItemViewModel child)) continue;
|
if (!(control.Content is ICalendarItem child)) continue;
|
||||||
|
|
||||||
bool isHorizontallyLastItem = false;
|
bool isHorizontallyLastItem = false;
|
||||||
|
|
||||||
@@ -161,7 +160,7 @@ public partial class WinoCalendarPanel : Panel
|
|||||||
double extraRightMargin = 0;
|
double extraRightMargin = 0;
|
||||||
|
|
||||||
// Multi-day events don't have any margin and their hit test is disabled.
|
// Multi-day events don't have any margin and their hit test is disabled.
|
||||||
if (child.ItemType != CalendarItemType.MultiDay || child.ItemType != CalendarItemType.MultiDayAllDay)
|
if (!child.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
// Max of 5% of the width or 20px max.
|
// Max of 5% of the width or 20px max.
|
||||||
extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0;
|
extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0;
|
||||||
@@ -169,10 +168,8 @@ public partial class WinoCalendarPanel : Panel
|
|||||||
|
|
||||||
if (childWidth < 0) childWidth = 1;
|
if (childWidth < 0) childWidth = 1;
|
||||||
|
|
||||||
bool isAllOrMultiDayEvent = child.ItemType == CalendarItemType.AllDay || child.ItemType == CalendarItemType.MultiDay || child.ItemType == CalendarItemType.MultiDayAllDay;
|
|
||||||
|
|
||||||
// Regular events must have 2px margin
|
// Regular events must have 2px margin
|
||||||
if (!isAllOrMultiDayEvent)
|
if (!child.IsMultiDayEvent && !child.IsAllDayEvent)
|
||||||
{
|
{
|
||||||
childLeft += 2;
|
childLeft += 2;
|
||||||
childTop += 2;
|
childTop += 2;
|
||||||
@@ -214,12 +211,10 @@ public partial class WinoCalendarPanel : Panel
|
|||||||
var columns = new List<List<ICalendarItem>>();
|
var columns = new List<List<ICalendarItem>>();
|
||||||
DateTime? lastEventEnding = null;
|
DateTime? lastEventEnding = null;
|
||||||
|
|
||||||
foreach (var ev in events.OrderBy(ev => ev.StartDateTime).ThenBy(ev => ev.EndDateTime))
|
foreach (var ev in events.OrderBy(ev => ev.StartDate).ThenBy(ev => ev.EndDate))
|
||||||
{
|
{
|
||||||
// Multi-day events are not measured.
|
// Multi-day events are not measured.
|
||||||
bool isMultiDayEvent = ev.ItemType == CalendarItemType.MultiDay || ev.ItemType == CalendarItemType.MultiDayAllDay;
|
if (ev.IsMultiDayEvent) continue;
|
||||||
|
|
||||||
if (isMultiDayEvent) continue;
|
|
||||||
|
|
||||||
if (ev.Period.Start >= lastEventEnding)
|
if (ev.Period.Start >= lastEventEnding)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ public static class CalendarXamlHelpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||||
{
|
{
|
||||||
// TODO: This is not correct.
|
|
||||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||||
|
|
||||||
var start = calendarItemViewModel.Period.Start;
|
var start = calendarItemViewModel.Period.Start;
|
||||||
@@ -31,7 +30,7 @@ public static class CalendarXamlHelpers
|
|||||||
string timeFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "h:mm tt" : "HH:mm";
|
string timeFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "h:mm tt" : "HH:mm";
|
||||||
string dateFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "dddd, dd MMMM h:mm tt" : "dddd, dd MMMM HH:mm";
|
string dateFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "dddd, dd MMMM h:mm tt" : "dddd, dd MMMM HH:mm";
|
||||||
|
|
||||||
if (calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.MultiDay || calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay)
|
if (calendarItemViewModel.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
||||||
}
|
}
|
||||||
@@ -43,14 +42,54 @@ public static class CalendarXamlHelpers
|
|||||||
|
|
||||||
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
||||||
{
|
{
|
||||||
// TODO
|
if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringChild) return string.Empty;
|
||||||
return string.Empty;
|
|
||||||
|
// Parse recurrence rules
|
||||||
|
var calendarEvent = new CalendarEvent
|
||||||
|
{
|
||||||
|
Start = new CalDateTime(calendarItemViewModel.StartDate),
|
||||||
|
End = new CalDateTime(calendarItemViewModel.EndDate),
|
||||||
|
};
|
||||||
|
|
||||||
|
var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator);
|
||||||
|
|
||||||
|
foreach (var line in recurrenceLines)
|
||||||
|
{
|
||||||
|
calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendarEvent.RecurrenceRules == null || !calendarEvent.RecurrenceRules.Any())
|
||||||
|
{
|
||||||
|
return "No recurrence pattern.";
|
||||||
|
}
|
||||||
|
|
||||||
|
var recurrenceRule = calendarEvent.RecurrenceRules.First();
|
||||||
|
var daysOfWeek = string.Join(", ", recurrenceRule.ByDay.Select(day => day.DayOfWeek.ToString()));
|
||||||
|
string timeZone = calendarEvent.DtStart.TzId ?? "UTC";
|
||||||
|
|
||||||
|
return $"Every {daysOfWeek}, effective {calendarEvent.DtStart.Value.ToShortDateString()} " +
|
||||||
|
$"from {calendarEvent.DtStart.Value.ToShortTimeString()} to {calendarEvent.DtEnd.Value.ToShortTimeString()} " +
|
||||||
|
$"{timeZone}.";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||||
{
|
{
|
||||||
// TODO
|
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||||
return string.Empty;
|
|
||||||
|
// Single event in a day.
|
||||||
|
if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent)
|
||||||
|
{
|
||||||
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}";
|
||||||
|
}
|
||||||
|
else if (calendarItemViewModel.IsMultiDayEvent)
|
||||||
|
{
|
||||||
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// All day event.
|
||||||
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
||||||
@@ -59,12 +98,8 @@ public static class CalendarXamlHelpers
|
|||||||
{
|
{
|
||||||
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
||||||
|
|
||||||
bool isAllDayOrMultiDay = calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.MultiDay ||
|
|
||||||
calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.AllDay ||
|
|
||||||
calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay;
|
|
||||||
|
|
||||||
// All and/or multi day events always go to the top of the screen.
|
// All and/or multi day events always go to the top of the screen.
|
||||||
if (isAllDayOrMultiDay) return PopupPlacementMode.Bottom;
|
if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom;
|
||||||
|
|
||||||
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public partial class CustomAreaCalendarItemSelector : DataTemplateSelector
|
|||||||
{
|
{
|
||||||
if (item is CalendarItemViewModel calendarItemViewModel)
|
if (item is CalendarItemViewModel calendarItemViewModel)
|
||||||
{
|
{
|
||||||
return calendarItemViewModel.CalendarItem.ItemType == Core.Domain.Enums.CalendarItemType.MultiDay ||
|
return calendarItemViewModel.IsMultiDayEvent ? MultiDayTemplate : AllDayTemplate;
|
||||||
calendarItemViewModel.CalendarItem.ItemType == Core.Domain.Enums.CalendarItemType.MultiDayAllDay ? MultiDayTemplate : AllDayTemplate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.SelectTemplateCore(item, container);
|
return base.SelectTemplateCore(item, container);
|
||||||
|
|||||||
@@ -89,7 +89,6 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
BorderBrush="Transparent"
|
BorderBrush="Transparent"
|
||||||
IsTabStop="False"
|
|
||||||
PlaceholderText="Search" />
|
PlaceholderText="Search" />
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
|||||||
@@ -256,7 +256,7 @@
|
|||||||
<PersonPicture
|
<PersonPicture
|
||||||
Width="40"
|
Width="40"
|
||||||
Height="40"
|
Height="40"
|
||||||
DisplayName="{x:Bind DisplayName}" />
|
DisplayName="{x:Bind Name}" />
|
||||||
|
|
||||||
<!-- TODO: Organizer -->
|
<!-- TODO: Organizer -->
|
||||||
<Grid Grid.Column="1">
|
<Grid Grid.Column="1">
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind DisplayName}" />
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
FontSize="13"
|
FontSize="13"
|
||||||
|
|||||||
@@ -80,11 +80,11 @@ public class CalendarEventCollection
|
|||||||
// Multi-day events go to both.
|
// Multi-day events go to both.
|
||||||
// Anything else goes to regular.
|
// Anything else goes to regular.
|
||||||
|
|
||||||
if (calendarItem.ItemType == Enums.CalendarItemType.AllDay || calendarItem.ItemType == Enums.CalendarItemType.MultiDayAllDay || calendarItem.ItemType == Enums.CalendarItemType.RecurringAllDay)
|
if (calendarItem.IsAllDayEvent)
|
||||||
{
|
{
|
||||||
return [_internalAllDayEvents];
|
return [_internalAllDayEvents];
|
||||||
}
|
}
|
||||||
else if (calendarItem.ItemType == Enums.CalendarItemType.MultiDay || calendarItem.ItemType == Enums.CalendarItemType.MultiDayAllDay)
|
else if (calendarItem.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
return [_internalRegularEvents, _internalAllDayEvents];
|
return [_internalRegularEvents, _internalAllDayEvents];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ public static class Constants
|
|||||||
public const string WinoLocalDraftHeader = "X-Wino-Draft-Id";
|
public const string WinoLocalDraftHeader = "X-Wino-Draft-Id";
|
||||||
public const string LocalDraftStartPrefix = "localDraft_";
|
public const string LocalDraftStartPrefix = "localDraft_";
|
||||||
|
|
||||||
|
public const string CalendarEventRecurrenceRuleSeperator = "___";
|
||||||
|
|
||||||
public const string ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey);
|
public const string ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey);
|
||||||
public const string ToastActionKey = nameof(ToastActionKey);
|
public const string ToastActionKey = nameof(ToastActionKey);
|
||||||
|
|
||||||
|
|||||||
@@ -7,40 +7,12 @@ namespace Wino.Core.Domain.Entities.Calendar;
|
|||||||
public class AccountCalendar : IAccountCalendar
|
public class AccountCalendar : IAccountCalendar
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
[NotNull]
|
|
||||||
public string RemoteCalendarId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[NotNull]
|
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
|
public string RemoteCalendarId { get; set; }
|
||||||
[NotNull]
|
public string SynchronizationDeltaToken { get; set; }
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; }
|
||||||
|
public bool IsPrimary { get; set; }
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
public string? Location { get; set; }
|
|
||||||
|
|
||||||
public string? TimeZone { get; set; }
|
|
||||||
|
|
||||||
public string? AccessRole { get; set; }
|
|
||||||
|
|
||||||
public bool IsPrimary { get; set; } = false;
|
|
||||||
|
|
||||||
public string? BackgroundColor { get; set; }
|
|
||||||
|
|
||||||
public string? ForegroundColor { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedDate { get; set; }
|
|
||||||
|
|
||||||
public DateTime LastModified { get; set; }
|
|
||||||
|
|
||||||
public DateTime? LastSyncTime { get; set; }
|
|
||||||
|
|
||||||
public string? SynchronizationDeltaToken { get; set; }
|
|
||||||
|
|
||||||
public bool IsDeleted { get; set; } = false;
|
|
||||||
public bool IsExtended { get; set; } = true;
|
public bool IsExtended { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -48,4 +20,5 @@ public class AccountCalendar : IAccountCalendar
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string TextColorHex { get; set; }
|
public string TextColorHex { get; set; }
|
||||||
public string BackgroundColorHex { get; set; }
|
public string BackgroundColorHex { get; set; }
|
||||||
|
public string TimeZone { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,29 +8,12 @@ namespace Wino.Core.Domain.Entities.Calendar;
|
|||||||
public class CalendarEventAttendee
|
public class CalendarEventAttendee
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; }
|
||||||
|
public Guid CalendarItemId { get; set; }
|
||||||
[NotNull]
|
public string Name { get; set; }
|
||||||
public Guid EventId { get; set; }
|
public string Email { get; set; }
|
||||||
|
public AttendeeStatus AttendenceStatus { get; set; }
|
||||||
[NotNull]
|
public bool IsOrganizer { get; set; }
|
||||||
public string Email { get; set; } = string.Empty;
|
public bool IsOptionalAttendee { get; set; }
|
||||||
|
public string Comment { get; set; }
|
||||||
public string? DisplayName { get; set; }
|
|
||||||
|
|
||||||
public AttendeeResponseStatus ResponseStatus { get; set; } = AttendeeResponseStatus.NeedsAction;
|
|
||||||
|
|
||||||
public bool IsOptional { get; set; } = false;
|
|
||||||
|
|
||||||
public bool IsOrganizer { get; set; } = false;
|
|
||||||
|
|
||||||
public bool IsSelf { get; set; } = false;
|
|
||||||
|
|
||||||
public string? Comment { get; set; }
|
|
||||||
|
|
||||||
public int? AdditionalGuests { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedDate { get; set; }
|
|
||||||
|
|
||||||
public DateTime LastModified { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Diagnostics;
|
|||||||
using Itenso.TimePeriod;
|
using Itenso.TimePeriod;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Helpers;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Entities.Calendar;
|
namespace Wino.Core.Domain.Entities.Calendar;
|
||||||
@@ -12,81 +11,166 @@ namespace Wino.Core.Domain.Entities.Calendar;
|
|||||||
public class CalendarItem : ICalendarItem
|
public class CalendarItem : ICalendarItem
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; }
|
||||||
|
public string RemoteEventId { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Location { get; set; }
|
||||||
|
|
||||||
[NotNull]
|
public string StartTimeOffset { get; set; }
|
||||||
public string RemoteEventId { get; set; } = string.Empty;
|
public string EndTimeOffset { get; set; }
|
||||||
|
|
||||||
[NotNull]
|
public DateTimeOffset StartDateTimeOffset => DateTimeOffset.Parse(StartTimeOffset);
|
||||||
public Guid CalendarId { get; set; }
|
public DateTimeOffset EndDateTimeOffsete => DateTimeOffset.Parse(EndTimeOffset);
|
||||||
|
|
||||||
[Ignore]
|
public DateTime StartDate => StartDateTimeOffset.DateTime;//TimeZoneInfo.ConvertTime(StartDateTimeOffset, TimeZoneInfo.Local).DateTime;
|
||||||
public IAccountCalendar AssignedCalendar { get; set; }
|
public DateTime EndDate => EndDateTimeOffsete.DateTime;// TimeZoneInfo.ConvertTime(, TimeZoneInfo.Local).DateTime;
|
||||||
|
|
||||||
[NotNull]
|
|
||||||
public string Title { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
public string? Location { get; set; }
|
|
||||||
public string? HtmlLink { get; set; }
|
|
||||||
|
|
||||||
public DateTime StartDateTime { get; set; }
|
|
||||||
|
|
||||||
public DateTime EndDateTime { get; set; }
|
|
||||||
|
|
||||||
private ITimePeriod _period;
|
private ITimePeriod _period;
|
||||||
public ITimePeriod Period
|
public ITimePeriod Period
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
_period ??= new TimeRange(StartDateTime, EndDateTime);
|
// Period must be loaded by user's current UI culture based on StartDateTimeOffset and EndDateTimeOffset
|
||||||
|
// Get the time zone corresponding to the current culture
|
||||||
|
|
||||||
|
_period ??= new TimeRange(StartDate, EndDate);
|
||||||
|
|
||||||
return _period;
|
return _period;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAllDay { get; set; }
|
|
||||||
|
|
||||||
public string? TimeZone { get; set; }
|
|
||||||
|
|
||||||
public string? RecurrenceRules { get; set; }
|
|
||||||
|
|
||||||
public string? Status { get; set; }
|
|
||||||
|
|
||||||
public string? OrganizerDisplayName { get; set; }
|
|
||||||
|
|
||||||
public string? OrganizerEmail { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedDate { get; set; }
|
|
||||||
|
|
||||||
public DateTime LastModified { get; set; }
|
|
||||||
|
|
||||||
public bool IsDeleted { get; set; }
|
|
||||||
|
|
||||||
public string? RecurringEventId { get; set; }
|
|
||||||
|
|
||||||
public string? OriginalStartTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of calendar item (Timed, AllDay, MultiDay, etc.)
|
/// Events that starts at midnight and ends at midnight are considered all-day events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CalendarItemType ItemType { get; set; }
|
public bool IsAllDayEvent
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Automatically determines and sets the ItemType based on event properties
|
|
||||||
/// </summary>
|
|
||||||
public void DetermineItemType()
|
|
||||||
{
|
{
|
||||||
var hasRecurrence = !string.IsNullOrEmpty(RecurrenceRules);
|
get
|
||||||
var isCancelled = Status?.ToLowerInvariant() == "cancelled" || IsDeleted;
|
{
|
||||||
|
return
|
||||||
|
StartDate.TimeOfDay == TimeSpan.Zero &&
|
||||||
|
EndDate.TimeOfDay == TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ItemType = CalendarItemTypeHelper.DetermineItemType(
|
/// <summary>
|
||||||
StartDateTime,
|
/// Events that are either an exceptional instance of a recurring event or occurrences.
|
||||||
EndDateTime,
|
/// IsOccurrence is used to display occurrence instances of parent recurring events.
|
||||||
IsAllDay,
|
/// IsOccurrence == false && IsRecurringChild == true => exceptional single instance.
|
||||||
hasRecurrence,
|
/// </summary>
|
||||||
isCancelled,
|
public bool IsRecurringChild
|
||||||
Status);
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return RecurringCalendarItemId != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// Events that are not all-day events and last more than one day are considered multi-day events.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMultiDayEvent
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Period.Duration.TotalDays >= 1 && !IsAllDayEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double DurationInSeconds => Period.Duration.TotalSeconds;
|
||||||
|
public string Recurrence { get; set; }
|
||||||
|
|
||||||
|
public string OrganizerDisplayName { get; set; }
|
||||||
|
public string OrganizerEmail { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The id of the parent calendar item of the recurring event.
|
||||||
|
/// Exceptional instances are stored as a separate calendar item.
|
||||||
|
/// This makes the calendar item a child of the recurring event.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? RecurringCalendarItemId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates read-only events. Default is false.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsLocked { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hidden events must not be displayed to the user.
|
||||||
|
/// This usually happens when a child instance of recurring parent is cancelled after creation.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHidden { get; set; }
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
public string CustomEventColorHex { get; set; }
|
||||||
|
public string HtmlLink { get; set; }
|
||||||
|
public CalendarItemStatus Status { get; set; }
|
||||||
|
public CalendarItemVisibility Visibility { get; set; }
|
||||||
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
|
public DateTimeOffset UpdatedAt { get; set; }
|
||||||
|
public Guid CalendarId { get; set; }
|
||||||
|
|
||||||
|
[Ignore]
|
||||||
|
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(DateTimeOffset startDateOffset, DateTimeOffset endDateOffset)
|
||||||
|
{
|
||||||
|
// Create a copy with the new start date and duration
|
||||||
|
|
||||||
|
return new CalendarItem
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Title = Title,
|
||||||
|
Description = Description,
|
||||||
|
Location = Location,
|
||||||
|
StartTimeOffset = startDateOffset.ToString("o"),
|
||||||
|
EndTimeOffset = endDateOffset.ToString("o"),
|
||||||
|
Recurrence = Recurrence,
|
||||||
|
OrganizerDisplayName = OrganizerDisplayName,
|
||||||
|
OrganizerEmail = OrganizerEmail,
|
||||||
|
RecurringCalendarItemId = Id,
|
||||||
|
AssignedCalendar = AssignedCalendar,
|
||||||
|
CalendarId = CalendarId,
|
||||||
|
CreatedAt = CreatedAt,
|
||||||
|
UpdatedAt = UpdatedAt,
|
||||||
|
Visibility = Visibility,
|
||||||
|
Status = Status,
|
||||||
|
CustomEventColorHex = CustomEventColorHex,
|
||||||
|
HtmlLink = HtmlLink,
|
||||||
|
RemoteEventId = RemoteEventId,
|
||||||
|
IsHidden = IsHidden,
|
||||||
|
IsLocked = IsLocked,
|
||||||
|
IsOccurrence = true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
using SQLite;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Entities.Shared;
|
|
||||||
|
|
||||||
public class Thumbnail
|
|
||||||
{
|
|
||||||
[PrimaryKey]
|
|
||||||
public string Domain { get; set; }
|
|
||||||
|
|
||||||
public string Gravatar { get; set; }
|
|
||||||
public string Favicon { get; set; }
|
|
||||||
public DateTime LastUpdated { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Enums;
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the response status of an attendee to a calendar event
|
|
||||||
/// </summary>
|
|
||||||
public enum AttendeeResponseStatus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The attendee has not responded to the invitation
|
|
||||||
/// </summary>
|
|
||||||
NeedsAction = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The attendee has accepted the invitation
|
|
||||||
/// </summary>
|
|
||||||
Accepted = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The attendee has declined the invitation
|
|
||||||
/// </summary>
|
|
||||||
Declined = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The attendee has tentatively accepted the invitation
|
|
||||||
/// </summary>
|
|
||||||
Tentative = 3
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Enums;
|
|
||||||
public enum CalendarItemType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A standard timed event with specific start and end times on the same day
|
|
||||||
/// </summary>
|
|
||||||
Timed = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An all-day event that spans exactly one day
|
|
||||||
/// </summary>
|
|
||||||
AllDay = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A multi-day event that spans more than one day but has specific times
|
|
||||||
/// </summary>
|
|
||||||
MultiDay = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A multi-day all-day event (e.g., vacation, conference spanning multiple days)
|
|
||||||
/// </summary>
|
|
||||||
MultiDayAllDay = 3,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A recurring event with a defined pattern (daily, weekly, monthly, yearly)
|
|
||||||
/// </summary>
|
|
||||||
Recurring = 4,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A recurring all-day event (e.g., annual holiday, weekly all-day event)
|
|
||||||
/// </summary>
|
|
||||||
RecurringAllDay = 5,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A single instance of a recurring event that has been modified
|
|
||||||
/// </summary>
|
|
||||||
RecurringException = 6,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An event that extends beyond midnight but is not multi-day (e.g., 11 PM to 2 AM)
|
|
||||||
/// </summary>
|
|
||||||
CrossMidnight = 7,
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Helpers;
|
|
||||||
/// <summary>
|
|
||||||
/// Helper class for CalendarItemType operations
|
|
||||||
/// </summary>
|
|
||||||
public static class CalendarItemTypeHelper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the calendar item type based on event properties
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startDateTime">Event start date/time</param>
|
|
||||||
/// <param name="endDateTime">Event end date/time</param>
|
|
||||||
/// <param name="isAllDay">Whether the event is marked as all-day</param>
|
|
||||||
/// <param name="isRecurring">Whether the event has recurrence rules</param>
|
|
||||||
/// <param name="isCancelled">Whether the event is cancelled</param>
|
|
||||||
/// <param name="status">Event status</param>
|
|
||||||
/// <returns>The appropriate CalendarItemType</returns>
|
|
||||||
public static CalendarItemType DetermineItemType(
|
|
||||||
DateTime startDateTime,
|
|
||||||
DateTime endDateTime,
|
|
||||||
bool isAllDay,
|
|
||||||
bool isRecurring = false,
|
|
||||||
bool isCancelled = false,
|
|
||||||
string? status = null)
|
|
||||||
{
|
|
||||||
// Handle recurring events
|
|
||||||
if (isRecurring)
|
|
||||||
{
|
|
||||||
return isAllDay ? CalendarItemType.RecurringAllDay : CalendarItemType.Recurring;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle all-day events
|
|
||||||
if (isAllDay)
|
|
||||||
{
|
|
||||||
var daySpan = (endDateTime.Date - startDateTime.Date).Days;
|
|
||||||
return daySpan > 1 ? CalendarItemType.MultiDayAllDay : CalendarItemType.AllDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle timed events
|
|
||||||
var duration = endDateTime - startDateTime;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Multi-day timed events
|
|
||||||
if (duration.TotalDays >= 1)
|
|
||||||
{
|
|
||||||
return CalendarItemType.MultiDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cross midnight events (same calendar day but extends past midnight)
|
|
||||||
if (startDateTime.Date != endDateTime.Date && duration.TotalHours <= 24)
|
|
||||||
{
|
|
||||||
return CalendarItemType.CrossMidnight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard timed events
|
|
||||||
return CalendarItemType.Timed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a human-readable description of the calendar item type
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="itemType">The calendar item type</param>
|
|
||||||
/// <returns>Description string</returns>
|
|
||||||
public static string GetDescription(CalendarItemType itemType)
|
|
||||||
{
|
|
||||||
return itemType switch
|
|
||||||
{
|
|
||||||
CalendarItemType.Timed => "Timed Event",
|
|
||||||
CalendarItemType.AllDay => "All-Day Event",
|
|
||||||
CalendarItemType.MultiDay => "Multi-Day Event",
|
|
||||||
CalendarItemType.MultiDayAllDay => "Multi-Day All-Day Event",
|
|
||||||
CalendarItemType.Recurring => "Recurring Event",
|
|
||||||
CalendarItemType.RecurringAllDay => "Recurring All-Day Event",
|
|
||||||
CalendarItemType.RecurringException => "Modified Recurring Event",
|
|
||||||
CalendarItemType.CrossMidnight => "Cross-Midnight Event",
|
|
||||||
_ => "Unknown Event Type"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the event type represents an all-day event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="itemType">The calendar item type</param>
|
|
||||||
/// <returns>True if it's an all-day event type</returns>
|
|
||||||
public static bool IsAllDayType(CalendarItemType itemType)
|
|
||||||
{
|
|
||||||
return itemType == CalendarItemType.AllDay ||
|
|
||||||
itemType == CalendarItemType.MultiDayAllDay ||
|
|
||||||
itemType == CalendarItemType.RecurringAllDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the event type represents a recurring event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="itemType">The calendar item type</param>
|
|
||||||
/// <returns>True if it's a recurring event type</returns>
|
|
||||||
public static bool IsRecurringType(CalendarItemType itemType)
|
|
||||||
{
|
|
||||||
return itemType == CalendarItemType.Recurring ||
|
|
||||||
itemType == CalendarItemType.RecurringAllDay ||
|
|
||||||
itemType == CalendarItemType.RecurringException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the event type represents a multi-day event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="itemType">The calendar item type</param>
|
|
||||||
/// <returns>True if it's a multi-day event type</returns>
|
|
||||||
public static bool IsMultiDayType(CalendarItemType itemType)
|
|
||||||
{
|
|
||||||
return itemType == CalendarItemType.MultiDay ||
|
|
||||||
itemType == CalendarItemType.MultiDayAllDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the priority level for sorting events (lower number = higher priority)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="itemType">The calendar item type</param>
|
|
||||||
/// <returns>Priority number for sorting</returns>
|
|
||||||
public static int GetSortPriority(CalendarItemType itemType)
|
|
||||||
{
|
|
||||||
return itemType switch
|
|
||||||
{
|
|
||||||
|
|
||||||
CalendarItemType.AllDay => 2,
|
|
||||||
CalendarItemType.MultiDayAllDay => 3,
|
|
||||||
CalendarItemType.Timed => 4,
|
|
||||||
CalendarItemType.CrossMidnight => 5,
|
|
||||||
CalendarItemType.MultiDay => 6,
|
|
||||||
CalendarItemType.Recurring => 7,
|
|
||||||
CalendarItemType.RecurringAllDay => 8,
|
|
||||||
CalendarItemType.RecurringException => 9,
|
|
||||||
_ => 99
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Itenso.TimePeriod;
|
using Itenso.TimePeriod;
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
@@ -9,8 +8,15 @@ public interface ICalendarItem
|
|||||||
string Title { get; }
|
string Title { get; }
|
||||||
Guid Id { get; }
|
Guid Id { get; }
|
||||||
IAccountCalendar AssignedCalendar { get; }
|
IAccountCalendar AssignedCalendar { get; }
|
||||||
DateTime StartDateTime { get; set; }
|
DateTime StartDate { get; }
|
||||||
DateTime EndDateTime { get; }
|
DateTime EndDate { get; }
|
||||||
|
double DurationInSeconds { get; }
|
||||||
ITimePeriod Period { get; }
|
ITimePeriod Period { get; }
|
||||||
CalendarItemType ItemType { get; }
|
|
||||||
|
bool IsAllDayEvent { get; }
|
||||||
|
bool IsMultiDayEvent { get; }
|
||||||
|
|
||||||
|
bool IsRecurringChild { get; }
|
||||||
|
bool IsRecurringParent { get; }
|
||||||
|
bool IsRecurringEvent { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using System;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporarily to enforce CalendarItemViewModel. Used in CalendarEventCollection.
|
/// Temporarily to enforce CalendarItemViewModel. Used in CalendarEventCollection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICalendarItemViewModel : ICalendarItem
|
public interface ICalendarItemViewModel
|
||||||
{
|
{
|
||||||
bool IsSelected { get; set; }
|
bool IsSelected { get; set; }
|
||||||
DateTime LocalStartDateTime { get; }
|
|
||||||
DateTime LocalEndDateTime { get; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
|
||||||
public interface ICalendarServiceEx
|
|
||||||
{
|
|
||||||
Task<int> ClearAllCalendarEventAttendeesAsync();
|
|
||||||
Task<int> ClearAllCalendarsAsync();
|
|
||||||
Task<int> ClearAllDataAsync();
|
|
||||||
Task<int> ClearAllEventsAsync();
|
|
||||||
Task<int> DeleteCalendarAsync(string remoteCalendarId);
|
|
||||||
Task<int> DeleteCalendarEventAttendeesForEventAsync(Guid eventId);
|
|
||||||
Task<int> DeleteEventAsync(string remoteEventId);
|
|
||||||
Task<List<CalendarEventAttendee>> GetAllCalendarEventAttendeesAsync();
|
|
||||||
Task<List<AccountCalendar>> GetAllCalendarsAsync();
|
|
||||||
Task<List<CalendarItem>> GetAllDayEventsAsync();
|
|
||||||
Task<List<CalendarItem>> GetAllEventsAsync();
|
|
||||||
Task<List<CalendarItem>> GetAllEventsIncludingDeletedAsync();
|
|
||||||
Task<List<CalendarItem>> GetAllRecurringEventsByTypeAsync();
|
|
||||||
Task<AccountCalendar> GetCalendarByRemoteIdAsync(string remoteCalendarId);
|
|
||||||
Task<Dictionary<AttendeeResponseStatus, int>> GetCalendarEventAttendeeResponseCountsAsync(Guid eventId);
|
|
||||||
Task<List<CalendarEventAttendee>> GetCalendarEventAttendeesForEventAsync(Guid eventId);
|
|
||||||
Task<List<CalendarEventAttendee>> GetCalendarEventAttendeesForEventByRemoteIdAsync(string remoteEventId);
|
|
||||||
Task<string> GetCalendarSyncTokenAsync(string calendarId);
|
|
||||||
Task<CalendarItem> GetEventByRemoteIdAsync(string remoteEventId);
|
|
||||||
Task<List<CalendarItem>> GetEventsByItemTypeAsync(CalendarItemType itemType);
|
|
||||||
Task<List<CalendarItem>> GetEventsByItemTypesAsync(params CalendarItemType[] itemTypes);
|
|
||||||
Task<List<CalendarItem>> GetEventsByremoteCalendarIdAsync(string remoteCalendarId);
|
|
||||||
Task<List<CalendarItem>> GetEventsForCalendarAsync(Guid calendarId);
|
|
||||||
Task<List<CalendarItem>> GetEventsInDateRangeAsync(DateTime startDate, DateTime endDate);
|
|
||||||
Task<List<CalendarItem>> GetEventsSinceLastSyncAsync(DateTime? lastSyncTime);
|
|
||||||
Task<Dictionary<CalendarItemType, int>> GetEventStatsByItemTypeAsync();
|
|
||||||
Task<List<CalendarItem>> GetExpandedEventsInDateRangeAsync(DateTime startDate, DateTime endDate);
|
|
||||||
Task<List<CalendarItem>> GetExpandedEventsInDateRangeWithExceptionsAsync(DateTime startDate, DateTime endDate, AccountCalendar calendar);
|
|
||||||
Task<DateTime?> GetLastSyncTimeAsync(string calendarId);
|
|
||||||
Task<List<CalendarItem>> GetMultiDayEventsAsync();
|
|
||||||
Task<List<CalendarItem>> GetRecurringEventsAsync();
|
|
||||||
Task<int> HardDeleteEventAsync(string remoteEventId);
|
|
||||||
Task<int> InsertCalendarAsync(AccountCalendar calendar);
|
|
||||||
Task<int> InsertCalendarEventAttendeeAsync(CalendarEventAttendee calendareventattendee);
|
|
||||||
Task<int> InsertEventAsync(CalendarItem calendarItem);
|
|
||||||
Task<int> MarkEventAsDeletedAsync(string remoteEventId, string remoteCalendarId);
|
|
||||||
Task<int> SyncAttendeesForEventAsync(Guid eventId, List<CalendarEventAttendee> attendees);
|
|
||||||
Task<int> SyncCalendarEventAttendeesForEventAsync(Guid eventId, List<CalendarEventAttendee> calendareventattendees);
|
|
||||||
Task<int> UpdateAllEventItemTypesAsync();
|
|
||||||
Task<int> UpdateCalendarAsync(AccountCalendar calendar);
|
|
||||||
Task<int> UpdateCalendarEventAttendeeAsync(CalendarEventAttendee calendareventattendee);
|
|
||||||
Task<int> UpdateCalendarSyncTokenAsync(string calendarId, string syncToken);
|
|
||||||
Task<int> UpdateEventAsync(CalendarItem calendarItem);
|
|
||||||
Task<int> UpsertCalendarAsync(AccountCalendar calendar);
|
|
||||||
Task<int> UpsertEventAsync(CalendarItem calendarItem);
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Core.Domain.Models.Reader;
|
using Wino.Core.Domain.Models.Reader;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
public interface IPreferencesService: INotifyPropertyChanged
|
public interface IPreferencesService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When any of the preferences are changed.
|
/// When any of the preferences are changed.
|
||||||
@@ -189,21 +188,6 @@ public interface IPreferencesService: INotifyPropertyChanged
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsMailListActionBarEnabled { get; set; }
|
bool IsMailListActionBarEnabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setting: Whether the mail rendering page will show the action labels
|
|
||||||
/// </summary>
|
|
||||||
bool IsShowActionLabelsEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setting: Enable/disable Gravatar for sender avatars.
|
|
||||||
/// </summary>
|
|
||||||
bool IsGravatarEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setting: Enable/disable Favicon for sender avatars.
|
|
||||||
/// </summary>
|
|
||||||
bool IsFaviconEnabled { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Calendar
|
#region Calendar
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
public interface IThumbnailService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the thumbnail cache.
|
|
||||||
/// </summary>
|
|
||||||
Task ClearCache();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets thumbnail
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="email">Address for thumbnail</param>
|
|
||||||
/// <param name="awaitLoad">Force to wait for thumbnail loading.
|
|
||||||
/// Should be used in non-UI threads or where delay is acceptable
|
|
||||||
/// </param>
|
|
||||||
ValueTask<string> GetThumbnailAsync(string email, bool awaitLoad = false);
|
|
||||||
}
|
|
||||||
@@ -519,7 +519,7 @@
|
|||||||
"SettingsCustomTheme_Title": "Custom Theme",
|
"SettingsCustomTheme_Title": "Custom Theme",
|
||||||
"SettingsDeleteAccount_Description": "Delete all e-mails and credentials associated with this account.",
|
"SettingsDeleteAccount_Description": "Delete all e-mails and credentials associated with this account.",
|
||||||
"SettingsDeleteAccount_Title": "Delete this account",
|
"SettingsDeleteAccount_Title": "Delete this account",
|
||||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
"SettingsDeleteProtection_Description": "Should Wino ask you for comfirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||||
"SettingsDiagnostics_Description": "For developers",
|
"SettingsDiagnostics_Description": "For developers",
|
||||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||||
@@ -623,11 +623,6 @@
|
|||||||
"SettingsShowPreviewText_Title": "Show Preview Text",
|
"SettingsShowPreviewText_Title": "Show Preview Text",
|
||||||
"SettingsShowSenderPictures_Description": "Hide/show the thumbnail sender pictures.",
|
"SettingsShowSenderPictures_Description": "Hide/show the thumbnail sender pictures.",
|
||||||
"SettingsShowSenderPictures_Title": "Show Sender Avatars",
|
"SettingsShowSenderPictures_Title": "Show Sender Avatars",
|
||||||
"SettingsEnableGravatarAvatars_Title": "Gravatar",
|
|
||||||
"SettingsEnableGravatarAvatars_Description": "Use gravatar (if available) as sender picture",
|
|
||||||
"SettingsEnableFavicons_Title": "Domain icons (Favicons)",
|
|
||||||
"SettingsEnableFavicons_Description": "Use domain favicons (if available) as sender picture",
|
|
||||||
"SettingsMailList_ClearAvatarsCache_Button": "Clear cached avatars",
|
|
||||||
"SettingsSignature_AddCustomSignature_Button": "Add signature",
|
"SettingsSignature_AddCustomSignature_Button": "Add signature",
|
||||||
"SettingsSignature_AddCustomSignature_Title": "Add custom signature",
|
"SettingsSignature_AddCustomSignature_Title": "Add custom signature",
|
||||||
"SettingsSignature_DeleteSignature_Title": "Delete signature",
|
"SettingsSignature_DeleteSignature_Title": "Delete signature",
|
||||||
@@ -649,8 +644,6 @@
|
|||||||
"SettingsThreads_Title": "Conversation Threading",
|
"SettingsThreads_Title": "Conversation Threading",
|
||||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
|
||||||
"SettingsMailRendering_ActionLabels_Description": "Show action labels.",
|
|
||||||
"SignatureDeleteDialog_Message": "Are you sure you want to delete \"{0}\" signature?",
|
"SignatureDeleteDialog_Message": "Are you sure you want to delete \"{0}\" signature?",
|
||||||
"SignatureDeleteDialog_Title": "Delete signature",
|
"SignatureDeleteDialog_Title": "Delete signature",
|
||||||
"SignatureEditorDialog_SignatureName_Placeholder": "Name your signature",
|
"SignatureEditorDialog_SignatureName_Placeholder": "Name your signature",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<Setter Property="ChildrenTransitions">
|
<Setter Property="ChildrenTransitions">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<TransitionCollection>
|
<TransitionCollection>
|
||||||
<EntranceThemeTransition IsStaggeringEnabled="False" />
|
<EntranceThemeTransition IsStaggeringEnabled="True" />
|
||||||
</TransitionCollection>
|
</TransitionCollection>
|
||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
</Setter>
|
</Setter>
|
||||||
@@ -96,19 +96,6 @@
|
|||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Spacing between cards -->
|
|
||||||
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
|
|
||||||
|
|
||||||
<!-- Style (inc. the correct spacing) of a section header -->
|
|
||||||
<Style
|
|
||||||
x:Key="SettingsSectionHeaderTextBlockStyle"
|
|
||||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
|
||||||
TargetType="TextBlock">
|
|
||||||
<Style.Setters>
|
|
||||||
<Setter Property="Margin" Value="1,30,0,6" />
|
|
||||||
</Style.Setters>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public static class CoreUWPContainerSetup
|
|||||||
services.AddSingleton<IPreferencesService, PreferencesService>();
|
services.AddSingleton<IPreferencesService, PreferencesService>();
|
||||||
services.AddSingleton<IThemeService, ThemeService>();
|
services.AddSingleton<IThemeService, ThemeService>();
|
||||||
services.AddSingleton<IStatePersistanceService, StatePersistenceService>();
|
services.AddSingleton<IStatePersistanceService, StatePersistenceService>();
|
||||||
services.AddSingleton<IThumbnailService, ThumbnailService>();
|
|
||||||
services.AddSingleton<IDialogServiceBase, DialogServiceBase>();
|
services.AddSingleton<IDialogServiceBase, DialogServiceBase>();
|
||||||
services.AddTransient<IConfigurationService, ConfigurationService>();
|
services.AddTransient<IConfigurationService, ConfigurationService>();
|
||||||
services.AddTransient<IFileService, FileService>();
|
services.AddTransient<IFileService, FileService>();
|
||||||
|
|||||||
@@ -23,6 +23,6 @@
|
|||||||
|
|
||||||
<StackPanel Spacing="12">
|
<StackPanel Spacing="12">
|
||||||
<TextBlock x:Name="DialogDescription" TextWrapping="Wrap" />
|
<TextBlock x:Name="DialogDescription" TextWrapping="Wrap" />
|
||||||
<TextBox x:Name="FolderTextBox" Text="{x:Bind CurrentInput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
<TextBox x:Name="FolderTextBox" Text="{x:Bind CurrentInput, Mode=TwoWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ContentDialog>
|
</ContentDialog>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public class NativeAppService : INativeAppService
|
|||||||
return _mimeMessagesFolder;
|
return _mimeMessagesFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<string> GetEditorBundlePathAsync()
|
public async Task<string> GetEditorBundlePathAsync()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_editorBundlePath))
|
if (string.IsNullOrEmpty(_editorBundlePath))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using Wino.Core.Domain.Entities.Mail;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
using System.IO;
|
using Wino.Core.Services;
|
||||||
|
|
||||||
namespace Wino.Core.UWP.Services;
|
namespace Wino.Core.UWP.Services;
|
||||||
|
|
||||||
@@ -23,19 +23,16 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IFolderService _folderService;
|
private readonly IFolderService _folderService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly IThumbnailService _thumbnailService;
|
|
||||||
|
|
||||||
public NotificationBuilder(IUnderlyingThemeService underlyingThemeService,
|
public NotificationBuilder(IUnderlyingThemeService underlyingThemeService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IFolderService folderService,
|
IFolderService folderService,
|
||||||
IMailService mailService,
|
IMailService mailService)
|
||||||
IThumbnailService thumbnailService)
|
|
||||||
{
|
{
|
||||||
_underlyingThemeService = underlyingThemeService;
|
_underlyingThemeService = underlyingThemeService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_folderService = folderService;
|
_folderService = folderService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_thumbnailService = thumbnailService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateNotificationsAsync(Guid inboxFolderId, IEnumerable<IMailItem> downloadedMailItems)
|
public async Task CreateNotificationsAsync(Guid inboxFolderId, IEnumerable<IMailItem> downloadedMailItems)
|
||||||
@@ -86,16 +83,24 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
var builder = new ToastContentBuilder();
|
var builder = new ToastContentBuilder();
|
||||||
builder.SetToastScenario(ToastScenario.Default);
|
builder.SetToastScenario(ToastScenario.Default);
|
||||||
|
|
||||||
var avatarThumbnail = await _thumbnailService.GetThumbnailAsync(mailItem.FromAddress, awaitLoad: true);
|
var host = ThumbnailService.GetHost(mailItem.FromAddress);
|
||||||
if (!string.IsNullOrEmpty(avatarThumbnail))
|
|
||||||
|
var knownTuple = ThumbnailService.CheckIsKnown(host);
|
||||||
|
|
||||||
|
bool isKnown = knownTuple.Item1;
|
||||||
|
host = knownTuple.Item2;
|
||||||
|
|
||||||
|
if (isKnown)
|
||||||
|
builder.AddAppLogoOverride(new System.Uri(ThumbnailService.GetKnownHostImage(host)), hintCrop: ToastGenericAppLogoCrop.Default);
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var tempFile = await Windows.Storage.ApplicationData.Current.TemporaryFolder.CreateFileAsync($"{Guid.NewGuid()}.png", Windows.Storage.CreationCollisionOption.ReplaceExisting);
|
// TODO: https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=toolkit
|
||||||
await using (var stream = await tempFile.OpenStreamForWriteAsync())
|
// Follow official guides for icons/theme.
|
||||||
{
|
|
||||||
var bytes = Convert.FromBase64String(avatarThumbnail);
|
bool isOSDarkTheme = _underlyingThemeService.IsUnderlyingThemeDark();
|
||||||
await stream.WriteAsync(bytes);
|
string profileLogoName = isOSDarkTheme ? "profile-dark.png" : "profile-light.png";
|
||||||
}
|
|
||||||
builder.AddAppLogoOverride(new Uri($"ms-appdata:///temp/{tempFile.Name}"), hintCrop: ToastGenericAppLogoCrop.Default);
|
builder.AddAppLogoOverride(new System.Uri($"ms-appx:///Assets/NotificationIcons/{profileLogoName}"), hintCrop: ToastGenericAppLogoCrop.Circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override system notification timetamp with received date of the mail.
|
// Override system notification timetamp with received date of the mail.
|
||||||
@@ -109,9 +114,9 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
|
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
|
||||||
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
|
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
|
||||||
|
|
||||||
builder.AddButton(GetMarkAsReadButton(mailItem.UniqueId));
|
builder.AddButton(GetMarkedAsRead(mailItem.UniqueId));
|
||||||
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
||||||
builder.AddButton(GetArchiveButton(mailItem.UniqueId));
|
builder.AddButton(GetDismissButton());
|
||||||
builder.AddAudio(new ToastAudio()
|
builder.AddAudio(new ToastAudio()
|
||||||
{
|
{
|
||||||
Src = new Uri("ms-winsoundevent:Notification.Mail")
|
Src = new Uri("ms-winsoundevent:Notification.Mail")
|
||||||
@@ -134,14 +139,6 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
.SetDismissActivation()
|
.SetDismissActivation()
|
||||||
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/dismiss.png"));
|
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/dismiss.png"));
|
||||||
|
|
||||||
private static ToastButton GetArchiveButton(Guid mailUniqueId)
|
|
||||||
=> new ToastButton()
|
|
||||||
.SetContent(Translator.MailOperation_Archive)
|
|
||||||
.SetImageUri(new Uri("ms-appx:///Assets/NotificationIcons/archive.png"))
|
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.Archive)
|
|
||||||
.SetBackgroundActivation();
|
|
||||||
|
|
||||||
private ToastButton GetDeleteButton(Guid mailUniqueId)
|
private ToastButton GetDeleteButton(Guid mailUniqueId)
|
||||||
=> new ToastButton()
|
=> new ToastButton()
|
||||||
.SetContent(Translator.MailOperation_Delete)
|
.SetContent(Translator.MailOperation_Delete)
|
||||||
@@ -150,7 +147,7 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
|
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
|
||||||
.SetBackgroundActivation();
|
.SetBackgroundActivation();
|
||||||
|
|
||||||
private static ToastButton GetMarkAsReadButton(Guid mailUniqueId)
|
private ToastButton GetMarkedAsRead(Guid mailUniqueId)
|
||||||
=> new ToastButton()
|
=> new ToastButton()
|
||||||
.SetContent(Translator.MailOperation_MarkAsRead)
|
.SetContent(Translator.MailOperation_MarkAsRead)
|
||||||
.SetImageUri(new System.Uri("ms-appx:///Assets/NotificationIcons/markread.png"))
|
.SetImageUri(new System.Uri("ms-appx:///Assets/NotificationIcons/markread.png"))
|
||||||
|
|||||||
@@ -13,12 +13,17 @@ using Wino.Services;
|
|||||||
|
|
||||||
namespace Wino.Core.UWP.Services;
|
namespace Wino.Core.UWP.Services;
|
||||||
|
|
||||||
public class PreferencesService(IConfigurationService configurationService) : ObservableObject, IPreferencesService
|
public class PreferencesService : ObservableObject, IPreferencesService
|
||||||
{
|
{
|
||||||
private readonly IConfigurationService _configurationService = configurationService;
|
private readonly IConfigurationService _configurationService;
|
||||||
|
|
||||||
public event EventHandler<string> PreferenceChanged;
|
public event EventHandler<string> PreferenceChanged;
|
||||||
|
|
||||||
|
public PreferencesService(IConfigurationService configurationService)
|
||||||
|
{
|
||||||
|
_configurationService = configurationService;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
|
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPropertyChanged(e);
|
base.OnPropertyChanged(e);
|
||||||
@@ -74,12 +79,6 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
|
|||||||
set => SetPropertyAndSave(nameof(IsMailListActionBarEnabled), value);
|
set => SetPropertyAndSave(nameof(IsMailListActionBarEnabled), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsShowActionLabelsEnabled
|
|
||||||
{
|
|
||||||
get => _configurationService.Get(nameof(IsShowActionLabelsEnabled), true);
|
|
||||||
set => SetPropertyAndSave(nameof(IsShowActionLabelsEnabled), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsShowSenderPicturesEnabled
|
public bool IsShowSenderPicturesEnabled
|
||||||
{
|
{
|
||||||
get => _configurationService.Get(nameof(IsShowSenderPicturesEnabled), true);
|
get => _configurationService.Get(nameof(IsShowSenderPicturesEnabled), true);
|
||||||
@@ -176,18 +175,6 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
|
|||||||
set => SetPropertyAndSave(nameof(IsMailkitProtocolLoggerEnabled), value);
|
set => SetPropertyAndSave(nameof(IsMailkitProtocolLoggerEnabled), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGravatarEnabled
|
|
||||||
{
|
|
||||||
get => _configurationService.Get(nameof(IsGravatarEnabled), true);
|
|
||||||
set => SetPropertyAndSave(nameof(IsGravatarEnabled), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsFaviconEnabled
|
|
||||||
{
|
|
||||||
get => _configurationService.Get(nameof(IsFaviconEnabled), true);
|
|
||||||
set => SetPropertyAndSave(nameof(IsFaviconEnabled), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Guid? StartupEntityId
|
public Guid? StartupEntityId
|
||||||
{
|
{
|
||||||
get => _configurationService.Get<Guid?>(nameof(StartupEntityId), null);
|
get => _configurationService.Get<Guid?>(nameof(StartupEntityId), null);
|
||||||
|
|||||||
@@ -1,198 +1,63 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Mail;
|
using System.Net.Mail;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
|
||||||
using Gravatar;
|
|
||||||
using Windows.Networking.Connectivity;
|
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
using Wino.Messaging.UI;
|
|
||||||
using Wino.Services;
|
|
||||||
|
|
||||||
namespace Wino.Core.UWP.Services;
|
namespace Wino.Core.UWP.Services;
|
||||||
|
|
||||||
public class ThumbnailService(IPreferencesService preferencesService, IDatabaseService databaseService) : IThumbnailService
|
public static class ThumbnailService
|
||||||
{
|
{
|
||||||
private readonly IPreferencesService _preferencesService = preferencesService;
|
private static string[] knownCompanies = new string[]
|
||||||
private readonly IDatabaseService _databaseService = databaseService;
|
|
||||||
private static readonly HttpClient _httpClient = new();
|
|
||||||
private bool _isInitialized = false;
|
|
||||||
|
|
||||||
private ConcurrentDictionary<string, (string graviton, string favicon)> _cache;
|
|
||||||
private readonly ConcurrentDictionary<string, Task> _requests = [];
|
|
||||||
|
|
||||||
private static readonly List<string> _excludedFaviconDomains = [
|
|
||||||
"gmail.com",
|
|
||||||
"outlook.com",
|
|
||||||
"hotmail.com",
|
|
||||||
"live.com",
|
|
||||||
"yahoo.com",
|
|
||||||
"icloud.com",
|
|
||||||
"aol.com",
|
|
||||||
"protonmail.com",
|
|
||||||
"zoho.com",
|
|
||||||
"mail.com",
|
|
||||||
"gmx.com",
|
|
||||||
"yandex.com",
|
|
||||||
"yandex.ru",
|
|
||||||
"tutanota.com",
|
|
||||||
"mail.ru",
|
|
||||||
"rediffmail.com"
|
|
||||||
];
|
|
||||||
|
|
||||||
public async ValueTask<string> GetThumbnailAsync(string email, bool awaitLoad = false)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(email))
|
"microsoft.com", "apple.com", "google.com", "steampowered.com", "airbnb.com", "youtube.com", "uber.com"
|
||||||
return null;
|
};
|
||||||
|
|
||||||
if (!_preferencesService.IsShowSenderPicturesEnabled)
|
public static bool IsKnown(string mailHost) => !string.IsNullOrEmpty(mailHost) && knownCompanies.Contains(mailHost);
|
||||||
return null;
|
|
||||||
|
|
||||||
if (!_isInitialized)
|
public static string GetHost(string address)
|
||||||
{
|
|
||||||
var thumbnailsList = await _databaseService.Connection.Table<Thumbnail>().ToListAsync();
|
|
||||||
|
|
||||||
_cache = new ConcurrentDictionary<string, (string graviton, string favicon)>(
|
|
||||||
thumbnailsList.ToDictionary(x => x.Domain, x => (x.Gravatar, x.Favicon)));
|
|
||||||
_isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sanitizedEmail = email.Trim().ToLowerInvariant();
|
|
||||||
|
|
||||||
var (gravatar, favicon) = await GetThumbnailInternal(sanitizedEmail, awaitLoad);
|
|
||||||
|
|
||||||
if (_preferencesService.IsGravatarEnabled && !string.IsNullOrEmpty(gravatar))
|
|
||||||
{
|
|
||||||
return gravatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_preferencesService.IsFaviconEnabled && !string.IsNullOrEmpty(favicon))
|
|
||||||
{
|
|
||||||
return favicon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ClearCache()
|
|
||||||
{
|
{
|
||||||
_cache?.Clear();
|
if (string.IsNullOrEmpty(address))
|
||||||
_requests.Clear();
|
return string.Empty;
|
||||||
await _databaseService.Connection.DeleteAllAsync<Thumbnail>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask<(string gravatar, string favicon)> GetThumbnailInternal(string email, bool awaitLoad)
|
if (address.Contains('@'))
|
||||||
{
|
|
||||||
if (_cache.TryGetValue(email, out var cached))
|
|
||||||
return cached;
|
|
||||||
|
|
||||||
// No network available, skip fetching Gravatar
|
|
||||||
// Do not cache it, since network can be available later
|
|
||||||
bool isInternetAvailable = GetIsInternetAvailable();
|
|
||||||
|
|
||||||
if (!isInternetAvailable)
|
|
||||||
return default;
|
|
||||||
|
|
||||||
if (!_requests.TryGetValue(email, out var request))
|
|
||||||
{
|
{
|
||||||
request = Task.Run(() => RequestNewThumbnail(email));
|
var splitted = address.Split('@');
|
||||||
_requests[email] = request;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (awaitLoad)
|
if (splitted.Length >= 2 && !string.IsNullOrEmpty(splitted[1]))
|
||||||
{
|
|
||||||
await request;
|
|
||||||
_cache.TryGetValue(email, out cached);
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
|
|
||||||
static bool GetIsInternetAvailable()
|
|
||||||
{
|
|
||||||
var connection = NetworkInformation.GetInternetConnectionProfile();
|
|
||||||
return connection != null && connection.GetNetworkConnectivityLevel() == NetworkConnectivityLevel.InternetAccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RequestNewThumbnail(string email)
|
|
||||||
{
|
|
||||||
var gravatarBase64 = await GetGravatarBase64(email);
|
|
||||||
var faviconBase64 = await GetFaviconBase64(email);
|
|
||||||
|
|
||||||
await _databaseService.Connection.InsertOrReplaceAsync(new Thumbnail
|
|
||||||
{
|
|
||||||
Domain = email,
|
|
||||||
Gravatar = gravatarBase64,
|
|
||||||
Favicon = faviconBase64,
|
|
||||||
LastUpdated = DateTime.UtcNow
|
|
||||||
});
|
|
||||||
_ = _cache.TryAdd(email, (gravatarBase64, faviconBase64));
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new ThumbnailAdded(email));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<string> GetGravatarBase64(string email)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var gravatarUrl = GravatarHelper.GetAvatarUrl(
|
|
||||||
email,
|
|
||||||
size: 128,
|
|
||||||
defaultValue: GravatarAvatarDefault.Blank,
|
|
||||||
withFileExtension: false).ToString().Replace("d=blank", "d=404");
|
|
||||||
var response = await _httpClient.GetAsync(gravatarUrl);
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
{
|
||||||
var bytes = response.Content.ReadAsByteArrayAsync().Result;
|
try
|
||||||
return Convert.ToBase64String(bytes);
|
{
|
||||||
|
return new MailAddress(address).Host;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// TODO: Exceptions are ignored for now.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<string> GetFaviconBase64(string email)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var host = GetHost(email);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(host))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Do not fetch favicon for specific default domains of major platforms
|
|
||||||
if (_excludedFaviconDomains.Contains(host, StringComparer.OrdinalIgnoreCase))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var primaryDomain = string.Join('.', host.Split('.')[^2..]);
|
|
||||||
|
|
||||||
var googleFaviconUrl = $"https://www.google.com/s2/favicons?sz=128&domain_url={primaryDomain}";
|
|
||||||
var response = await _httpClient.GetAsync(googleFaviconUrl);
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var bytes = response.Content.ReadAsByteArrayAsync().Result;
|
|
||||||
return Convert.ToBase64String(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetHost(string email)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(email) && email.Contains('@'))
|
|
||||||
{
|
|
||||||
var split = email.Split('@');
|
|
||||||
if (split.Length >= 2 && !string.IsNullOrEmpty(split[1]))
|
|
||||||
{
|
|
||||||
try { return new MailAddress(email).Host; } catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Tuple<bool, string> CheckIsKnown(string host)
|
||||||
|
{
|
||||||
|
// Check known hosts.
|
||||||
|
// Apply company logo if available.
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var last = host.Split('.');
|
||||||
|
|
||||||
|
if (last.Length > 2)
|
||||||
|
host = $"{last[last.Length - 2]}.{last[last.Length - 1]}";
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return new Tuple<bool, string>(false, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Tuple<bool, string>(IsKnown(host), host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetKnownHostImage(string host)
|
||||||
|
=> $"ms-appx:///Assets/Thumbnails/{host}.png";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,6 @@
|
|||||||
<Content Include="BackgroundImages\Snowflake.jpg" />
|
<Content Include="BackgroundImages\Snowflake.jpg" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="gravatar-dotnet" />
|
|
||||||
<PackageReference Include="Microsoft.Identity.Client" />
|
<PackageReference Include="Microsoft.Identity.Client" />
|
||||||
<PackageReference Include="Microsoft.UI.Xaml" />
|
<PackageReference Include="Microsoft.UI.Xaml" />
|
||||||
<PackageReference Include="CommunityToolkit.Common" />
|
<PackageReference Include="CommunityToolkit.Common" />
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ public abstract class WinoApplication : Application, IRecipient<LanguageChanged>
|
|||||||
protected IWinoServerConnectionManager<AppServiceConnection> AppServiceConnectionManager { get; }
|
protected IWinoServerConnectionManager<AppServiceConnection> AppServiceConnectionManager { get; }
|
||||||
public IThemeService ThemeService { get; }
|
public IThemeService ThemeService { get; }
|
||||||
public IUnderlyingThemeService UnderlyingThemeService { get; }
|
public IUnderlyingThemeService UnderlyingThemeService { get; }
|
||||||
public IThumbnailService ThumbnailService { get; }
|
|
||||||
protected IDatabaseService DatabaseService { get; }
|
protected IDatabaseService DatabaseService { get; }
|
||||||
protected ITranslationService TranslationService { get; }
|
protected ITranslationService TranslationService { get; }
|
||||||
|
|
||||||
@@ -65,7 +64,6 @@ public abstract class WinoApplication : Application, IRecipient<LanguageChanged>
|
|||||||
DatabaseService = Services.GetService<IDatabaseService>();
|
DatabaseService = Services.GetService<IDatabaseService>();
|
||||||
TranslationService = Services.GetService<ITranslationService>();
|
TranslationService = Services.GetService<ITranslationService>();
|
||||||
UnderlyingThemeService = Services.GetService<IUnderlyingThemeService>();
|
UnderlyingThemeService = Services.GetService<IUnderlyingThemeService>();
|
||||||
ThumbnailService = Services.GetService<IThumbnailService>();
|
|
||||||
|
|
||||||
// Make sure the paths are setup on app start.
|
// Make sure the paths are setup on app start.
|
||||||
AppConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
|
AppConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Web;
|
|||||||
using Google.Apis.Calendar.v3.Data;
|
using Google.Apis.Calendar.v3.Data;
|
||||||
using Google.Apis.Gmail.v1.Data;
|
using Google.Apis.Gmail.v1.Data;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
@@ -178,11 +179,6 @@ public static class GoogleIntegratorExtensions
|
|||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
TimeZone = calendarListEntry.TimeZone,
|
TimeZone = calendarListEntry.TimeZone,
|
||||||
IsPrimary = calendarListEntry.Primary.GetValueOrDefault(),
|
IsPrimary = calendarListEntry.Primary.GetValueOrDefault(),
|
||||||
Description = calendarListEntry.Description,
|
|
||||||
AccessRole = calendarListEntry.AccessRole,
|
|
||||||
CreatedDate = DateTime.UtcNow,
|
|
||||||
LastSyncTime = DateTime.UtcNow,
|
|
||||||
Location = calendarListEntry.Location,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bg color must present. Generate one if doesnt exists.
|
// Bg color must present. Generate one if doesnt exists.
|
||||||
@@ -194,121 +190,42 @@ public static class GoogleIntegratorExtensions
|
|||||||
return calendar;
|
return calendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CalendarItem MapGoogleEventToCalendarEvent(this Event googleEvent, AccountCalendar calendar)
|
public static DateTimeOffset GetEventDateTimeOffset(EventDateTime calendarEvent)
|
||||||
{
|
{
|
||||||
var calendarEvent = new CalendarItem
|
if (calendarEvent != null)
|
||||||
{
|
{
|
||||||
RemoteEventId = googleEvent.Id,
|
if (calendarEvent.DateTimeDateTimeOffset != null)
|
||||||
CalendarId = calendar.Id, // Use internal Guid
|
|
||||||
Title = googleEvent.Summary ?? string.Empty,
|
|
||||||
Description = googleEvent.Description,
|
|
||||||
Location = googleEvent.Location,
|
|
||||||
Status = googleEvent.Status,
|
|
||||||
RecurringEventId = googleEvent.RecurringEventId
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle start and end times
|
|
||||||
if (googleEvent.Start != null)
|
|
||||||
{
|
|
||||||
if (googleEvent.Start.Date != null)
|
|
||||||
{
|
{
|
||||||
calendarEvent.IsAllDay = true;
|
return calendarEvent.DateTimeDateTimeOffset.Value;
|
||||||
calendarEvent.StartDateTime = DateTime.Parse(googleEvent.Start.Date);
|
|
||||||
}
|
}
|
||||||
else if (googleEvent.Start.DateTimeDateTimeOffset.HasValue)
|
else if (calendarEvent.Date != null)
|
||||||
{
|
{
|
||||||
calendarEvent.IsAllDay = false;
|
if (DateTime.TryParse(calendarEvent.Date, out DateTime eventDateTime))
|
||||||
calendarEvent.StartDateTime = googleEvent.Start.DateTimeDateTimeOffset.Value.DateTime;
|
{
|
||||||
calendarEvent.TimeZone = googleEvent.Start.TimeZone;
|
// Date-only events are treated as UTC midnight
|
||||||
|
return new DateTimeOffset(eventDateTime, TimeSpan.Zero);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Invalid date format in Google Calendar event date.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (googleEvent.End != null)
|
throw new Exception("Invalid date format in Google Calendar event date.");
|
||||||
{
|
|
||||||
if (googleEvent.End.Date != null)
|
|
||||||
{
|
|
||||||
calendarEvent.EndDateTime = DateTime.Parse(googleEvent.End.Date);
|
|
||||||
}
|
|
||||||
else if (googleEvent.End.DateTimeDateTimeOffset.HasValue)
|
|
||||||
{
|
|
||||||
calendarEvent.EndDateTime = googleEvent.End.DateTimeDateTimeOffset.Value.DateTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle recurrence rules
|
|
||||||
if (googleEvent.Recurrence != null && googleEvent.Recurrence.Count > 0)
|
|
||||||
{
|
|
||||||
calendarEvent.RecurrenceRules = string.Join(";", googleEvent.Recurrence);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle organizer
|
|
||||||
if (googleEvent.Organizer != null)
|
|
||||||
{
|
|
||||||
calendarEvent.OrganizerDisplayName = googleEvent.Organizer.DisplayName;
|
|
||||||
calendarEvent.OrganizerEmail = googleEvent.Organizer.Email;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle timestamps
|
|
||||||
if (googleEvent.CreatedDateTimeOffset.HasValue)
|
|
||||||
{
|
|
||||||
calendarEvent.CreatedDate = googleEvent.CreatedDateTimeOffset.Value.DateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (googleEvent.UpdatedDateTimeOffset.HasValue)
|
|
||||||
{
|
|
||||||
calendarEvent.LastModified = googleEvent.UpdatedDateTimeOffset.Value.DateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle original start time for recurring event instances
|
|
||||||
if (googleEvent.OriginalStartTime != null)
|
|
||||||
{
|
|
||||||
if (googleEvent.OriginalStartTime.Date != null)
|
|
||||||
{
|
|
||||||
calendarEvent.OriginalStartTime = googleEvent.OriginalStartTime.Date;
|
|
||||||
}
|
|
||||||
else if (googleEvent.OriginalStartTime.DateTimeDateTimeOffset.HasValue)
|
|
||||||
{
|
|
||||||
calendarEvent.OriginalStartTime = googleEvent.OriginalStartTime.DateTimeDateTimeOffset.Value.ToString("O");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Automatically determine the calendar item type based on event properties
|
|
||||||
calendarEvent.DetermineItemType();
|
|
||||||
|
|
||||||
return calendarEvent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a Google Calendar API response status string to AttendeeResponseStatus enum
|
/// RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="googleStatus">The status string from Google Calendar API</param>
|
/// <returns>___ separated lines.</returns>
|
||||||
/// <returns>Corresponding AttendeeResponseStatus enum value</returns>
|
public static string GetRecurrenceString(this Event calendarEvent)
|
||||||
public static AttendeeResponseStatus FromGoogleStatus(string? googleStatus)
|
|
||||||
{
|
{
|
||||||
return googleStatus?.ToLowerInvariant() switch
|
if (calendarEvent == null || calendarEvent.Recurrence == null || !calendarEvent.Recurrence.Any())
|
||||||
{
|
{
|
||||||
"accepted" => AttendeeResponseStatus.Accepted,
|
return null;
|
||||||
"declined" => AttendeeResponseStatus.Declined,
|
}
|
||||||
"tentative" => AttendeeResponseStatus.Tentative,
|
|
||||||
"needsaction" => AttendeeResponseStatus.NeedsAction,
|
|
||||||
_ => AttendeeResponseStatus.NeedsAction
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
return string.Join(Constants.CalendarEventRecurrenceRuleSeperator, calendarEvent.Recurrence);
|
||||||
/// Converts an AttendeeResponseStatus enum to Google Calendar API response status string
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="status">The AttendeeResponseStatus enum value</param>
|
|
||||||
/// <returns>Corresponding Google Calendar API status string</returns>
|
|
||||||
public static string ToGoogleStatus(AttendeeResponseStatus status)
|
|
||||||
{
|
|
||||||
return status switch
|
|
||||||
{
|
|
||||||
AttendeeResponseStatus.Accepted => "accepted",
|
|
||||||
AttendeeResponseStatus.Declined => "declined",
|
|
||||||
AttendeeResponseStatus.Tentative => "tentative",
|
|
||||||
AttendeeResponseStatus.NeedsAction => "needsAction",
|
|
||||||
_ => "needsAction"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.Graph.Models;
|
using Microsoft.Graph.Models;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
@@ -126,26 +127,125 @@ public static class OutlookIntegratorExtensions
|
|||||||
return calendar;
|
return calendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetRfc5545DayOfWeek(DayOfWeekObject dayOfWeek)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts Outlook response status to our enum
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outlookResponse">Outlook response type</param>
|
|
||||||
/// <returns>AttendeeResponseStatus enum value</returns>
|
|
||||||
public static AttendeeResponseStatus ConvertOutlookResponseStatus(this Microsoft.Graph.Models.ResponseType? outlookResponse)
|
|
||||||
{
|
{
|
||||||
return outlookResponse switch
|
return dayOfWeek switch
|
||||||
{
|
{
|
||||||
Microsoft.Graph.Models.ResponseType.Accepted => AttendeeResponseStatus.Accepted,
|
DayOfWeekObject.Monday => "MO",
|
||||||
Microsoft.Graph.Models.ResponseType.Declined => AttendeeResponseStatus.Declined,
|
DayOfWeekObject.Tuesday => "TU",
|
||||||
Microsoft.Graph.Models.ResponseType.TentativelyAccepted => AttendeeResponseStatus.Tentative,
|
DayOfWeekObject.Wednesday => "WE",
|
||||||
Microsoft.Graph.Models.ResponseType.None => AttendeeResponseStatus.NeedsAction,
|
DayOfWeekObject.Thursday => "TH",
|
||||||
Microsoft.Graph.Models.ResponseType.NotResponded => AttendeeResponseStatus.NeedsAction,
|
DayOfWeekObject.Friday => "FR",
|
||||||
_ => AttendeeResponseStatus.NeedsAction
|
DayOfWeekObject.Saturday => "SA",
|
||||||
|
DayOfWeekObject.Sunday => "SU",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(dayOfWeek), dayOfWeek, null)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToRfc5545RecurrenceString(this PatternedRecurrence recurrence)
|
||||||
|
{
|
||||||
|
if (recurrence == null || recurrence.Pattern == null)
|
||||||
|
throw new ArgumentNullException(nameof(recurrence), "PatternedRecurrence or its Pattern cannot be null.");
|
||||||
|
|
||||||
|
var ruleBuilder = new StringBuilder("RRULE:");
|
||||||
|
var pattern = recurrence.Pattern;
|
||||||
|
|
||||||
|
// Frequency
|
||||||
|
switch (pattern.Type)
|
||||||
|
{
|
||||||
|
case RecurrencePatternType.Daily:
|
||||||
|
ruleBuilder.Append("FREQ=DAILY;");
|
||||||
|
break;
|
||||||
|
case RecurrencePatternType.Weekly:
|
||||||
|
ruleBuilder.Append("FREQ=WEEKLY;");
|
||||||
|
break;
|
||||||
|
case RecurrencePatternType.AbsoluteMonthly:
|
||||||
|
ruleBuilder.Append("FREQ=MONTHLY;");
|
||||||
|
break;
|
||||||
|
case RecurrencePatternType.AbsoluteYearly:
|
||||||
|
ruleBuilder.Append("FREQ=YEARLY;");
|
||||||
|
break;
|
||||||
|
case RecurrencePatternType.RelativeMonthly:
|
||||||
|
ruleBuilder.Append("FREQ=MONTHLY;");
|
||||||
|
break;
|
||||||
|
case RecurrencePatternType.RelativeYearly:
|
||||||
|
ruleBuilder.Append("FREQ=YEARLY;");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException($"Unsupported recurrence pattern type: {pattern.Type}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interval
|
||||||
|
if (pattern.Interval > 0)
|
||||||
|
ruleBuilder.Append($"INTERVAL={pattern.Interval};");
|
||||||
|
|
||||||
|
// Days of Week
|
||||||
|
if (pattern.DaysOfWeek?.Any() == true)
|
||||||
|
{
|
||||||
|
var days = string.Join(",", pattern.DaysOfWeek.Select(day => day.ToString().ToUpperInvariant().Substring(0, 2)));
|
||||||
|
ruleBuilder.Append($"BYDAY={days};");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Day of Month (BYMONTHDAY)
|
||||||
|
if (pattern.Type == RecurrencePatternType.AbsoluteMonthly || pattern.Type == RecurrencePatternType.AbsoluteYearly)
|
||||||
|
{
|
||||||
|
if (pattern.DayOfMonth <= 0)
|
||||||
|
throw new ArgumentException("DayOfMonth must be greater than 0 for absoluteMonthly or absoluteYearly patterns.");
|
||||||
|
|
||||||
|
ruleBuilder.Append($"BYMONTHDAY={pattern.DayOfMonth};");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Month (BYMONTH)
|
||||||
|
if (pattern.Type == RecurrencePatternType.AbsoluteYearly || pattern.Type == RecurrencePatternType.RelativeYearly)
|
||||||
|
{
|
||||||
|
if (pattern.Month <= 0)
|
||||||
|
throw new ArgumentException("Month must be greater than 0 for absoluteYearly or relativeYearly patterns.");
|
||||||
|
|
||||||
|
ruleBuilder.Append($"BYMONTH={pattern.Month};");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count or Until
|
||||||
|
if (recurrence.Range != null)
|
||||||
|
{
|
||||||
|
if (recurrence.Range.Type == RecurrenceRangeType.EndDate && recurrence.Range.EndDate != null)
|
||||||
|
{
|
||||||
|
ruleBuilder.Append($"UNTIL={recurrence.Range.EndDate.Value:yyyyMMddTHHmmssZ};");
|
||||||
|
}
|
||||||
|
else if (recurrence.Range.Type == RecurrenceRangeType.Numbered && recurrence.Range.NumberOfOccurrences.HasValue)
|
||||||
|
{
|
||||||
|
ruleBuilder.Append($"COUNT={recurrence.Range.NumberOfOccurrences.Value};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing semicolon
|
||||||
|
return ruleBuilder.ToString().TrimEnd(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DateTimeOffset GetDateTimeOffsetFromDateTimeTimeZone(DateTimeTimeZone dateTimeTimeZone)
|
||||||
|
{
|
||||||
|
if (dateTimeTimeZone == null || string.IsNullOrEmpty(dateTimeTimeZone.DateTime) || string.IsNullOrEmpty(dateTimeTimeZone.TimeZone))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("DateTimeTimeZone is null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Parse the DateTime string
|
||||||
|
if (DateTime.TryParse(dateTimeTimeZone.DateTime, out DateTime parsedDateTime))
|
||||||
|
{
|
||||||
|
// Get TimeZoneInfo to get the offset
|
||||||
|
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(dateTimeTimeZone.TimeZone);
|
||||||
|
TimeSpan offset = timeZoneInfo.GetUtcOffset(parsedDateTime);
|
||||||
|
return new DateTimeOffset(parsedDateTime, offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new ArgumentException("DateTime string is not in a valid format.");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static AttendeeStatus GetAttendeeStatus(ResponseType? responseType)
|
private static AttendeeStatus GetAttendeeStatus(ResponseType? responseType)
|
||||||
{
|
{
|
||||||
@@ -161,6 +261,24 @@ public static class OutlookIntegratorExtensions
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Wino.Core.Integration.Processors;
|
|||||||
/// <see cref="IGmailChangeProcessor"/>, <see cref="IOutlookChangeProcessor"/> and <see cref="IImapChangeProcessor"/>
|
/// <see cref="IGmailChangeProcessor"/>, <see cref="IOutlookChangeProcessor"/> and <see cref="IImapChangeProcessor"/>
|
||||||
/// None of the synchronizers can directly change anything in the database.
|
/// None of the synchronizers can directly change anything in the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDefaultChangeProcessor : ICalendarServiceEx
|
public interface IDefaultChangeProcessor
|
||||||
{
|
{
|
||||||
Task UpdateAccountAsync(MailAccount account);
|
Task UpdateAccountAsync(MailAccount account);
|
||||||
// Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string deltaSynchronizationIdentifier);
|
// Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string deltaSynchronizationIdentifier);
|
||||||
@@ -113,14 +113,13 @@ public class DefaultChangeProcessor(IDatabaseService databaseService,
|
|||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
ICalendarService calendarService,
|
ICalendarService calendarService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ICalendarServiceEx calendarServiceEx,
|
IMimeFileService mimeFileService) : BaseDatabaseService(databaseService), IDefaultChangeProcessor
|
||||||
IMimeFileService mimeFileService) : BaseDatabaseService(databaseService), IDefaultChangeProcessor, ICalendarServiceEx
|
|
||||||
{
|
{
|
||||||
protected IMailService MailService = mailService;
|
protected IMailService MailService = mailService;
|
||||||
protected ICalendarService CalendarService = calendarService;
|
protected ICalendarService CalendarService = calendarService;
|
||||||
protected IFolderService FolderService = folderService;
|
protected IFolderService FolderService = folderService;
|
||||||
protected IAccountService AccountService = accountService;
|
protected IAccountService AccountService = accountService;
|
||||||
private readonly ICalendarServiceEx _calendarServiceEx = calendarServiceEx;
|
|
||||||
private readonly IMimeFileService _mimeFileService = mimeFileService;
|
private readonly IMimeFileService _mimeFileService = mimeFileService;
|
||||||
|
|
||||||
public Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string synchronizationDeltaIdentifier)
|
public Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string synchronizationDeltaIdentifier)
|
||||||
@@ -209,234 +208,4 @@ public class DefaultChangeProcessor(IDatabaseService databaseService,
|
|||||||
|
|
||||||
public Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId)
|
public Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId)
|
||||||
=> MailService.IsMailExistsAsync(messageId, folderId);
|
=> MailService.IsMailExistsAsync(messageId, folderId);
|
||||||
|
|
||||||
// TODO: Normalize this shit. Not everything needs to be exposed for processor.
|
|
||||||
#region ICalendarServiceEx
|
|
||||||
|
|
||||||
public Task<int> ClearAllCalendarEventAttendeesAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.ClearAllCalendarEventAttendeesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> ClearAllCalendarsAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.ClearAllCalendarsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> ClearAllDataAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.ClearAllDataAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> ClearAllEventsAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.ClearAllEventsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> DeleteCalendarAsync(string remoteCalendarId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.DeleteCalendarAsync(remoteCalendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> DeleteCalendarEventAttendeesForEventAsync(Guid eventId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.DeleteCalendarEventAttendeesForEventAsync(eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> DeleteEventAsync(string remoteEventId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.DeleteEventAsync(remoteEventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarEventAttendee>> GetAllCalendarEventAttendeesAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetAllCalendarEventAttendeesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<AccountCalendar>> GetAllCalendarsAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetAllCalendarsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetAllDayEventsAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetAllDayEventsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetAllEventsAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetAllEventsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetAllEventsIncludingDeletedAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetAllEventsIncludingDeletedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetAllRecurringEventsByTypeAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetAllRecurringEventsByTypeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<AccountCalendar> GetCalendarByRemoteIdAsync(string remoteCalendarId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetCalendarByRemoteIdAsync(remoteCalendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Dictionary<AttendeeResponseStatus, int>> GetCalendarEventAttendeeResponseCountsAsync(Guid eventId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetCalendarEventAttendeeResponseCountsAsync(eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarEventAttendee>> GetCalendarEventAttendeesForEventAsync(Guid eventId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetCalendarEventAttendeesForEventAsync(eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarEventAttendee>> GetCalendarEventAttendeesForEventByRemoteIdAsync(string remoteEventId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetCalendarEventAttendeesForEventByRemoteIdAsync(remoteEventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string> GetCalendarSyncTokenAsync(string calendarId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetCalendarSyncTokenAsync(calendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<CalendarItem> GetEventByRemoteIdAsync(string remoteEventId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventByRemoteIdAsync(remoteEventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetEventsByItemTypeAsync(CalendarItemType itemType)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventsByItemTypeAsync(itemType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetEventsByItemTypesAsync(params CalendarItemType[] itemTypes)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventsByItemTypesAsync(itemTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetEventsByremoteCalendarIdAsync(string remoteCalendarId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventsByremoteCalendarIdAsync(remoteCalendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetEventsForCalendarAsync(Guid calendarId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventsForCalendarAsync(calendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetEventsInDateRangeAsync(DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventsInDateRangeAsync(startDate, endDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetEventsSinceLastSyncAsync(DateTime? lastSyncTime)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventsSinceLastSyncAsync(lastSyncTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Dictionary<CalendarItemType, int>> GetEventStatsByItemTypeAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetEventStatsByItemTypeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetExpandedEventsInDateRangeAsync(DateTime startDate, DateTime endDate)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetExpandedEventsInDateRangeAsync(startDate, endDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetExpandedEventsInDateRangeWithExceptionsAsync(DateTime startDate, DateTime endDate, AccountCalendar calendar)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetExpandedEventsInDateRangeWithExceptionsAsync(startDate, endDate, calendar);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<DateTime?> GetLastSyncTimeAsync(string calendarId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetLastSyncTimeAsync(calendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetMultiDayEventsAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetMultiDayEventsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<CalendarItem>> GetRecurringEventsAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.GetRecurringEventsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> HardDeleteEventAsync(string remoteEventId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.HardDeleteEventAsync(remoteEventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> InsertCalendarAsync(AccountCalendar calendar)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.InsertCalendarAsync(calendar);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> InsertCalendarEventAttendeeAsync(CalendarEventAttendee calendareventattendee)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.InsertCalendarEventAttendeeAsync(calendareventattendee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> InsertEventAsync(CalendarItem calendarItem)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.InsertEventAsync(calendarItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> MarkEventAsDeletedAsync(string remoteEventId, string remoteCalendarId)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.MarkEventAsDeletedAsync(remoteEventId, remoteCalendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> SyncCalendarEventAttendeesForEventAsync(Guid eventId, List<CalendarEventAttendee> calendareventattendees)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.SyncCalendarEventAttendeesForEventAsync(eventId, calendareventattendees);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> UpdateAllEventItemTypesAsync()
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.UpdateAllEventItemTypesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> UpdateCalendarAsync(AccountCalendar calendar)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.UpdateCalendarAsync(calendar);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> UpdateCalendarEventAttendeeAsync(CalendarEventAttendee calendareventattendee)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.UpdateCalendarEventAttendeeAsync(calendareventattendee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> UpdateCalendarSyncTokenAsync(string calendarId, string syncToken)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.UpdateCalendarSyncTokenAsync(calendarId, syncToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> UpdateEventAsync(CalendarItem calendarItem)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.UpdateEventAsync(calendarItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> UpsertCalendarAsync(AccountCalendar calendar)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.UpsertCalendarAsync(calendar);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> UpsertEventAsync(CalendarItem calendarItem)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.UpsertEventAsync(calendarItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<int> SyncAttendeesForEventAsync(Guid eventId, List<CalendarEventAttendee> attendees)
|
|
||||||
{
|
|
||||||
return _calendarServiceEx.SyncAttendeesForEventAsync(eventId, attendees);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Google.Apis.Calendar.v3.Data;
|
using Google.Apis.Calendar.v3.Data;
|
||||||
|
using Serilog;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
|
using Wino.Core.Extensions;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
|
using CalendarEventAttendee = Wino.Core.Domain.Entities.Calendar.CalendarEventAttendee;
|
||||||
|
using CalendarItem = Wino.Core.Domain.Entities.Calendar.CalendarItem;
|
||||||
|
|
||||||
namespace Wino.Core.Integration.Processors;
|
namespace Wino.Core.Integration.Processors;
|
||||||
|
|
||||||
@@ -18,8 +23,7 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
|||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
ICalendarService calendarService,
|
ICalendarService calendarService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ICalendarServiceEx calendarServiceEx,
|
IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, calendarService, accountService, mimeFileService)
|
||||||
IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, calendarService, accountService, calendarServiceEx, mimeFileService)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -32,7 +36,202 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
|||||||
|
|
||||||
public async Task ManageCalendarEventAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount)
|
public async Task ManageCalendarEventAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount)
|
||||||
{
|
{
|
||||||
// TODO:
|
var status = calendarEvent.Status;
|
||||||
|
|
||||||
|
var recurringEventId = calendarEvent.RecurringEventId;
|
||||||
|
|
||||||
|
// 1. Canceled exceptions of recurred events are only guaranteed to have recurringEventId, Id and start time.
|
||||||
|
// 2. Updated exceptions of recurred events have different Id, but recurringEventId is the same as parent.
|
||||||
|
|
||||||
|
// Check if we have this event before.
|
||||||
|
var existingCalendarItem = await CalendarService.GetCalendarItemAsync(assignedCalendar.Id, calendarEvent.Id);
|
||||||
|
|
||||||
|
if (existingCalendarItem == null)
|
||||||
|
{
|
||||||
|
CalendarItem parentRecurringEvent = null;
|
||||||
|
|
||||||
|
// Manage the recurring event id.
|
||||||
|
if (!string.IsNullOrEmpty(recurringEventId))
|
||||||
|
{
|
||||||
|
parentRecurringEvent = await CalendarService.GetCalendarItemAsync(assignedCalendar.Id, recurringEventId).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (parentRecurringEvent == null)
|
||||||
|
{
|
||||||
|
Log.Information($"Parent recurring event is missing for event. Skipping creation of {calendarEvent.Id}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't have this event yet. Create a new one.
|
||||||
|
var eventStartDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.Start);
|
||||||
|
var eventEndDateTimeOffset = GoogleIntegratorExtensions.GetEventDateTimeOffset(calendarEvent.End);
|
||||||
|
|
||||||
|
|
||||||
|
CalendarItem calendarItem = null;
|
||||||
|
|
||||||
|
if (parentRecurringEvent != null)
|
||||||
|
{
|
||||||
|
// Exceptions of parent events might not have all the fields populated.
|
||||||
|
// We must use the parent event's data for fields that don't exists.
|
||||||
|
|
||||||
|
var organizerMail = GetOrganizerEmail(calendarEvent, organizerAccount);
|
||||||
|
var organizerName = GetOrganizerName(calendarEvent, organizerAccount);
|
||||||
|
|
||||||
|
|
||||||
|
calendarItem = new CalendarItem()
|
||||||
|
{
|
||||||
|
CalendarId = assignedCalendar.Id,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
Description = calendarEvent.Description ?? parentRecurringEvent.Description,
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
StartTimeOffset = eventStartDateTimeOffset.ToString("o"),
|
||||||
|
EndTimeOffset = eventEndDateTimeOffset.ToString("o"),
|
||||||
|
Location = string.IsNullOrEmpty(calendarEvent.Location) ? parentRecurringEvent.Location : calendarEvent.Location,
|
||||||
|
|
||||||
|
// Leave it empty if it's not populated.
|
||||||
|
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent) == null ? string.Empty : GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
|
||||||
|
Status = GetStatus(calendarEvent.Status),
|
||||||
|
Title = string.IsNullOrEmpty(calendarEvent.Summary) ? parentRecurringEvent.Title : calendarEvent.Summary,
|
||||||
|
UpdatedAt = DateTimeOffset.UtcNow,
|
||||||
|
Visibility = string.IsNullOrEmpty(calendarEvent.Visibility) ? parentRecurringEvent.Visibility : GetVisibility(calendarEvent.Visibility),
|
||||||
|
HtmlLink = string.IsNullOrEmpty(calendarEvent.HtmlLink) ? parentRecurringEvent.HtmlLink : calendarEvent.HtmlLink,
|
||||||
|
RemoteEventId = calendarEvent.Id,
|
||||||
|
IsLocked = calendarEvent.Locked.GetValueOrDefault(),
|
||||||
|
OrganizerDisplayName = string.IsNullOrEmpty(organizerName) ? parentRecurringEvent.OrganizerDisplayName : organizerName,
|
||||||
|
OrganizerEmail = string.IsNullOrEmpty(organizerMail) ? parentRecurringEvent.OrganizerEmail : organizerMail
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is a parent event creation.
|
||||||
|
// Start-End dates are guaranteed to be populated.
|
||||||
|
|
||||||
|
calendarItem = new CalendarItem()
|
||||||
|
{
|
||||||
|
CalendarId = assignedCalendar.Id,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
Description = calendarEvent.Description,
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
StartTimeOffset = eventStartDateTimeOffset.ToString("o"),
|
||||||
|
EndTimeOffset = eventEndDateTimeOffset.ToString("o"),
|
||||||
|
Location = calendarEvent.Location,
|
||||||
|
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
|
||||||
|
Status = GetStatus(calendarEvent.Status),
|
||||||
|
Title = calendarEvent.Summary,
|
||||||
|
UpdatedAt = DateTimeOffset.UtcNow,
|
||||||
|
Visibility = GetVisibility(calendarEvent.Visibility),
|
||||||
|
HtmlLink = calendarEvent.HtmlLink,
|
||||||
|
RemoteEventId = calendarEvent.Id,
|
||||||
|
IsLocked = calendarEvent.Locked.GetValueOrDefault(),
|
||||||
|
OrganizerDisplayName = GetOrganizerName(calendarEvent, organizerAccount),
|
||||||
|
OrganizerEmail = GetOrganizerEmail(calendarEvent, organizerAccount)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide canceled events.
|
||||||
|
calendarItem.IsHidden = calendarItem.Status == CalendarItemStatus.Cancelled;
|
||||||
|
|
||||||
|
// Manage the recurring event id.
|
||||||
|
if (parentRecurringEvent != null)
|
||||||
|
{
|
||||||
|
calendarItem.RecurringCalendarItemId = parentRecurringEvent.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"({assignedCalendar.Name}) {calendarItem.Title}, Start: {calendarItem.StartDate.ToString("f")}, End: {calendarItem.EndDate.ToString("f")}");
|
||||||
|
|
||||||
|
// Attendees
|
||||||
|
var attendees = new List<CalendarEventAttendee>();
|
||||||
|
|
||||||
|
if (calendarEvent.Attendees == null)
|
||||||
|
{
|
||||||
|
// Self-only event.
|
||||||
|
|
||||||
|
attendees.Add(new CalendarEventAttendee()
|
||||||
|
{
|
||||||
|
CalendarItemId = calendarItem.Id,
|
||||||
|
IsOrganizer = true,
|
||||||
|
Email = organizerAccount.Address,
|
||||||
|
Name = organizerAccount.SenderName,
|
||||||
|
AttendenceStatus = AttendeeStatus.Accepted,
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
IsOptionalAttendee = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var attendee in calendarEvent.Attendees)
|
||||||
|
{
|
||||||
|
if (attendee.Self == true)
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(attendee.Email))
|
||||||
|
{
|
||||||
|
AttendeeStatus GetAttendenceStatus(string responseStatus)
|
||||||
|
{
|
||||||
|
return responseStatus switch
|
||||||
|
{
|
||||||
|
"accepted" => AttendeeStatus.Accepted,
|
||||||
|
"declined" => AttendeeStatus.Declined,
|
||||||
|
"tentative" => AttendeeStatus.Tentative,
|
||||||
|
"needsAction" => AttendeeStatus.NeedsAction,
|
||||||
|
_ => AttendeeStatus.NeedsAction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventAttendee = new CalendarEventAttendee()
|
||||||
|
{
|
||||||
|
CalendarItemId = calendarItem.Id,
|
||||||
|
IsOrganizer = attendee.Organizer ?? false,
|
||||||
|
Comment = attendee.Comment,
|
||||||
|
Email = attendee.Email,
|
||||||
|
Name = attendee.DisplayName,
|
||||||
|
AttendenceStatus = GetAttendenceStatus(attendee.ResponseStatus),
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
IsOptionalAttendee = attendee.Optional ?? false,
|
||||||
|
};
|
||||||
|
|
||||||
|
attendees.Add(eventAttendee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await CalendarService.CreateNewCalendarItemAsync(calendarItem, attendees);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We have this event already. Update it.
|
||||||
|
if (calendarEvent.Status == "cancelled")
|
||||||
|
{
|
||||||
|
// Parent event is canceled. We must delete everything.
|
||||||
|
if (string.IsNullOrEmpty(recurringEventId))
|
||||||
|
{
|
||||||
|
Log.Information("Parent event is canceled. Deleting all instances of {Id}", existingCalendarItem.Id);
|
||||||
|
|
||||||
|
await CalendarService.DeleteCalendarItemAsync(existingCalendarItem.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Child event is canceled.
|
||||||
|
// Child should live as long as parent lives, but must not be displayed to the user.
|
||||||
|
|
||||||
|
existingCalendarItem.IsHidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Make sure to unhide the event.
|
||||||
|
// It might be marked as hidden before.
|
||||||
|
existingCalendarItem.IsHidden = false;
|
||||||
|
|
||||||
|
// Update the event properties.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upsert the event.
|
||||||
|
await Connection.InsertOrReplaceAsync(existingCalendarItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetOrganizerName(Event calendarEvent, MailAccount account)
|
private string GetOrganizerName(Event calendarEvent, MailAccount account)
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public class ImapChangeProcessor : DefaultChangeProcessor, IImapChangeProcessor
|
|||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ICalendarService calendarService,
|
ICalendarService calendarService,
|
||||||
ICalendarServiceEx calendarServiceEx,
|
IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, calendarService, accountService, mimeFileService)
|
||||||
IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, calendarService, accountService, calendarServiceEx, mimeFileService)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
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 Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Extensions;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
|
|
||||||
namespace Wino.Core.Integration.Processors;
|
namespace Wino.Core.Integration.Processors;
|
||||||
@@ -13,8 +17,7 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
|
|||||||
ICalendarService calendarService,
|
ICalendarService calendarService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ICalendarServiceEx calendarServiceEx,
|
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, calendarService, accountService, mimeFileService)
|
||||||
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, calendarService, accountService, calendarServiceEx, mimeFileService)
|
|
||||||
, IOutlookChangeProcessor
|
, IOutlookChangeProcessor
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -37,6 +40,109 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
|
|||||||
|
|
||||||
public async Task ManageCalendarEventAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount)
|
public async Task ManageCalendarEventAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount)
|
||||||
{
|
{
|
||||||
// TODO
|
// We parse the occurrences based on the parent event.
|
||||||
|
// There is literally no point to store them because
|
||||||
|
// type=Exception events are the exceptional childs of recurrency parent event.
|
||||||
|
|
||||||
|
if (calendarEvent.Type == EventType.Occurrence) return;
|
||||||
|
|
||||||
|
var savingItem = await CalendarService.GetCalendarItemAsync(assignedCalendar.Id, calendarEvent.Id);
|
||||||
|
|
||||||
|
Guid savingItemId = Guid.Empty;
|
||||||
|
|
||||||
|
if (savingItem != null)
|
||||||
|
savingItemId = savingItem.Id;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
savingItemId = Guid.NewGuid();
|
||||||
|
savingItem = new CalendarItem() { Id = savingItemId };
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset eventStartDateTimeOffset = OutlookIntegratorExtensions.GetDateTimeOffsetFromDateTimeTimeZone(calendarEvent.Start);
|
||||||
|
DateTimeOffset eventEndDateTimeOffset = OutlookIntegratorExtensions.GetDateTimeOffsetFromDateTimeTimeZone(calendarEvent.End);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
savingItem.RemoteEventId = calendarEvent.Id;
|
||||||
|
savingItem.StartTimeOffset = eventStartDateTimeOffset.ToString("O");
|
||||||
|
savingItem.EndTimeOffset = eventEndDateTimeOffset.ToString("O");
|
||||||
|
|
||||||
|
savingItem.Title = calendarEvent.Subject;
|
||||||
|
savingItem.Description = calendarEvent.Body?.Content;
|
||||||
|
savingItem.Location = calendarEvent.Location?.DisplayName;
|
||||||
|
|
||||||
|
if (savingItem.Title.Contains("Atatürk"))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendarEvent.Type == EventType.Exception && !string.IsNullOrEmpty(calendarEvent.SeriesMasterId))
|
||||||
|
{
|
||||||
|
// This is a recurring event exception.
|
||||||
|
// We need to find the parent event and set it as recurring event id.
|
||||||
|
|
||||||
|
var parentEvent = await CalendarService.GetCalendarItemAsync(assignedCalendar.Id, calendarEvent.SeriesMasterId);
|
||||||
|
|
||||||
|
if (parentEvent != null)
|
||||||
|
{
|
||||||
|
savingItem.RecurringCalendarItemId = parentEvent.Id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Warning($"Parent recurring event is missing for event. Skipping creation of {calendarEvent.Id}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the recurrence pattern to string for parent recurring events.
|
||||||
|
if (calendarEvent.Type == EventType.SeriesMaster && calendarEvent.Recurrence != null)
|
||||||
|
{
|
||||||
|
savingItem.Recurrence = OutlookIntegratorExtensions.ToRfc5545RecurrenceString(calendarEvent.Recurrence);
|
||||||
|
}
|
||||||
|
|
||||||
|
savingItem.HtmlLink = calendarEvent.WebLink;
|
||||||
|
savingItem.CalendarId = assignedCalendar.Id;
|
||||||
|
savingItem.OrganizerEmail = calendarEvent.Organizer?.EmailAddress?.Address;
|
||||||
|
savingItem.OrganizerDisplayName = calendarEvent.Organizer?.EmailAddress?.Name;
|
||||||
|
savingItem.IsHidden = false;
|
||||||
|
|
||||||
|
if (calendarEvent.ResponseStatus?.Response != null)
|
||||||
|
{
|
||||||
|
switch (calendarEvent.ResponseStatus.Response.Value)
|
||||||
|
{
|
||||||
|
case ResponseType.None:
|
||||||
|
case ResponseType.NotResponded:
|
||||||
|
savingItem.Status = CalendarItemStatus.NotResponded;
|
||||||
|
break;
|
||||||
|
case ResponseType.TentativelyAccepted:
|
||||||
|
savingItem.Status = CalendarItemStatus.Tentative;
|
||||||
|
break;
|
||||||
|
case ResponseType.Accepted:
|
||||||
|
case ResponseType.Organizer:
|
||||||
|
savingItem.Status = CalendarItemStatus.Confirmed;
|
||||||
|
break;
|
||||||
|
case ResponseType.Declined:
|
||||||
|
savingItem.Status = CalendarItemStatus.Cancelled;
|
||||||
|
savingItem.IsHidden = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
savingItem.Status = CalendarItemStatus.Confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upsert the event.
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,207 +361,75 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false);
|
await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
bool isInitialSync = string.IsNullOrEmpty(Account.SynchronizationDeltaIdentifier);
|
||||||
|
|
||||||
|
_logger.Debug("Is initial synchronization: {IsInitialSync}", isInitialSync);
|
||||||
|
|
||||||
var localCalendars = await _gmailChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
var localCalendars = await _gmailChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// TODO: Better logging and exception handling.
|
||||||
foreach (var calendar in localCalendars)
|
foreach (var calendar in localCalendars)
|
||||||
{
|
{
|
||||||
// We can do just delta sync. It will fallback to full sync if there are no sync tokens or if the token is expired.
|
var request = _calendarService.Events.List(calendar.RemoteCalendarId);
|
||||||
await DeltaSynchronizeCalendarAsync(calendar).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Return proper result from delta or full sync.
|
request.SingleEvents = false;
|
||||||
return new CalendarSynchronizationResult()
|
request.ShowDeleted = true;
|
||||||
{
|
|
||||||
CompletedState = SynchronizationCompletedState.Success,
|
|
||||||
DownloadedEvents = new List<ICalendarItem>(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FullSynchronizeCalendarAsync(AccountCalendar calendar)
|
if (!string.IsNullOrEmpty(calendar.SynchronizationDeltaToken))
|
||||||
{
|
|
||||||
var calendarId = calendar.RemoteCalendarId;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Get events from the last 30 days to 1 year in the future
|
|
||||||
var timeMin = DateTime.Now.AddYears(-3);
|
|
||||||
var timeMax = DateTime.Now.AddYears(2);
|
|
||||||
|
|
||||||
var request = _calendarService.Events.List(calendarId);
|
|
||||||
request.TimeMinDateTimeOffset = timeMin;
|
|
||||||
request.TimeMaxDateTimeOffset = timeMax;
|
|
||||||
request.SingleEvents = false; // Include recurring events
|
|
||||||
request.ShowDeleted = true; // Include deleted events for synchronization
|
|
||||||
request.MaxResults = 2500; // Maximum allowed by Google Calendar API
|
|
||||||
|
|
||||||
var events = await request.ExecuteAsync();
|
|
||||||
|
|
||||||
if (events.Items != null && events.Items.Count > 0)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Processing {events.Items.Count} events from calendar: {calendarId}");
|
// If a sync token is available, perform an incremental sync
|
||||||
|
request.SyncToken = calendar.SynchronizationDeltaToken;
|
||||||
foreach (var googleEvent in events.Items)
|
|
||||||
{
|
|
||||||
await ProcessGoogleEventAsync(googleEvent, calendar);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine($"No events found in calendar: {calendarId}");
|
// If no sync token, perform an initial sync
|
||||||
|
// Fetch events from the past year
|
||||||
|
|
||||||
|
request.TimeMinDateTimeOffset = DateTimeOffset.UtcNow.AddYears(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the sync token for future delta syncs
|
string nextPageToken;
|
||||||
if (!string.IsNullOrEmpty(events.NextSyncToken))
|
string syncToken;
|
||||||
|
|
||||||
|
var allEvents = new List<Event>();
|
||||||
|
|
||||||
|
do
|
||||||
{
|
{
|
||||||
await _gmailChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, events.NextSyncToken).ConfigureAwait(false);
|
// Execute the request
|
||||||
|
var events = await request.ExecuteAsync();
|
||||||
|
|
||||||
Console.WriteLine($"Stored sync token for calendar {calendarId} to enable delta sync");
|
// Process the fetched events
|
||||||
}
|
if (events.Items != null)
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Failed to synchronize calendar {calendarId}: {ex.Message}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> DeltaSynchronizeCalendarAsync(AccountCalendar calendar)
|
|
||||||
{
|
|
||||||
var calendarId = calendar.RemoteCalendarId;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Starting delta sync for calendar: {calendarId}");
|
|
||||||
|
|
||||||
// Get the stored sync token for this calendar
|
|
||||||
var syncToken = calendar.SynchronizationDeltaToken;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(syncToken))
|
|
||||||
{
|
|
||||||
Console.WriteLine($"No sync token found for calendar {calendarId}. Performing full sync...");
|
|
||||||
await FullSynchronizeCalendarAsync(calendar);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the events list request with sync token
|
|
||||||
var request = _calendarService.Events.List(calendarId);
|
|
||||||
request.SyncToken = syncToken;
|
|
||||||
request.ShowDeleted = true; // Important: include deleted events for delta sync
|
|
||||||
request.SingleEvents = false; // Include recurring events
|
|
||||||
|
|
||||||
Console.WriteLine($"Requesting delta changes with sync token: {syncToken.Substring(0, Math.Min(20, syncToken.Length))}...");
|
|
||||||
|
|
||||||
Events events;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
events = await request.ExecuteAsync();
|
|
||||||
}
|
|
||||||
catch (Google.GoogleApiException ex) when (ex.HttpStatusCode == System.Net.HttpStatusCode.Gone)
|
|
||||||
{
|
|
||||||
// Sync token has expired, need to do full sync
|
|
||||||
Console.WriteLine($"Sync token expired for calendar {calendarId}. Performing full sync...");
|
|
||||||
await FullSynchronizeCalendarAsync(calendar);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events.Items != null && events.Items.Count > 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Processing {events.Items.Count} delta changes for calendar: {calendarId}");
|
|
||||||
|
|
||||||
foreach (var googleEvent in events.Items)
|
|
||||||
{
|
{
|
||||||
await ProcessDeltaCalendarEventAsync(googleEvent, calendar);
|
allEvents.AddRange(events.Items);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
// Get the next page token and sync token
|
||||||
|
nextPageToken = events.NextPageToken;
|
||||||
|
syncToken = events.NextSyncToken;
|
||||||
|
|
||||||
|
// Set the next page token for subsequent requests
|
||||||
|
request.PageToken = nextPageToken;
|
||||||
|
|
||||||
|
} while (!string.IsNullOrEmpty(nextPageToken));
|
||||||
|
|
||||||
|
calendar.SynchronizationDeltaToken = syncToken;
|
||||||
|
|
||||||
|
// allEvents contains new or updated events.
|
||||||
|
// Process them and create/update local calendar items.
|
||||||
|
|
||||||
|
foreach (var @event in allEvents)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"No changes found for calendar: {calendarId}");
|
// TODO: Exception handling for event processing.
|
||||||
|
// TODO: Also update attendees and other properties.
|
||||||
|
|
||||||
|
await _gmailChangeProcessor.ManageCalendarEventAsync(@event, calendar, Account).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the new sync token
|
await _gmailChangeProcessor.UpdateAccountCalendarAsync(calendar).ConfigureAwait(false);
|
||||||
if (!string.IsNullOrEmpty(events.NextSyncToken))
|
|
||||||
{
|
|
||||||
await _gmailChangeProcessor.UpdateCalendarSyncTokenAsync(calendarId, events.NextSyncToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
calendar.SynchronizationDeltaToken = events.NextSyncToken;
|
|
||||||
Console.WriteLine($"Updated sync token for calendar {calendarId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Error during delta sync for calendar {calendarId}: {ex.Message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
return default;
|
||||||
/// Processes a single event change from delta synchronization
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="googleEvent">The Google Calendar event</param>
|
|
||||||
/// <param name="calendarId">The ID of the calendar containing the event</param>
|
|
||||||
private async Task ProcessDeltaCalendarEventAsync(Event googleEvent, AccountCalendar calendar)
|
|
||||||
{
|
|
||||||
var calendarId = calendar.RemoteCalendarId;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (googleEvent.Status == "cancelled")
|
|
||||||
{
|
|
||||||
// Handle deleted/canceled events
|
|
||||||
await _gmailChangeProcessor.MarkEventAsDeletedAsync(googleEvent.Id, calendarId);
|
|
||||||
Console.WriteLine($"🗑️ Marked event as deleted: {googleEvent.Summary ?? googleEvent.Id}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For active events (confirmed, tentative), process normally
|
|
||||||
var calendarEvent = GoogleIntegratorExtensions.MapGoogleEventToCalendarEvent(googleEvent, calendar);
|
|
||||||
var result = await _gmailChangeProcessor.UpsertEventAsync(calendarEvent);
|
|
||||||
|
|
||||||
// Sync attendees for delta events too
|
|
||||||
await SyncEventAttendeesAsync(googleEvent, calendarEvent.Id);
|
|
||||||
|
|
||||||
if (result > 0)
|
|
||||||
{
|
|
||||||
var action = await _gmailChangeProcessor.GetEventByRemoteIdAsync(googleEvent.Id) != null ? "Updated" : "Created";
|
|
||||||
Console.WriteLine($"✅ {action} event: {calendarEvent.Title} ({calendarEvent.RemoteEventId})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"❌ Failed to process delta event {googleEvent.Id}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes a single Google Calendar event and updates the local database
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="googleEvent">The Google Calendar event</param>
|
|
||||||
/// <param name="calendarId">The ID of the calendar containing the event</param>
|
|
||||||
private async Task ProcessGoogleEventAsync(Event googleEvent, AccountCalendar calendar)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (googleEvent.Status == "cancelled")
|
|
||||||
{
|
|
||||||
// Handle deleted events
|
|
||||||
await _gmailChangeProcessor.DeleteEventAsync(googleEvent.Id);
|
|
||||||
Console.WriteLine($"Marked event as deleted: {googleEvent.Summary ?? googleEvent.Id}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var calendarEvent = googleEvent.MapGoogleEventToCalendarEvent(calendar);
|
|
||||||
await _gmailChangeProcessor.UpsertEventAsync(calendarEvent);
|
|
||||||
|
|
||||||
// Sync attendees separately
|
|
||||||
await SyncEventAttendeesAsync(googleEvent, calendarEvent.Id);
|
|
||||||
|
|
||||||
Console.WriteLine($"Processed event: {calendarEvent.Title} ({calendarEvent.RemoteEventId})");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Failed to process event {googleEvent.Id}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SynchronizeCalendarsAsync(CancellationToken cancellationToken = default)
|
private async Task SynchronizeCalendarsAsync(CancellationToken cancellationToken = default)
|
||||||
@@ -643,40 +511,6 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Syncs attendees for an event from Google Calendar data
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="googleEvent">The Google Calendar event</param>
|
|
||||||
/// <param name="eventId">The internal event Guid</param>
|
|
||||||
private async Task SyncEventAttendeesAsync(Event googleEvent, Guid eventId)
|
|
||||||
{
|
|
||||||
var attendees = new List<CalendarEventAttendee>();
|
|
||||||
|
|
||||||
if (googleEvent.Attendees != null && googleEvent.Attendees.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var googleAttendee in googleEvent.Attendees)
|
|
||||||
{
|
|
||||||
var attendee = new CalendarEventAttendee
|
|
||||||
{
|
|
||||||
EventId = eventId,
|
|
||||||
Email = googleAttendee.Email ?? string.Empty,
|
|
||||||
DisplayName = googleAttendee.DisplayName,
|
|
||||||
ResponseStatus = GoogleIntegratorExtensions.FromGoogleStatus(googleAttendee.ResponseStatus),
|
|
||||||
IsOptional = googleAttendee.Optional ?? false,
|
|
||||||
IsOrganizer = googleAttendee.Organizer ?? false,
|
|
||||||
IsSelf = googleAttendee.Self ?? false,
|
|
||||||
Comment = googleAttendee.Comment,
|
|
||||||
AdditionalGuests = googleAttendee.AdditionalGuests
|
|
||||||
};
|
|
||||||
|
|
||||||
attendees.Add(attendee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync attendees (replaces existing)
|
|
||||||
await _gmailChangeProcessor.SyncAttendeesForEventAsync(eventId, attendees).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeArchiveFolderAsync()
|
private async Task InitializeArchiveFolderAsync()
|
||||||
{
|
{
|
||||||
var localFolders = await _gmailChangeProcessor.GetLocalFoldersAsync(Account.Id).ConfigureAwait(false);
|
var localFolders = await _gmailChangeProcessor.GetLocalFoldersAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private List<string> _appTerminationBehavior;
|
private List<string> _appTerminationBehavior;
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial List<string> SearchModes { get; set; }
|
public partial List<string> SearchModes { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -129,11 +129,11 @@ public class WinoMailCollection
|
|||||||
private async Task HandleExistingThreadAsync(ObservableGroup<object, IMailItem> group, ThreadMailItemViewModel threadViewModel, MailCopy addedItem)
|
private async Task HandleExistingThreadAsync(ObservableGroup<object, IMailItem> group, ThreadMailItemViewModel threadViewModel, MailCopy addedItem)
|
||||||
{
|
{
|
||||||
var existingGroupKey = GetGroupingKey(threadViewModel);
|
var existingGroupKey = GetGroupingKey(threadViewModel);
|
||||||
|
|
||||||
await ExecuteUIThread(() => { threadViewModel.AddMailItemViewModel(addedItem); });
|
await ExecuteUIThread(() => { threadViewModel.AddMailItemViewModel(addedItem); });
|
||||||
|
|
||||||
var newGroupKey = GetGroupingKey(threadViewModel);
|
var newGroupKey = GetGroupingKey(threadViewModel);
|
||||||
|
|
||||||
if (!existingGroupKey.Equals(newGroupKey))
|
if (!existingGroupKey.Equals(newGroupKey))
|
||||||
{
|
{
|
||||||
await MoveThreadToNewGroupAsync(group, threadViewModel, newGroupKey);
|
await MoveThreadToNewGroupAsync(group, threadViewModel, newGroupKey);
|
||||||
@@ -294,25 +294,6 @@ public class WinoMailCollection
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateThumbnails(string address)
|
|
||||||
{
|
|
||||||
if (CoreDispatcher == null) return;
|
|
||||||
|
|
||||||
CoreDispatcher.ExecuteOnUIThread(() =>
|
|
||||||
{
|
|
||||||
foreach (var group in _mailItemSource)
|
|
||||||
{
|
|
||||||
foreach (var item in group)
|
|
||||||
{
|
|
||||||
if (item is MailItemViewModel mailItemViewModel && mailItemViewModel.MailCopy.FromAddress.Equals(address, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
mailItemViewModel.ThumbnailUpdatedEvent = !mailItemViewModel.ThumbnailUpdatedEvent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fins the item container that updated mail copy belongs to and updates it.
|
/// Fins the item container that updated mail copy belongs to and updates it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
using System;
|
using Wino.Core.Domain;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using Wino.Core.Domain;
|
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels.Data;
|
namespace Wino.Mail.ViewModels.Data;
|
||||||
|
|
||||||
public partial class AccountContactViewModel : ObservableObject
|
public class AccountContactViewModel : AccountContact
|
||||||
{
|
{
|
||||||
public string Address { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Base64ContactPicture { get; set; }
|
|
||||||
public bool IsRootContact { get; set; }
|
|
||||||
|
|
||||||
public AccountContactViewModel(AccountContact contact)
|
public AccountContactViewModel(AccountContact contact)
|
||||||
{
|
{
|
||||||
Address = contact.Address;
|
Address = contact.Address;
|
||||||
@@ -46,7 +39,4 @@ public partial class AccountContactViewModel : ObservableObject
|
|||||||
/// Display name of the contact in a format: Name <Address>.
|
/// Display name of the contact in a format: Name <Address>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DisplayName => Address == Name || string.IsNullOrWhiteSpace(Name) ? Address.ToLowerInvariant() : $"{Name} <{Address.ToLowerInvariant()}>";
|
public string DisplayName => Address == Name || string.IsNullOrWhiteSpace(Name) ? Address.ToLowerInvariant() : $"{Name} <{Address.ToLowerInvariant()}>";
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial bool ThumbnailUpdatedEvent { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Wino.Mail.ViewModels.Data;
|
|||||||
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IMailItem
|
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IMailItem
|
||||||
{
|
{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
private MailCopy mailCopy = mailCopy;
|
||||||
|
|
||||||
public Guid UniqueId => ((IMailItem)MailCopy).UniqueId;
|
public Guid UniqueId => ((IMailItem)MailCopy).UniqueId;
|
||||||
public string ThreadId => ((IMailItem)MailCopy).ThreadId;
|
public string ThreadId => ((IMailItem)MailCopy).ThreadId;
|
||||||
@@ -23,13 +23,10 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IM
|
|||||||
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
|
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool ThumbnailUpdatedEvent { get; set; } = false;
|
private bool isCustomFocused;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool IsCustomFocused { get; set; }
|
private bool isSelected;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial bool IsSelected { get; set; }
|
|
||||||
|
|
||||||
public bool IsFlagged
|
public bool IsFlagged
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
IRecipient<AccountSynchronizationCompleted>,
|
IRecipient<AccountSynchronizationCompleted>,
|
||||||
IRecipient<NewMailSynchronizationRequested>,
|
IRecipient<NewMailSynchronizationRequested>,
|
||||||
IRecipient<AccountSynchronizerStateChanged>,
|
IRecipient<AccountSynchronizerStateChanged>,
|
||||||
IRecipient<AccountCacheResetMessage>,
|
IRecipient<AccountCacheResetMessage>
|
||||||
IRecipient<ThumbnailAdded>
|
|
||||||
{
|
{
|
||||||
private bool isChangingFolder = false;
|
private bool isChangingFolder = false;
|
||||||
|
|
||||||
@@ -1141,6 +1140,4 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(ThumbnailAdded message) => MailCollection.UpdateThumbnails(message.Email);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,12 @@ using Wino.Mail.ViewModels.Data;
|
|||||||
using Wino.Mail.ViewModels.Messages;
|
using Wino.Mail.ViewModels.Messages;
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
using Wino.Messaging.UI;
|
|
||||||
using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
|
|
||||||
public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||||
IRecipient<NewMailItemRenderingRequestedEvent>,
|
IRecipient<NewMailItemRenderingRequestedEvent>,
|
||||||
IRecipient<ThumbnailAdded>,
|
|
||||||
ITransferProgress // For listening IMAP message download progress.
|
ITransferProgress // For listening IMAP message download progress.
|
||||||
{
|
{
|
||||||
private readonly IMailDialogService _dialogService;
|
private readonly IMailDialogService _dialogService;
|
||||||
@@ -790,27 +788,4 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
Log.Error(ex, "Failed to render mail.");
|
Log.Error(ex, "Failed to render mail.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(ThumbnailAdded message)
|
|
||||||
{
|
|
||||||
UpdateThumbnails(ToItems, message.Email);
|
|
||||||
UpdateThumbnails(CcItems, message.Email);
|
|
||||||
UpdateThumbnails(BccItems, message.Email);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateThumbnails(ObservableCollection<AccountContactViewModel> items, string email)
|
|
||||||
{
|
|
||||||
if (Dispatcher == null || items.Count == 0) return;
|
|
||||||
|
|
||||||
Dispatcher.ExecuteOnUIThread(() =>
|
|
||||||
{
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
if (item.Address.Equals(email, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
item.ThumbnailUpdatedEvent = !item.ThumbnailUpdatedEvent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
|
|
||||||
public partial class MessageListPageViewModel : MailBaseViewModel
|
public class MessageListPageViewModel : MailBaseViewModel
|
||||||
{
|
{
|
||||||
public IPreferencesService PreferencesService { get; }
|
public IPreferencesService PreferencesService { get; }
|
||||||
private readonly IThumbnailService _thumbnailService;
|
|
||||||
|
|
||||||
private int selectedMarkAsOptionIndex;
|
private int selectedMarkAsOptionIndex;
|
||||||
|
|
||||||
public int SelectedMarkAsOptionIndex
|
public int SelectedMarkAsOptionIndex
|
||||||
{
|
{
|
||||||
get => selectedMarkAsOptionIndex;
|
get => selectedMarkAsOptionIndex;
|
||||||
@@ -48,7 +46,9 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
];
|
];
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
private int leftHoverActionIndex;
|
private int leftHoverActionIndex;
|
||||||
|
|
||||||
public int LeftHoverActionIndex
|
public int LeftHoverActionIndex
|
||||||
{
|
{
|
||||||
get => leftHoverActionIndex;
|
get => leftHoverActionIndex;
|
||||||
@@ -61,7 +61,9 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private int centerHoverActionIndex;
|
private int centerHoverActionIndex;
|
||||||
|
|
||||||
public int CenterHoverActionIndex
|
public int CenterHoverActionIndex
|
||||||
{
|
{
|
||||||
get => centerHoverActionIndex;
|
get => centerHoverActionIndex;
|
||||||
@@ -75,6 +77,7 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int rightHoverActionIndex;
|
private int rightHoverActionIndex;
|
||||||
|
|
||||||
public int RightHoverActionIndex
|
public int RightHoverActionIndex
|
||||||
{
|
{
|
||||||
get => rightHoverActionIndex;
|
get => rightHoverActionIndex;
|
||||||
@@ -86,21 +89,18 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public MessageListPageViewModel(IPreferencesService preferencesService, IThumbnailService thumbnailService)
|
public MessageListPageViewModel(IMailDialogService dialogService,
|
||||||
|
IPreferencesService preferencesService)
|
||||||
{
|
{
|
||||||
PreferencesService = preferencesService;
|
PreferencesService = preferencesService;
|
||||||
_thumbnailService = thumbnailService;
|
|
||||||
leftHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.LeftHoverAction);
|
leftHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.LeftHoverAction);
|
||||||
centerHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.CenterHoverAction);
|
centerHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.CenterHoverAction);
|
||||||
rightHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.RightHoverAction);
|
rightHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.RightHoverAction);
|
||||||
|
|
||||||
SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues<MailMarkAsOption>(), PreferencesService.MarkAsPreference);
|
SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues<MailMarkAsOption>(), PreferencesService.MarkAsPreference);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task ClearAvatarsCacheAsync()
|
|
||||||
{
|
|
||||||
await _thumbnailService.ClearCache();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
<DataTemplate x:Key="ClickableAccountMenuTemplate" x:DataType="menu:AccountMenuItem">
|
<DataTemplate x:Key="ClickableAccountMenuTemplate" x:DataType="menu:AccountMenuItem">
|
||||||
<controls:AccountNavigationItem
|
<controls:AccountNavigationItem
|
||||||
x:Name="AccountItem"
|
x:Name="AccountItem"
|
||||||
Height="50"
|
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
VerticalContentAlignment="Stretch"
|
VerticalContentAlignment="Stretch"
|
||||||
BindingData="{x:Bind}"
|
BindingData="{x:Bind}"
|
||||||
@@ -58,12 +57,19 @@
|
|||||||
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
|
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
|
||||||
</muxc:NavigationViewItem.InfoBadge>
|
</muxc:NavigationViewItem.InfoBadge>
|
||||||
|
|
||||||
<Grid>
|
<Grid
|
||||||
|
MaxHeight="70"
|
||||||
|
Margin="0,8"
|
||||||
|
RowSpacing="6">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="2" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
<StackPanel VerticalAlignment="Center">
|
<StackPanel VerticalAlignment="Center">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="AccountNameTextblock"
|
x:Name="AccountNameTextblock"
|
||||||
@@ -74,7 +80,8 @@
|
|||||||
TextTrimming="CharacterEllipsis" />
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
FontSize="13"
|
||||||
|
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
|
||||||
MaxLines="1"
|
MaxLines="1"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{x:Bind Parameter.Address, Mode=OneWay}"
|
Text="{x:Bind Parameter.Address, Mode=OneWay}"
|
||||||
@@ -83,6 +90,8 @@
|
|||||||
|
|
||||||
<PathIcon
|
<PathIcon
|
||||||
x:Name="AttentionIcon"
|
x:Name="AttentionIcon"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.RowSpan="2"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -95,6 +104,7 @@
|
|||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="3"
|
||||||
Width="10"
|
Width="10"
|
||||||
Height="10"
|
Height="10"
|
||||||
|
Margin="0,8,0,0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Background="{ThemeResource AppBarItemBackgroundThemeBrush}"
|
Background="{ThemeResource AppBarItemBackgroundThemeBrush}"
|
||||||
@@ -159,7 +169,7 @@
|
|||||||
<!-- Inbox or any other folders. -->
|
<!-- Inbox or any other folders. -->
|
||||||
<DataTemplate x:Key="FolderMenuTemplate" x:DataType="menu:FolderMenuItem">
|
<DataTemplate x:Key="FolderMenuTemplate" x:DataType="menu:FolderMenuItem">
|
||||||
<coreControls:WinoNavigationViewItem
|
<coreControls:WinoNavigationViewItem
|
||||||
MinHeight="40"
|
MinHeight="30"
|
||||||
AllowDrop="True"
|
AllowDrop="True"
|
||||||
ContextRequested="MenuItemContextRequested"
|
ContextRequested="MenuItemContextRequested"
|
||||||
DataContext="{x:Bind}"
|
DataContext="{x:Bind}"
|
||||||
@@ -190,7 +200,6 @@
|
|||||||
<muxc:NavigationViewItem.Content>
|
<muxc:NavigationViewItem.Content>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="FolderBackgroundGrid"
|
x:Name="FolderBackgroundGrid"
|
||||||
MaxHeight="36"
|
|
||||||
Padding="2"
|
Padding="2"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
<Grid
|
<Grid
|
||||||
@@ -252,7 +261,7 @@
|
|||||||
<PathIcon Data="F1 M 8.613281 17.5 C 8.75 17.942709 8.945312 18.359375 9.199219 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 L 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 6.572266 C 18.580729 6.344402 18.390299 6.132813 18.178711 5.9375 C 17.967121 5.742188 17.740885 5.566407 17.5 5.410156 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 L 4.951172 2.5 C 4.619141 2.5 4.303385 2.568359 4.003906 2.705078 C 3.704427 2.841797 3.44401 3.02409 3.222656 3.251953 C 3.001302 3.479818 2.825521 3.745117 2.695312 4.047852 C 2.565104 4.350587 2.5 4.66797 2.5 5 L 13.310547 5 C 12.60091 5.266928 11.998697 5.683594 11.503906 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.38737 2.568359 15.704753 2.705078 16.000977 C 2.841797 16.297201 3.024088 16.55599 3.251953 16.777344 C 3.479818 16.998697 3.745117 17.174479 4.047852 17.304688 C 4.350586 17.434896 4.667969 17.5 5 17.5 Z" />
|
<PathIcon Data="F1 M 8.613281 17.5 C 8.75 17.942709 8.945312 18.359375 9.199219 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 L 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 6.572266 C 18.580729 6.344402 18.390299 6.132813 18.178711 5.9375 C 17.967121 5.742188 17.740885 5.566407 17.5 5.410156 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 L 4.951172 2.5 C 4.619141 2.5 4.303385 2.568359 4.003906 2.705078 C 3.704427 2.841797 3.44401 3.02409 3.222656 3.251953 C 3.001302 3.479818 2.825521 3.745117 2.695312 4.047852 C 2.565104 4.350587 2.5 4.66797 2.5 5 L 13.310547 5 C 12.60091 5.266928 11.998697 5.683594 11.503906 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.38737 2.568359 15.704753 2.705078 16.000977 C 2.841797 16.297201 3.024088 16.55599 3.251953 16.777344 C 3.479818 16.998697 3.745117 17.174479 4.047852 17.304688 C 4.350586 17.434896 4.667969 17.5 5 17.5 Z" />
|
||||||
</coreControls:WinoNavigationViewItem.Icon>
|
</coreControls:WinoNavigationViewItem.Icon>
|
||||||
|
|
||||||
<Grid Height="50">
|
<Grid MinHeight="50">
|
||||||
<StackPanel VerticalAlignment="Center" Spacing="0">
|
<StackPanel VerticalAlignment="Center" Spacing="0">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="AccountNameTextblock"
|
x:Name="AccountNameTextblock"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB |
BIN
Wino.Mail/Assets/Thumbnails/airbnb.com.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Wino.Mail/Assets/Thumbnails/apple.com.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Wino.Mail/Assets/Thumbnails/google.com.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Wino.Mail/Assets/Thumbnails/microsoft.com.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
Wino.Mail/Assets/Thumbnails/steampowered.com.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Wino.Mail/Assets/Thumbnails/uber.com.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Wino.Mail/Assets/Thumbnails/youtube.com.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
@@ -1,28 +1,30 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using CommunityToolkit.WinUI;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Xaml.Interactivity;
|
using Microsoft.Xaml.Interactivity;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Windows.UI.Xaml.Controls.Primitives;
|
using Windows.UI.Xaml.Controls.Primitives;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Controls;
|
||||||
using Wino.Core.Domain.Models.Menus;
|
using Wino.Core.Domain.Models.Menus;
|
||||||
using Wino.Core.UWP.Controls;
|
using Wino.Core.UWP.Controls;
|
||||||
using Wino.Helpers;
|
using Wino.Helpers;
|
||||||
|
|
||||||
namespace Wino.Behaviors;
|
namespace Wino.Behaviors;
|
||||||
|
|
||||||
public partial class BindableCommandBarBehavior : Behavior<CommandBar>
|
public class BindableCommandBarBehavior : Behavior<CommandBar>
|
||||||
{
|
{
|
||||||
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>();
|
|
||||||
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
|
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
|
||||||
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
|
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
|
||||||
new PropertyMetadata(null, UpdateCommands));
|
new PropertyMetadata(null, UpdateCommands));
|
||||||
|
|
||||||
[GeneratedDependencyProperty]
|
public ICommand ItemClickedCommand
|
||||||
public partial ICommand ItemClickedCommand { get; set; }
|
{
|
||||||
|
get { return (ICommand)GetValue(ItemClickedCommandProperty); }
|
||||||
|
set { SetValue(ItemClickedCommandProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ItemClickedCommandProperty = DependencyProperty.Register(nameof(ItemClickedCommand), typeof(ICommand), typeof(BindableCommandBarBehavior), new PropertyMetadata(null));
|
||||||
|
|
||||||
public object PrimaryCommands
|
public object PrimaryCommands
|
||||||
{
|
{
|
||||||
@@ -81,7 +83,7 @@ public partial class BindableCommandBarBehavior : Behavior<CommandBar>
|
|||||||
AssociatedObject.PrimaryCommands.Clear();
|
AssociatedObject.PrimaryCommands.Clear();
|
||||||
AssociatedObject.SecondaryCommands.Clear();
|
AssociatedObject.SecondaryCommands.Clear();
|
||||||
|
|
||||||
if (PrimaryCommands is not IEnumerable enumerable) return;
|
if (!(PrimaryCommands is IEnumerable enumerable)) return;
|
||||||
|
|
||||||
foreach (var command in enumerable)
|
foreach (var command in enumerable)
|
||||||
{
|
{
|
||||||
@@ -96,26 +98,19 @@ public partial class BindableCommandBarBehavior : Behavior<CommandBar>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var label = XamlHelpers.GetOperationString(mailOperationMenuItem.Operation);
|
var label = XamlHelpers.GetOperationString(mailOperationMenuItem.Operation);
|
||||||
var labelPosition = string.IsNullOrWhiteSpace(label) || !_preferencesService.IsShowActionLabelsEnabled ?
|
|
||||||
CommandBarLabelPosition.Collapsed : CommandBarLabelPosition.Default;
|
|
||||||
menuItem = new AppBarButton
|
menuItem = new AppBarButton
|
||||||
{
|
{
|
||||||
Width = double.NaN,
|
|
||||||
MinWidth = 40,
|
|
||||||
Icon = new WinoFontIcon() { Glyph = ControlConstants.WinoIconFontDictionary[XamlHelpers.GetWinoIconGlyph(mailOperationMenuItem.Operation)] },
|
Icon = new WinoFontIcon() { Glyph = ControlConstants.WinoIconFontDictionary[XamlHelpers.GetWinoIconGlyph(mailOperationMenuItem.Operation)] },
|
||||||
Label = label,
|
Label = label,
|
||||||
LabelPosition = labelPosition,
|
LabelPosition = string.IsNullOrWhiteSpace(label) ? CommandBarLabelPosition.Collapsed : CommandBarLabelPosition.Default,
|
||||||
DataContext = mailOperationMenuItem,
|
DataContext = mailOperationMenuItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(label))
|
ToolTip toolTip = new ToolTip
|
||||||
{
|
{
|
||||||
var toolTip = new ToolTip
|
Content = label
|
||||||
{
|
};
|
||||||
Content = label
|
ToolTipService.SetToolTip((DependencyObject)menuItem, toolTip);
|
||||||
};
|
|
||||||
ToolTipService.SetToolTip((DependencyObject)menuItem, toolTip);
|
|
||||||
}
|
|
||||||
|
|
||||||
((AppBarButton)menuItem).Click -= Button_Click;
|
((AppBarButton)menuItem).Click -= Button_Click;
|
||||||
((AppBarButton)menuItem).Click += Button_Click;
|
((AppBarButton)menuItem).Click += Button_Click;
|
||||||
@@ -169,7 +164,7 @@ public partial class BindableCommandBarBehavior : Behavior<CommandBar>
|
|||||||
private static void UpdateCommands(DependencyObject dependencyObject,
|
private static void UpdateCommands(DependencyObject dependencyObject,
|
||||||
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
||||||
{
|
{
|
||||||
if (dependencyObject is not BindableCommandBarBehavior behavior) return;
|
if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
|
||||||
|
|
||||||
if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList)
|
if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ public partial class AccountNavigationItem : WinoNavigationViewItem
|
|||||||
_itemsRepeater = GetTemplateChild(PART_NavigationViewItemMenuItemsHost) as ItemsRepeater;
|
_itemsRepeater = GetTemplateChild(PART_NavigationViewItemMenuItemsHost) as ItemsRepeater;
|
||||||
_selectionIndicator = GetTemplateChild(PART_SelectionIndicator) as Windows.UI.Xaml.Shapes.Rectangle;
|
_selectionIndicator = GetTemplateChild(PART_SelectionIndicator) as Windows.UI.Xaml.Shapes.Rectangle;
|
||||||
|
|
||||||
|
if (_itemsRepeater == null) return;
|
||||||
|
|
||||||
|
(_itemsRepeater.Layout as StackLayout).Spacing = 0;
|
||||||
|
|
||||||
UpdateSelectionBorder();
|
UpdateSelectionBorder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +60,12 @@ public partial class AccountNavigationItem : WinoNavigationViewItem
|
|||||||
{
|
{
|
||||||
if (_selectionIndicator == null) return;
|
if (_selectionIndicator == null) return;
|
||||||
|
|
||||||
|
// Adjsuting Margin in the styles are not possible due to the fact that we use the same tempalte for different types of menu items.
|
||||||
|
// Account templates listed under merged accounts will have Padding of 44. We must adopt to that.
|
||||||
|
|
||||||
|
bool hasParentMenuItem = BindingData is IAccountMenuItem accountMenuItem && accountMenuItem.ParentMenuItem != null;
|
||||||
|
|
||||||
|
_selectionIndicator.Margin = !hasParentMenuItem ? new Thickness(-44, 12, 0, 12) : new Thickness(-60, 12, -60, 12);
|
||||||
_selectionIndicator.Scale = IsActiveAccount ? new Vector3(1, 1, 1) : new Vector3(0, 0, 0);
|
_selectionIndicator.Scale = IsActiveAccount ? new Vector3(1, 1, 1) : new Vector3(0, 0, 0);
|
||||||
_selectionIndicator.Visibility = IsActiveAccount ? Visibility.Visible : Visibility.Collapsed;
|
_selectionIndicator.Visibility = IsActiveAccount ? Visibility.Visible : Visibility.Collapsed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Fernandezja.ColorHashSharp;
|
using Fernandezja.ColorHashSharp;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Windows.UI;
|
using Windows.UI;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Windows.UI.Xaml.Media;
|
using Windows.UI.Xaml.Media;
|
||||||
using Windows.UI.Xaml.Media.Imaging;
|
using Windows.UI.Xaml.Media.Imaging;
|
||||||
using Windows.UI.Xaml.Shapes;
|
using Windows.UI.Xaml.Shapes;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.UWP.Services;
|
||||||
|
|
||||||
namespace Wino.Controls;
|
namespace Wino.Controls;
|
||||||
|
|
||||||
@@ -22,21 +21,12 @@ public partial class ImagePreviewControl : Control
|
|||||||
private const string PART_InitialsTextBlock = "InitialsTextBlock";
|
private const string PART_InitialsTextBlock = "InitialsTextBlock";
|
||||||
private const string PART_KnownHostImage = "KnownHostImage";
|
private const string PART_KnownHostImage = "KnownHostImage";
|
||||||
private const string PART_Ellipse = "Ellipse";
|
private const string PART_Ellipse = "Ellipse";
|
||||||
private const string PART_FaviconSquircle = "FaviconSquircle";
|
|
||||||
private const string PART_FaviconImage = "FaviconImage";
|
|
||||||
|
|
||||||
#region Dependency Properties
|
#region Dependency Properties
|
||||||
|
|
||||||
public static readonly DependencyProperty FromNameProperty = DependencyProperty.Register(nameof(FromName), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnInformationChanged));
|
public static readonly DependencyProperty FromNameProperty = DependencyProperty.Register(nameof(FromName), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnAddressInformationChanged));
|
||||||
public static readonly DependencyProperty FromAddressProperty = DependencyProperty.Register(nameof(FromAddress), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnInformationChanged));
|
public static readonly DependencyProperty FromAddressProperty = DependencyProperty.Register(nameof(FromAddress), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, OnAddressInformationChanged));
|
||||||
public static readonly DependencyProperty SenderContactPictureProperty = DependencyProperty.Register(nameof(SenderContactPicture), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnInformationChanged)));
|
public static readonly DependencyProperty SenderContactPictureProperty = DependencyProperty.Register(nameof(SenderContactPicture), typeof(string), typeof(ImagePreviewControl), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnAddressInformationChanged)));
|
||||||
public static readonly DependencyProperty ThumbnailUpdatedEventProperty = DependencyProperty.Register(nameof(ThumbnailUpdatedEvent), typeof(bool), typeof(ImagePreviewControl), new PropertyMetadata(false, new PropertyChangedCallback(OnInformationChanged)));
|
|
||||||
|
|
||||||
public bool ThumbnailUpdatedEvent
|
|
||||||
{
|
|
||||||
get { return (bool)GetValue(ThumbnailUpdatedEventProperty); }
|
|
||||||
set { SetValue(ThumbnailUpdatedEventProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets base64 string of the sender contact picture.
|
/// Gets or sets base64 string of the sender contact picture.
|
||||||
@@ -65,8 +55,6 @@ public partial class ImagePreviewControl : Control
|
|||||||
private Grid InitialsGrid;
|
private Grid InitialsGrid;
|
||||||
private TextBlock InitialsTextblock;
|
private TextBlock InitialsTextblock;
|
||||||
private Image KnownHostImage;
|
private Image KnownHostImage;
|
||||||
private Border FaviconSquircle;
|
|
||||||
private Image FaviconImage;
|
|
||||||
private CancellationTokenSource contactPictureLoadingCancellationTokenSource;
|
private CancellationTokenSource contactPictureLoadingCancellationTokenSource;
|
||||||
|
|
||||||
public ImagePreviewControl()
|
public ImagePreviewControl()
|
||||||
@@ -82,13 +70,11 @@ public partial class ImagePreviewControl : Control
|
|||||||
InitialsTextblock = GetTemplateChild(PART_InitialsTextBlock) as TextBlock;
|
InitialsTextblock = GetTemplateChild(PART_InitialsTextBlock) as TextBlock;
|
||||||
KnownHostImage = GetTemplateChild(PART_KnownHostImage) as Image;
|
KnownHostImage = GetTemplateChild(PART_KnownHostImage) as Image;
|
||||||
Ellipse = GetTemplateChild(PART_Ellipse) as Ellipse;
|
Ellipse = GetTemplateChild(PART_Ellipse) as Ellipse;
|
||||||
FaviconSquircle = GetTemplateChild(PART_FaviconSquircle) as Border;
|
|
||||||
FaviconImage = GetTemplateChild(PART_FaviconImage) as Image;
|
|
||||||
|
|
||||||
UpdateInformation();
|
UpdateInformation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnInformationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
private static void OnAddressInformationChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||||
{
|
{
|
||||||
if (obj is ImagePreviewControl control)
|
if (obj is ImagePreviewControl control)
|
||||||
control.UpdateInformation();
|
control.UpdateInformation();
|
||||||
@@ -96,7 +82,7 @@ public partial class ImagePreviewControl : Control
|
|||||||
|
|
||||||
private async void UpdateInformation()
|
private async void UpdateInformation()
|
||||||
{
|
{
|
||||||
if ((KnownHostImage == null && FaviconSquircle == null) || InitialsGrid == null || InitialsTextblock == null || (string.IsNullOrEmpty(FromName) && string.IsNullOrEmpty(FromAddress)))
|
if (KnownHostImage == null || InitialsGrid == null || InitialsTextblock == null || (string.IsNullOrEmpty(FromName) && string.IsNullOrEmpty(FromAddress)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Cancel active image loading if exists.
|
// Cancel active image loading if exists.
|
||||||
@@ -105,100 +91,81 @@ public partial class ImagePreviewControl : Control
|
|||||||
contactPictureLoadingCancellationTokenSource.Cancel();
|
contactPictureLoadingCancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
string contactPicture = SenderContactPicture;
|
var host = ThumbnailService.GetHost(FromAddress);
|
||||||
|
|
||||||
var isAvatarThumbnail = false;
|
bool isKnownHost = false;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(contactPicture) && !string.IsNullOrEmpty(FromAddress))
|
if (!string.IsNullOrEmpty(host))
|
||||||
{
|
{
|
||||||
contactPicture = await App.Current.ThumbnailService.GetThumbnailAsync(FromAddress);
|
var tuple = ThumbnailService.CheckIsKnown(host);
|
||||||
isAvatarThumbnail = true;
|
|
||||||
|
isKnownHost = tuple.Item1;
|
||||||
|
host = tuple.Item2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(contactPicture))
|
if (isKnownHost)
|
||||||
{
|
{
|
||||||
if (isAvatarThumbnail && FaviconSquircle != null && FaviconImage != null)
|
// Unrealize others.
|
||||||
{
|
|
||||||
// Show favicon in squircle
|
|
||||||
FaviconSquircle.Visibility = Visibility.Visible;
|
|
||||||
InitialsGrid.Visibility = Visibility.Collapsed;
|
|
||||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
var bitmapImage = await GetBitmapImageAsync(contactPicture);
|
KnownHostImage.Visibility = Visibility.Visible;
|
||||||
|
InitialsGrid.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
if (bitmapImage != null)
|
// Apply company logo.
|
||||||
{
|
KnownHostImage.Source = new BitmapImage(new Uri(ThumbnailService.GetKnownHostImage(host)));
|
||||||
FaviconImage.Source = bitmapImage;
|
}
|
||||||
}
|
else
|
||||||
}
|
{
|
||||||
else
|
KnownHostImage.Visibility = Visibility.Collapsed;
|
||||||
|
InitialsGrid.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(SenderContactPicture))
|
||||||
{
|
{
|
||||||
// Show normal avatar (tondo)
|
|
||||||
FaviconSquircle.Visibility = Visibility.Collapsed;
|
|
||||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
|
||||||
InitialsGrid.Visibility = Visibility.Visible;
|
|
||||||
contactPictureLoadingCancellationTokenSource = new CancellationTokenSource();
|
contactPictureLoadingCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var brush = await GetContactImageBrushAsync(contactPicture);
|
var brush = await GetContactImageBrushAsync();
|
||||||
|
|
||||||
if (brush != null)
|
if (!contactPictureLoadingCancellationTokenSource?.Token.IsCancellationRequested ?? false)
|
||||||
{
|
{
|
||||||
if (!contactPictureLoadingCancellationTokenSource?.Token.IsCancellationRequested ?? false)
|
Ellipse.Fill = brush;
|
||||||
{
|
InitialsTextblock.Text = string.Empty;
|
||||||
Ellipse.Fill = brush;
|
|
||||||
InitialsTextblock.Text = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
// Log exception.
|
||||||
Debugger.Break();
|
Debugger.Break();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
var colorHash = new ColorHash();
|
||||||
FaviconSquircle.Visibility = Visibility.Collapsed;
|
var rgb = colorHash.Rgb(FromAddress);
|
||||||
KnownHostImage.Visibility = Visibility.Collapsed;
|
|
||||||
InitialsGrid.Visibility = Visibility.Visible;
|
|
||||||
|
|
||||||
var colorHash = new ColorHash();
|
Ellipse.Fill = new SolidColorBrush(Color.FromArgb(rgb.A, rgb.R, rgb.G, rgb.B));
|
||||||
var rgb = colorHash.Rgb(FromAddress);
|
InitialsTextblock.Text = ExtractInitialsFromName(FromName);
|
||||||
|
}
|
||||||
Ellipse.Fill = new SolidColorBrush(Color.FromArgb(rgb.A, rgb.R, rgb.G, rgb.B));
|
|
||||||
InitialsTextblock.Text = ExtractInitialsFromName(FromName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ImageBrush> GetContactImageBrushAsync(string base64)
|
private async Task<ImageBrush> GetContactImageBrushAsync()
|
||||||
{
|
{
|
||||||
// Load the image from base64 string.
|
// Load the image from base64 string.
|
||||||
|
var bitmapImage = new BitmapImage();
|
||||||
var bitmapImage = await GetBitmapImageAsync(base64);
|
|
||||||
|
|
||||||
if (bitmapImage == null) return null;
|
var imageArray = Convert.FromBase64String(SenderContactPicture);
|
||||||
|
var imageStream = new MemoryStream(imageArray);
|
||||||
|
var randomAccessImageStream = imageStream.AsRandomAccessStream();
|
||||||
|
|
||||||
|
randomAccessImageStream.Seek(0);
|
||||||
|
|
||||||
|
|
||||||
|
await bitmapImage.SetSourceAsync(randomAccessImageStream);
|
||||||
|
|
||||||
return new ImageBrush() { ImageSource = bitmapImage };
|
return new ImageBrush() { ImageSource = bitmapImage };
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<BitmapImage> GetBitmapImageAsync(string base64)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bitmapImage = new BitmapImage();
|
|
||||||
var imageArray = Convert.FromBase64String(base64);
|
|
||||||
var imageStream = new MemoryStream(imageArray);
|
|
||||||
var randomAccessImageStream = imageStream.AsRandomAccessStream();
|
|
||||||
randomAccessImageStream.Seek(0);
|
|
||||||
await bitmapImage.SetSourceAsync(randomAccessImageStream);
|
|
||||||
return bitmapImage;
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ExtractInitialsFromName(string name)
|
public string ExtractInitialsFromName(string name)
|
||||||
{
|
{
|
||||||
// Change from name to from address in case of name doesn't exists.
|
// Change from name to from address in case of name doesn't exists.
|
||||||
|
|||||||
@@ -76,7 +76,6 @@
|
|||||||
FromAddress="{x:Bind MailItem.FromAddress, Mode=OneWay}"
|
FromAddress="{x:Bind MailItem.FromAddress, Mode=OneWay}"
|
||||||
FromName="{x:Bind MailItem.FromName, Mode=OneWay}"
|
FromName="{x:Bind MailItem.FromName, Mode=OneWay}"
|
||||||
SenderContactPicture="{x:Bind MailItem.SenderContact.Base64ContactPicture}"
|
SenderContactPicture="{x:Bind MailItem.SenderContact.Base64ContactPicture}"
|
||||||
ThumbnailUpdatedEvent="{x:Bind IsThumbnailUpdated, Mode=OneWay}"
|
|
||||||
Visibility="{x:Bind IsAvatarVisible, Mode=OneWay}" />
|
Visibility="{x:Bind IsAvatarVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using CommunityToolkit.WinUI;
|
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
@@ -34,13 +33,6 @@ public sealed partial class MailItemDisplayInformationControl : UserControl
|
|||||||
public static readonly DependencyProperty Prefer24HourTimeFormatProperty = DependencyProperty.Register(nameof(Prefer24HourTimeFormat), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
public static readonly DependencyProperty Prefer24HourTimeFormatProperty = DependencyProperty.Register(nameof(Prefer24HourTimeFormat), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
||||||
public static readonly DependencyProperty IsThreadExpanderVisibleProperty = DependencyProperty.Register(nameof(IsThreadExpanderVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
public static readonly DependencyProperty IsThreadExpanderVisibleProperty = DependencyProperty.Register(nameof(IsThreadExpanderVisible), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
||||||
public static readonly DependencyProperty IsThreadExpandedProperty = DependencyProperty.Register(nameof(IsThreadExpanded), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
public static readonly DependencyProperty IsThreadExpandedProperty = DependencyProperty.Register(nameof(IsThreadExpanded), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
||||||
public static readonly DependencyProperty IsThumbnailUpdatedProperty = DependencyProperty.Register(nameof(IsThumbnailUpdated), typeof(bool), typeof(MailItemDisplayInformationControl), new PropertyMetadata(false));
|
|
||||||
|
|
||||||
public bool IsThumbnailUpdated
|
|
||||||
{
|
|
||||||
get { return (bool)GetValue(IsThumbnailUpdatedProperty); }
|
|
||||||
set { SetValue(IsThumbnailUpdatedProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsThreadExpanded
|
public bool IsThreadExpanded
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,8 +55,7 @@
|
|||||||
DefaultLabelPosition="Collapsed"
|
DefaultLabelPosition="Collapsed"
|
||||||
IsOpen="False">
|
IsOpen="False">
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
||||||
LabelPosition="Collapsed"
|
LabelPosition="Collapsed"
|
||||||
ToolTipService.ToolTip="Light Theme"
|
ToolTipService.ToolTip="Light Theme"
|
||||||
@@ -67,8 +66,7 @@
|
|||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
||||||
LabelPosition="Collapsed"
|
LabelPosition="Collapsed"
|
||||||
ToolTipService.ToolTip="Dark Theme"
|
ToolTipService.ToolTip="Dark Theme"
|
||||||
@@ -81,8 +79,7 @@
|
|||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
|
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorBold, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorBold, Mode=TwoWay}"
|
||||||
Label="Bold"
|
Label="Bold"
|
||||||
ToolTipService.ToolTip="Bold">
|
ToolTipService.ToolTip="Bold">
|
||||||
@@ -91,8 +88,7 @@
|
|||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorItalic, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorItalic, Mode=TwoWay}"
|
||||||
Label="Italic"
|
Label="Italic"
|
||||||
ToolTipService.ToolTip="Italic">
|
ToolTipService.ToolTip="Italic">
|
||||||
@@ -101,8 +97,7 @@
|
|||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUnderline, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorUnderline, Mode=TwoWay}"
|
||||||
Label="Underline"
|
Label="Underline"
|
||||||
ToolTipService.ToolTip="Underline">
|
ToolTipService.ToolTip="Underline">
|
||||||
@@ -111,8 +106,7 @@
|
|||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorStrikethrough, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorStrikethrough, Mode=TwoWay}"
|
||||||
Label="Stroke"
|
Label="Stroke"
|
||||||
ToolTipService.ToolTip="Stroke">
|
ToolTipService.ToolTip="Stroke">
|
||||||
@@ -122,8 +116,7 @@
|
|||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUl, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorUl, Mode=TwoWay}"
|
||||||
Label="Bullet List"
|
Label="Bullet List"
|
||||||
ToolTipService.ToolTip="Bullet List">
|
ToolTipService.ToolTip="Bullet List">
|
||||||
@@ -132,8 +125,7 @@
|
|||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorOl, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorOl, Mode=TwoWay}"
|
||||||
Label="Ordered List"
|
Label="Ordered List"
|
||||||
ToolTipService.ToolTip="Ordered List">
|
ToolTipService.ToolTip="Ordered List">
|
||||||
@@ -145,8 +137,7 @@
|
|||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
|
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorOutdentAsync}"
|
Click="{x:Bind WebViewEditor.EditorOutdentAsync}"
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorOutdentEnabled, Mode=OneWay}"
|
IsEnabled="{x:Bind WebViewEditor.IsEditorOutdentEnabled, Mode=OneWay}"
|
||||||
Label="Decrease Indent"
|
Label="Decrease Indent"
|
||||||
@@ -156,8 +147,7 @@
|
|||||||
</AppBarButton.Icon>
|
</AppBarButton.Icon>
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorIndentAsync}"
|
Click="{x:Bind WebViewEditor.EditorIndentAsync}"
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorIndentEnabled, Mode=OneWay}"
|
IsEnabled="{x:Bind WebViewEditor.IsEditorIndentEnabled, Mode=OneWay}"
|
||||||
Label="Increase Indent"
|
Label="Increase Indent"
|
||||||
@@ -167,10 +157,7 @@
|
|||||||
</AppBarButton.Icon>
|
</AppBarButton.Icon>
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarElementContainer
|
<AppBarElementContainer VerticalAlignment="Center">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<ComboBox
|
<ComboBox
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="Transparent"
|
BorderBrush="Transparent"
|
||||||
@@ -214,8 +201,7 @@
|
|||||||
</AppBarElementContainer>
|
</AppBarElementContainer>
|
||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ShowImagePicker}"
|
Click="{x:Bind WebViewEditor.ShowImagePicker}"
|
||||||
Label="Add Image"
|
Label="Add Image"
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Photos}">
|
ToolTipService.ToolTip="{x:Bind domain:Translator.Photos}">
|
||||||
@@ -232,8 +218,7 @@
|
|||||||
</AppBarButton.Content>
|
</AppBarButton.Content>
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ShowEmojiPicker}"
|
Click="{x:Bind WebViewEditor.ShowEmojiPicker}"
|
||||||
Label="Add Emoji"
|
Label="Add Emoji"
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Emoji}">
|
ToolTipService.ToolTip="{x:Bind domain:Translator.Emoji}">
|
||||||
@@ -243,8 +228,7 @@
|
|||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
Width="48"
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorWebViewEditor, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorWebViewEditor, Mode=TwoWay}"
|
||||||
Label="Webview ToolBar"
|
Label="Webview ToolBar"
|
||||||
ToolTipService.ToolTip="Webview ToolBar">
|
ToolTipService.ToolTip="Webview ToolBar">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
using Wino.Core.Domain.Models.Folders;
|
||||||
@@ -10,11 +11,18 @@ namespace Wino.MenuFlyouts;
|
|||||||
|
|
||||||
public partial class WinoOperationFlyoutItem<TOperationMenuItem> : MenuFlyoutItem, IDisposable where TOperationMenuItem : IMenuOperation
|
public partial class WinoOperationFlyoutItem<TOperationMenuItem> : MenuFlyoutItem, IDisposable where TOperationMenuItem : IMenuOperation
|
||||||
{
|
{
|
||||||
|
private const double CustomHeight = 35;
|
||||||
|
|
||||||
public TOperationMenuItem Operation { get; set; }
|
public TOperationMenuItem Operation { get; set; }
|
||||||
Action<TOperationMenuItem> Clicked { get; set; }
|
Action<TOperationMenuItem> Clicked { get; set; }
|
||||||
|
|
||||||
public WinoOperationFlyoutItem(TOperationMenuItem operationMenuItem, Action<TOperationMenuItem> clicked)
|
public WinoOperationFlyoutItem(TOperationMenuItem operationMenuItem, Action<TOperationMenuItem> clicked)
|
||||||
{
|
{
|
||||||
|
Margin = new Thickness(4, 2, 4, 2);
|
||||||
|
CornerRadius = new CornerRadius(6, 6, 6, 6);
|
||||||
|
|
||||||
|
MinHeight = CustomHeight;
|
||||||
|
|
||||||
Operation = operationMenuItem;
|
Operation = operationMenuItem;
|
||||||
IsEnabled = operationMenuItem.IsEnabled;
|
IsEnabled = operationMenuItem.IsEnabled;
|
||||||
|
|
||||||
|
|||||||
@@ -28,27 +28,13 @@
|
|||||||
Foreground="White" />
|
Foreground="White" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Squircle for favicon -->
|
|
||||||
<Border
|
|
||||||
x:Name="FaviconSquircle"
|
|
||||||
Width="{TemplateBinding Width}"
|
|
||||||
Height="{TemplateBinding Height}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Background="Transparent"
|
|
||||||
CornerRadius="6"
|
|
||||||
Visibility="Collapsed">
|
|
||||||
<Image x:Name="FaviconImage" Stretch="Fill" />
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<Image
|
<Image
|
||||||
x:Name="KnownHostImage"
|
x:Name="KnownHostImage"
|
||||||
Width="{TemplateBinding Width}"
|
Width="{TemplateBinding Width}"
|
||||||
Height="{TemplateBinding Height}"
|
Height="{TemplateBinding Height}"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Stretch="UniformToFill"
|
Stretch="UniformToFill" />
|
||||||
Visibility="Collapsed" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
|
|||||||
@@ -90,7 +90,6 @@
|
|||||||
<Rectangle
|
<Rectangle
|
||||||
x:Name="CustomSelectionIndicator"
|
x:Name="CustomSelectionIndicator"
|
||||||
Width="3"
|
Width="3"
|
||||||
Margin="-44,12,0,12"
|
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Fill="{ThemeResource NavigationViewSelectionIndicatorForeground}"
|
Fill="{ThemeResource NavigationViewSelectionIndicatorForeground}"
|
||||||
Opacity="1"
|
Opacity="1"
|
||||||
@@ -112,7 +111,7 @@
|
|||||||
x:Load="False"
|
x:Load="False"
|
||||||
Visibility="Collapsed">
|
Visibility="Collapsed">
|
||||||
<muxc:ItemsRepeater.Layout>
|
<muxc:ItemsRepeater.Layout>
|
||||||
<muxc:StackLayout Orientation="Vertical" Spacing="0" />
|
<muxc:StackLayout Orientation="Vertical" />
|
||||||
</muxc:ItemsRepeater.Layout>
|
</muxc:ItemsRepeater.Layout>
|
||||||
</muxc:ItemsRepeater>
|
</muxc:ItemsRepeater>
|
||||||
|
|
||||||
|
|||||||
@@ -10,16 +10,11 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel Spacing="3">
|
||||||
<StackPanel.ChildrenTransitions>
|
|
||||||
<TransitionCollection>
|
|
||||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
|
||||||
</TransitionCollection>
|
|
||||||
</StackPanel.ChildrenTransitions>
|
|
||||||
<Image
|
<Image
|
||||||
Width="50"
|
Width="50"
|
||||||
Height="50"
|
Height="50"
|
||||||
Source="ms-appx:///Images/StoreLogo.png" />
|
Source="ms-appx:///Assets/StoreLogo.png" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,6,0,4"
|
Margin="0,6,0,4"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
@@ -172,14 +167,9 @@
|
|||||||
</controls:SettingsExpander.Items>
|
</controls:SettingsExpander.Items>
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
<controls:SettingsCard Header="Wino Mail">
|
<TextBlock Margin="0,0,4,0" HorizontalAlignment="Right">
|
||||||
<controls:SettingsCard.HeaderIcon>
|
<Run Text="{x:Bind domain:Translator.SettingsAboutVersion}" /><Run Text="{x:Bind ViewModel.VersionName}" />
|
||||||
<BitmapIcon ShowAsMonochrome="False" UriSource="ms-appx:///Images/StoreLogo.png" />
|
</TextBlock>
|
||||||
</controls:SettingsCard.HeaderIcon>
|
|
||||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" IsTextSelectionEnabled="True">
|
|
||||||
<Run Text="{x:Bind domain:Translator.SettingsAboutVersion}" /><Run Text="{x:Bind ViewModel.VersionName}" />
|
|
||||||
</TextBlock>
|
|
||||||
</controls:SettingsCard>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</abstract:AboutPageAbstract>
|
</abstract:AboutPageAbstract>
|
||||||
@@ -70,12 +70,6 @@
|
|||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<StackPanel.ChildrenTransitions>
|
|
||||||
<TransitionCollection>
|
|
||||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
|
||||||
</TransitionCollection>
|
|
||||||
</StackPanel.ChildrenTransitions>
|
|
||||||
|
|
||||||
<controls:SettingsCard
|
<controls:SettingsCard
|
||||||
Command="{x:Bind ViewModel.EditAccountDetailsCommand}"
|
Command="{x:Bind ViewModel.EditAccountDetailsCommand}"
|
||||||
Description="{x:Bind domain:Translator.SettingsEditAccountDetails_Description}"
|
Description="{x:Bind domain:Translator.SettingsEditAccountDetails_Description}"
|
||||||
|
|||||||
@@ -190,8 +190,6 @@
|
|||||||
IsDynamicOverflowEnabled="True"
|
IsDynamicOverflowEnabled="True"
|
||||||
OverflowButtonAlignment="Left">
|
OverflowButtonAlignment="Left">
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
||||||
LabelPosition="Collapsed"
|
LabelPosition="Collapsed"
|
||||||
ToolTipService.ToolTip="Light Theme"
|
ToolTipService.ToolTip="Light Theme"
|
||||||
@@ -202,8 +200,6 @@
|
|||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
||||||
LabelPosition="Collapsed"
|
LabelPosition="Collapsed"
|
||||||
ToolTipService.ToolTip="Dark Theme"
|
ToolTipService.ToolTip="Dark Theme"
|
||||||
@@ -227,38 +223,22 @@
|
|||||||
</toolkit:TabbedCommandBar.PaneCustomContent>
|
</toolkit:TabbedCommandBar.PaneCustomContent>
|
||||||
<toolkit:TabbedCommandBar.MenuItems>
|
<toolkit:TabbedCommandBar.MenuItems>
|
||||||
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="{x:Bind domain:Translator.EditorToolbarOption_Format}">
|
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="{x:Bind domain:Translator.EditorToolbarOption_Format}">
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton IsChecked="{x:Bind WebViewEditor.IsEditorBold, Mode=TwoWay}" Label="Bold">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorBold, Mode=TwoWay}"
|
|
||||||
Label="Bold">
|
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<PathIcon Data="{StaticResource BoldPathIcon}" />
|
<PathIcon Data="{StaticResource BoldPathIcon}" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton IsChecked="{x:Bind WebViewEditor.IsEditorItalic, Mode=TwoWay}" Label="Italic">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorItalic, Mode=TwoWay}"
|
|
||||||
Label="Italic">
|
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton IsChecked="{x:Bind WebViewEditor.IsEditorUnderline, Mode=TwoWay}" Label="Underline">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUnderline, Mode=TwoWay}"
|
|
||||||
Label="Underline">
|
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<PathIcon Data="{StaticResource UnderlinePathIcon}" />
|
<PathIcon Data="{StaticResource UnderlinePathIcon}" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton IsChecked="{x:Bind WebViewEditor.IsEditorStrikethrough, Mode=TwoWay}" Label="Stroke">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorStrikethrough, Mode=TwoWay}"
|
|
||||||
Label="Stroke">
|
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
@@ -266,21 +246,13 @@
|
|||||||
|
|
||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
|
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton IsChecked="{x:Bind WebViewEditor.IsEditorUl, Mode=TwoWay}" Label="Bullet List">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUl, Mode=TwoWay}"
|
|
||||||
Label="Bullet List">
|
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<PathIcon Data="{StaticResource BulletedListPathIcon}" />
|
<PathIcon Data="{StaticResource BulletedListPathIcon}" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
|
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton IsChecked="{x:Bind WebViewEditor.IsEditorOl, Mode=TwoWay}" Label="Ordered List">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorOl, Mode=TwoWay}"
|
|
||||||
Label="Ordered List">
|
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
@@ -289,8 +261,6 @@
|
|||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
|
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorOutdentAsync}"
|
Click="{x:Bind WebViewEditor.EditorOutdentAsync}"
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorOutdentEnabled, Mode=OneWay}"
|
IsEnabled="{x:Bind WebViewEditor.IsEditorOutdentEnabled, Mode=OneWay}"
|
||||||
Label="Outdent">
|
Label="Outdent">
|
||||||
@@ -302,8 +272,6 @@
|
|||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorIndentAsync}"
|
Click="{x:Bind WebViewEditor.EditorIndentAsync}"
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorIndentEnabled, Mode=OneWay}"
|
IsEnabled="{x:Bind WebViewEditor.IsEditorIndentEnabled, Mode=OneWay}"
|
||||||
Label="Indent">
|
Label="Indent">
|
||||||
@@ -314,11 +282,7 @@
|
|||||||
</AppBarButton.Content>
|
</AppBarButton.Content>
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarElementContainer
|
<AppBarElementContainer HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="AlignmentListView"
|
x:Name="AlignmentListView"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -366,8 +330,6 @@
|
|||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
|
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorWebViewEditor, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorWebViewEditor, Mode=TwoWay}"
|
||||||
Label="{x:Bind domain:Translator.EditorTooltip_WebViewEditor}"
|
Label="{x:Bind domain:Translator.EditorTooltip_WebViewEditor}"
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.EditorTooltip_WebViewEditor}">
|
ToolTipService.ToolTip="{x:Bind domain:Translator.EditorTooltip_WebViewEditor}">
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
|
|
||||||
if (e.NewFocusedElement == WebViewEditor)
|
if (e.NewFocusedElement == WebViewEditor)
|
||||||
{
|
{
|
||||||
await WebViewEditor.FocusEditorAsync(true);
|
await WebViewEditor.FocusEditorAsync(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,6 @@
|
|||||||
IsAvatarVisible="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowSenderPicturesEnabled, Mode=OneWay}"
|
IsAvatarVisible="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowSenderPicturesEnabled, Mode=OneWay}"
|
||||||
IsCustomFocused="{x:Bind IsCustomFocused, Mode=OneWay}"
|
IsCustomFocused="{x:Bind IsCustomFocused, Mode=OneWay}"
|
||||||
IsHoverActionsEnabled="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsHoverActionsEnabled, Mode=OneWay}"
|
IsHoverActionsEnabled="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsHoverActionsEnabled, Mode=OneWay}"
|
||||||
IsThumbnailUpdated="{x:Bind ThumbnailUpdatedEvent, Mode=OneWay}"
|
|
||||||
LeftHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.LeftHoverAction, Mode=OneWay}"
|
LeftHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.LeftHoverAction, Mode=OneWay}"
|
||||||
MailItem="{x:Bind MailCopy, Mode=OneWay}"
|
MailItem="{x:Bind MailCopy, Mode=OneWay}"
|
||||||
Prefer24HourTimeFormat="{Binding ElementName=root, Path=ViewModel.PreferencesService.Prefer24HourTimeFormat, Mode=OneWay}"
|
Prefer24HourTimeFormat="{Binding ElementName=root, Path=ViewModel.PreferencesService.Prefer24HourTimeFormat, Mode=OneWay}"
|
||||||
@@ -137,7 +136,6 @@
|
|||||||
IsAvatarVisible="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowSenderPicturesEnabled, Mode=OneWay}"
|
IsAvatarVisible="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowSenderPicturesEnabled, Mode=OneWay}"
|
||||||
IsCustomFocused="{x:Bind IsCustomFocused, Mode=OneWay}"
|
IsCustomFocused="{x:Bind IsCustomFocused, Mode=OneWay}"
|
||||||
IsHoverActionsEnabled="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsHoverActionsEnabled, Mode=OneWay}"
|
IsHoverActionsEnabled="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsHoverActionsEnabled, Mode=OneWay}"
|
||||||
IsThumbnailUpdated="{x:Bind ThumbnailUpdatedEvent, Mode=OneWay}"
|
|
||||||
LeftHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.LeftHoverAction, Mode=OneWay}"
|
LeftHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.LeftHoverAction, Mode=OneWay}"
|
||||||
MailItem="{x:Bind MailCopy, Mode=OneWay}"
|
MailItem="{x:Bind MailCopy, Mode=OneWay}"
|
||||||
Prefer24HourTimeFormat="{Binding ElementName=root, Path=ViewModel.PreferencesService.Prefer24HourTimeFormat, Mode=OneWay}"
|
Prefer24HourTimeFormat="{Binding ElementName=root, Path=ViewModel.PreferencesService.Prefer24HourTimeFormat, Mode=OneWay}"
|
||||||
|
|||||||
@@ -46,8 +46,7 @@
|
|||||||
Height="36"
|
Height="36"
|
||||||
FromAddress="{x:Bind Address}"
|
FromAddress="{x:Bind Address}"
|
||||||
FromName="{x:Bind Name}"
|
FromName="{x:Bind Name}"
|
||||||
SenderContactPicture="{x:Bind Base64ContactPicture}"
|
SenderContactPicture="{x:Bind Base64ContactPicture}" />
|
||||||
ThumbnailUpdatedEvent="{x:Bind ThumbnailUpdatedEvent, Mode=OneWay}" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" Text="{x:Bind Name}" />
|
<TextBlock Grid.Column="1" Text="{x:Bind Name}" />
|
||||||
|
|
||||||
@@ -157,11 +156,6 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ChildrenTransitions>
|
|
||||||
<TransitionCollection>
|
|
||||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
|
||||||
</TransitionCollection>
|
|
||||||
</Grid.ChildrenTransitions>
|
|
||||||
|
|
||||||
<Border
|
<Border
|
||||||
Background="{ThemeResource WinoContentZoneBackgroud}"
|
Background="{ThemeResource WinoContentZoneBackgroud}"
|
||||||
|
|||||||
@@ -51,13 +51,7 @@
|
|||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel Spacing="3">
|
||||||
<StackPanel.ChildrenTransitions>
|
|
||||||
<TransitionCollection>
|
|
||||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
|
||||||
</TransitionCollection>
|
|
||||||
</StackPanel.ChildrenTransitions>
|
|
||||||
|
|
||||||
<!-- Accent Color -->
|
<!-- Accent Color -->
|
||||||
<controls:SettingsExpander
|
<controls:SettingsExpander
|
||||||
Description="{x:Bind domain:Translator.SettingsAccentColor_Description}"
|
Description="{x:Bind domain:Translator.SettingsAccentColor_Description}"
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel>
|
||||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsAppPreferences_StartupBehavior_Description}" Header="{x:Bind domain:Translator.SettingsAppPreferences_StartupBehavior_Title}">
|
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsAppPreferences_StartupBehavior_Description}" Header="{x:Bind domain:Translator.SettingsAppPreferences_StartupBehavior_Title}">
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
x:Name="StartupEnabledToggleButton"
|
x:Name="StartupEnabledToggleButton"
|
||||||
|
|||||||
@@ -15,13 +15,7 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel Spacing="3">
|
||||||
<StackPanel.ChildrenTransitions>
|
|
||||||
<TransitionCollection>
|
|
||||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
|
||||||
</TransitionCollection>
|
|
||||||
</StackPanel.ChildrenTransitions>
|
|
||||||
|
|
||||||
<controls:SettingsExpander Description="{x:Bind domain:Translator.AccountDetailsPage_Description}" Header="{x:Bind domain:Translator.AccountDetailsPage_Title}">
|
<controls:SettingsExpander Description="{x:Bind domain:Translator.AccountDetailsPage_Description}" Header="{x:Bind domain:Translator.AccountDetailsPage_Title}">
|
||||||
<controls:SettingsExpander.HeaderIcon>
|
<controls:SettingsExpander.HeaderIcon>
|
||||||
<SymbolIcon Symbol="Contact" />
|
<SymbolIcon Symbol="Contact" />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel>
|
||||||
<controls:SettingsCard
|
<controls:SettingsCard
|
||||||
CommandParameter="Language"
|
CommandParameter="Language"
|
||||||
Description="{x:Bind domain:Translator.SettingsLanguage_Description}"
|
Description="{x:Bind domain:Translator.SettingsLanguage_Description}"
|
||||||
|
|||||||
@@ -11,14 +11,8 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel Spacing="4">
|
||||||
<StackPanel.ChildrenTransitions>
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.SettingsReader_Title}" />
|
||||||
<TransitionCollection>
|
|
||||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
|
||||||
</TransitionCollection>
|
|
||||||
</StackPanel.ChildrenTransitions>
|
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsReader_Title}" />
|
|
||||||
<controls:SettingsExpander
|
<controls:SettingsExpander
|
||||||
Description="{x:Bind domain:Translator.SettingsReaderFontFamily_Description}"
|
Description="{x:Bind domain:Translator.SettingsReaderFontFamily_Description}"
|
||||||
Header="{x:Bind domain:Translator.SettingsReaderFont_Title}"
|
Header="{x:Bind domain:Translator.SettingsReaderFont_Title}"
|
||||||
@@ -58,13 +52,6 @@
|
|||||||
</controls:SettingsExpander.Items>
|
</controls:SettingsExpander.Items>
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsMailRendering_ActionLabels_Description}" Header="{x:Bind domain:Translator.SettingsMailRendering_ActionLabels_Title}">
|
|
||||||
<controls:SettingsCard.HeaderIcon>
|
|
||||||
<PathIcon Data="F1 M 0 7.5 C 0 6.822917 0.169271 6.191406 0.507812 5.605469 C 0.839844 5.039062 1.289062 4.589844 1.855469 4.257812 C 2.441406 3.919271 3.072917 3.75 3.75 3.75 L 16.25 3.75 C 16.927082 3.75 17.558594 3.919271 18.144531 4.257812 C 18.710938 4.589844 19.160156 5.039062 19.492188 5.605469 C 19.830729 6.191406 20 6.822917 20 7.5 L 20 11.25 C 20 11.927084 19.830729 12.558594 19.492188 13.144531 C 19.160156 13.710938 18.710938 14.160156 18.144531 14.492188 C 17.558594 14.830729 16.927082 15 16.25 15 L 3.75 15 C 3.072917 15 2.441406 14.830729 1.855469 14.492188 C 1.289062 14.160156 0.839844 13.710938 0.507812 13.144531 C 0.169271 12.558594 0 11.927084 0 11.25 Z M 3.75 5 C 3.294271 5.000001 2.875977 5.112306 2.495117 5.336914 C 2.114258 5.561524 1.811523 5.864259 1.586914 6.245117 C 1.362305 6.625978 1.25 7.044271 1.25 7.5 L 1.25 11.25 C 1.25 11.705729 1.362305 12.124023 1.586914 12.504883 C 1.811523 12.885742 2.114258 13.188477 2.495117 13.413086 C 2.875977 13.637695 3.294271 13.75 3.75 13.75 L 16.25 13.75 C 16.705729 13.75 17.124023 13.637695 17.504883 13.413086 C 17.885742 13.188477 18.188477 12.885742 18.413086 12.504883 C 18.637695 12.124023 18.75 11.705729 18.75 11.25 L 18.75 7.5 C 18.75 7.044271 18.637695 6.625978 18.413086 6.245117 C 18.188477 5.864259 17.885742 5.561524 17.504883 5.336914 C 17.124023 5.112306 16.705729 5.000001 16.25 5 Z M 8.75 9.375 C 8.75 9.199219 8.810221 9.051107 8.930664 8.930664 C 9.051106 8.810222 9.199219 8.75 9.375 8.75 L 15 8.75 C 15.17578 8.75 15.323893 8.810222 15.444336 8.930664 C 15.564778 9.051107 15.625 9.199219 15.625 9.375 C 15.625 9.550781 15.564778 9.698894 15.444336 9.819336 C 15.323893 9.939779 15.17578 10 15 10 L 9.375 10 C 9.199219 10 9.051106 9.939779 8.930664 9.819336 C 8.810221 9.698894 8.75 9.550781 8.75 9.375 Z M 7.5 9.375 C 7.5 9.713542 7.415364 10.026042 7.246094 10.3125 C 7.076823 10.598959 6.848958 10.826823 6.5625 10.996094 C 6.276042 11.165365 5.963542 11.25 5.625 11.25 C 5.286458 11.25 4.973958 11.165365 4.6875 10.996094 C 4.401042 10.826823 4.173177 10.598959 4.003906 10.3125 C 3.834635 10.026042 3.75 9.713542 3.75 9.375 C 3.75 9.036459 3.834635 8.723959 4.003906 8.4375 C 4.173177 8.151042 4.401042 7.923178 4.6875 7.753906 C 4.973958 7.584636 5.286458 7.5 5.625 7.5 C 5.963542 7.5 6.276042 7.584636 6.5625 7.753906 C 6.848958 7.923178 7.076823 8.151042 7.246094 8.4375 C 7.415364 8.723959 7.5 9.036459 7.5 9.375 Z " />
|
|
||||||
</controls:SettingsCard.HeaderIcon>
|
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.IsShowActionLabelsEnabled, Mode=TwoWay}" />
|
|
||||||
</controls:SettingsCard>
|
|
||||||
|
|
||||||
<controls:SettingsExpander Description="{x:Bind domain:Translator.SettingsExternalContent_Description}" Header="{x:Bind domain:Translator.SettingsExternalContent_Title}">
|
<controls:SettingsExpander Description="{x:Bind domain:Translator.SettingsExternalContent_Description}" Header="{x:Bind domain:Translator.SettingsExternalContent_Title}">
|
||||||
<controls:SettingsExpander.HeaderIcon>
|
<controls:SettingsExpander.HeaderIcon>
|
||||||
<PathIcon Data="F1 M 4.023438 13.75 C 3.476562 13.75 2.958984 13.636068 2.470703 13.408203 C 1.982422 13.180339 1.554362 12.875977 1.186523 12.495117 C 0.818685 12.114258 0.528971 11.674805 0.317383 11.176758 C 0.105794 10.678711 0 10.159506 0 9.619141 C 0 9.065756 0.094401 8.535156 0.283203 8.027344 C 0.472005 7.519531 0.737305 7.068686 1.079102 6.674805 C 1.420898 6.280926 1.829427 5.961915 2.304688 5.717773 C 2.779948 5.473634 3.304036 5.338543 3.876953 5.3125 L 4.189453 5.302734 C 4.261067 4.501953 4.453125 3.776043 4.765625 3.125 C 5.078125 2.473959 5.488281 1.915691 5.996094 1.450195 C 6.503906 0.984701 7.097981 0.626629 7.77832 0.375977 C 8.458658 0.125326 9.199219 0 10 0 C 10.800781 0 11.541341 0.126953 12.22168 0.380859 C 12.902018 0.634766 13.496093 0.99284 14.003906 1.455078 C 14.511718 1.917318 14.921874 2.473959 15.234375 3.125 C 15.546874 3.776043 15.738932 4.501953 15.810547 5.302734 L 15.966797 5.302734 C 16.513672 5.302734 17.032877 5.416667 17.524414 5.644531 C 18.015949 5.872396 18.44401 6.176758 18.808594 6.557617 C 19.173176 6.938477 19.462891 7.379558 19.677734 7.880859 C 19.892578 8.382162 20 8.902995 20 9.443359 L 20 9.667969 C 20 9.700521 20 9.736328 20 9.775391 C 20 9.814453 19.996744 9.850261 19.990234 9.882812 C 19.573566 9.375 19.108072 8.932292 18.59375 8.554688 C 18.489582 8.248698 18.346354 7.972006 18.164062 7.724609 C 17.98177 7.477214 17.770182 7.267253 17.529297 7.094727 C 17.28841 6.922201 17.021484 6.788737 16.728516 6.694336 C 16.435547 6.599936 16.129557 6.552735 15.810547 6.552734 C 15.472005 6.552735 15.192057 6.446941 14.970703 6.235352 C 14.749349 6.023764 14.61263 5.755209 14.560547 5.429688 C 14.527994 5.195312 14.49056 4.967448 14.448242 4.746094 C 14.405924 4.52474 14.342447 4.300131 14.257812 4.072266 C 14.088541 3.603516 13.862304 3.193359 13.579102 2.841797 C 13.295897 2.490234 12.970377 2.195639 12.602539 1.958008 C 12.2347 1.720379 11.831055 1.542969 11.391602 1.425781 C 10.952148 1.308594 10.488281 1.25 10 1.25 C 9.511719 1.25 9.047852 1.308594 8.608398 1.425781 C 8.168945 1.542969 7.765299 1.71875 7.397461 1.953125 C 7.029622 2.1875 6.704102 2.480469 6.420898 2.832031 C 6.137695 3.183594 5.911458 3.59375 5.742188 4.0625 C 5.657552 4.290365 5.594075 4.514975 5.551758 4.736328 C 5.50944 4.957683 5.472005 5.188803 5.439453 5.429688 C 5.38737 5.813803 5.236002 6.097006 4.985352 6.279297 C 4.7347 6.46159 4.423828 6.552735 4.052734 6.552734 C 3.655599 6.552735 3.286133 6.635743 2.944336 6.801758 C 2.602539 6.967774 2.306315 7.189129 2.055664 7.46582 C 1.805013 7.742514 1.608073 8.059896 1.464844 8.417969 C 1.321615 8.776042 1.25 9.147136 1.25 9.53125 C 1.25 9.928386 1.321615 10.30599 1.464844 10.664062 C 1.608073 11.022136 1.806641 11.337891 2.060547 11.611328 C 2.314453 11.884766 2.61556 12.101237 2.963867 12.260742 C 3.312174 12.420248 3.691406 12.5 4.101562 12.5 L 7.431641 12.5 C 7.373047 12.708334 7.327474 12.916667 7.294922 13.125 C 7.26237 13.333334 7.236328 13.541667 7.216797 13.75 Z M 8.75 14.375 C 8.75 13.600261 8.898111 12.871094 9.194336 12.1875 C 9.49056 11.503906 9.892578 10.908203 10.400391 10.400391 C 10.908203 9.892578 11.503906 9.490561 12.1875 9.194336 C 12.871093 8.898112 13.60026 8.75 14.375 8.75 C 14.889322 8.75 15.385741 8.816732 15.864258 8.950195 C 16.342773 9.083659 16.790363 9.272461 17.207031 9.516602 C 17.623697 9.760742 18.004557 10.055339 18.349609 10.400391 C 18.69466 10.745443 18.989258 11.126303 19.233398 11.542969 C 19.477539 11.959636 19.66634 12.407227 19.799805 12.885742 C 19.933268 13.364258 20 13.860678 20 14.375 C 20 15.14974 19.851887 15.878906 19.555664 16.5625 C 19.259439 17.246094 18.857422 17.841797 18.349609 18.349609 C 17.841797 18.857422 17.246094 19.259439 16.5625 19.555664 C 15.878906 19.851889 15.149739 20 14.375 20 C 13.59375 20 12.861328 19.853516 12.177734 19.560547 C 11.494141 19.267578 10.898438 18.867188 10.390625 18.359375 C 9.882812 17.851562 9.482422 17.255859 9.189453 16.572266 C 8.896484 15.888672 8.75 15.15625 8.75 14.375 Z M 14.375 17.578125 C 14.570312 17.578125 14.736328 17.509766 14.873047 17.373047 L 17.373047 14.873047 C 17.509766 14.736328 17.578125 14.570312 17.578125 14.375 C 17.578125 14.179688 17.509766 14.013672 17.373047 13.876953 C 17.236328 13.740234 17.070312 13.671875 16.875 13.671875 C 16.679688 13.671875 16.513672 13.740234 16.376953 13.876953 L 15 15.253906 L 15 11.875 C 14.999999 11.705729 14.93815 11.559245 14.814453 11.435547 C 14.690755 11.31185 14.544271 11.25 14.375 11.25 C 14.205729 11.25 14.059244 11.31185 13.935547 11.435547 C 13.811849 11.559245 13.75 11.705729 13.75 11.875 L 13.75 15.253906 L 12.373047 13.876953 C 12.236328 13.740234 12.070312 13.671875 11.875 13.671875 C 11.679688 13.671875 11.513672 13.740234 11.376953 13.876953 C 11.240234 14.013672 11.171875 14.179688 11.171875 14.375 C 11.171875 14.570312 11.240234 14.736328 11.376953 14.873047 L 13.876953 17.373047 C 14.013672 17.509766 14.179688 17.578125 14.375 17.578125 Z " />
|
<PathIcon Data="F1 M 4.023438 13.75 C 3.476562 13.75 2.958984 13.636068 2.470703 13.408203 C 1.982422 13.180339 1.554362 12.875977 1.186523 12.495117 C 0.818685 12.114258 0.528971 11.674805 0.317383 11.176758 C 0.105794 10.678711 0 10.159506 0 9.619141 C 0 9.065756 0.094401 8.535156 0.283203 8.027344 C 0.472005 7.519531 0.737305 7.068686 1.079102 6.674805 C 1.420898 6.280926 1.829427 5.961915 2.304688 5.717773 C 2.779948 5.473634 3.304036 5.338543 3.876953 5.3125 L 4.189453 5.302734 C 4.261067 4.501953 4.453125 3.776043 4.765625 3.125 C 5.078125 2.473959 5.488281 1.915691 5.996094 1.450195 C 6.503906 0.984701 7.097981 0.626629 7.77832 0.375977 C 8.458658 0.125326 9.199219 0 10 0 C 10.800781 0 11.541341 0.126953 12.22168 0.380859 C 12.902018 0.634766 13.496093 0.99284 14.003906 1.455078 C 14.511718 1.917318 14.921874 2.473959 15.234375 3.125 C 15.546874 3.776043 15.738932 4.501953 15.810547 5.302734 L 15.966797 5.302734 C 16.513672 5.302734 17.032877 5.416667 17.524414 5.644531 C 18.015949 5.872396 18.44401 6.176758 18.808594 6.557617 C 19.173176 6.938477 19.462891 7.379558 19.677734 7.880859 C 19.892578 8.382162 20 8.902995 20 9.443359 L 20 9.667969 C 20 9.700521 20 9.736328 20 9.775391 C 20 9.814453 19.996744 9.850261 19.990234 9.882812 C 19.573566 9.375 19.108072 8.932292 18.59375 8.554688 C 18.489582 8.248698 18.346354 7.972006 18.164062 7.724609 C 17.98177 7.477214 17.770182 7.267253 17.529297 7.094727 C 17.28841 6.922201 17.021484 6.788737 16.728516 6.694336 C 16.435547 6.599936 16.129557 6.552735 15.810547 6.552734 C 15.472005 6.552735 15.192057 6.446941 14.970703 6.235352 C 14.749349 6.023764 14.61263 5.755209 14.560547 5.429688 C 14.527994 5.195312 14.49056 4.967448 14.448242 4.746094 C 14.405924 4.52474 14.342447 4.300131 14.257812 4.072266 C 14.088541 3.603516 13.862304 3.193359 13.579102 2.841797 C 13.295897 2.490234 12.970377 2.195639 12.602539 1.958008 C 12.2347 1.720379 11.831055 1.542969 11.391602 1.425781 C 10.952148 1.308594 10.488281 1.25 10 1.25 C 9.511719 1.25 9.047852 1.308594 8.608398 1.425781 C 8.168945 1.542969 7.765299 1.71875 7.397461 1.953125 C 7.029622 2.1875 6.704102 2.480469 6.420898 2.832031 C 6.137695 3.183594 5.911458 3.59375 5.742188 4.0625 C 5.657552 4.290365 5.594075 4.514975 5.551758 4.736328 C 5.50944 4.957683 5.472005 5.188803 5.439453 5.429688 C 5.38737 5.813803 5.236002 6.097006 4.985352 6.279297 C 4.7347 6.46159 4.423828 6.552735 4.052734 6.552734 C 3.655599 6.552735 3.286133 6.635743 2.944336 6.801758 C 2.602539 6.967774 2.306315 7.189129 2.055664 7.46582 C 1.805013 7.742514 1.608073 8.059896 1.464844 8.417969 C 1.321615 8.776042 1.25 9.147136 1.25 9.53125 C 1.25 9.928386 1.321615 10.30599 1.464844 10.664062 C 1.608073 11.022136 1.806641 11.337891 2.060547 11.611328 C 2.314453 11.884766 2.61556 12.101237 2.963867 12.260742 C 3.312174 12.420248 3.691406 12.5 4.101562 12.5 L 7.431641 12.5 C 7.373047 12.708334 7.327474 12.916667 7.294922 13.125 C 7.26237 13.333334 7.236328 13.541667 7.216797 13.75 Z M 8.75 14.375 C 8.75 13.600261 8.898111 12.871094 9.194336 12.1875 C 9.49056 11.503906 9.892578 10.908203 10.400391 10.400391 C 10.908203 9.892578 11.503906 9.490561 12.1875 9.194336 C 12.871093 8.898112 13.60026 8.75 14.375 8.75 C 14.889322 8.75 15.385741 8.816732 15.864258 8.950195 C 16.342773 9.083659 16.790363 9.272461 17.207031 9.516602 C 17.623697 9.760742 18.004557 10.055339 18.349609 10.400391 C 18.69466 10.745443 18.989258 11.126303 19.233398 11.542969 C 19.477539 11.959636 19.66634 12.407227 19.799805 12.885742 C 19.933268 13.364258 20 13.860678 20 14.375 C 20 15.14974 19.851887 15.878906 19.555664 16.5625 C 19.259439 17.246094 18.857422 17.841797 18.349609 18.349609 C 17.841797 18.857422 17.246094 19.259439 16.5625 19.555664 C 15.878906 19.851889 15.149739 20 14.375 20 C 13.59375 20 12.861328 19.853516 12.177734 19.560547 C 11.494141 19.267578 10.898438 18.867188 10.390625 18.359375 C 9.882812 17.851562 9.482422 17.255859 9.189453 16.572266 C 8.896484 15.888672 8.75 15.15625 8.75 14.375 Z M 14.375 17.578125 C 14.570312 17.578125 14.736328 17.509766 14.873047 17.373047 L 17.373047 14.873047 C 17.509766 14.736328 17.578125 14.570312 17.578125 14.375 C 17.578125 14.179688 17.509766 14.013672 17.373047 13.876953 C 17.236328 13.740234 17.070312 13.671875 16.875 13.671875 C 16.679688 13.671875 16.513672 13.740234 16.376953 13.876953 L 15 15.253906 L 15 11.875 C 14.999999 11.705729 14.93815 11.559245 14.814453 11.435547 C 14.690755 11.31185 14.544271 11.25 14.375 11.25 C 14.205729 11.25 14.059244 11.31185 13.935547 11.435547 C 13.811849 11.559245 13.75 11.705729 13.75 11.875 L 13.75 15.253906 L 12.373047 13.876953 C 12.236328 13.740234 12.070312 13.671875 11.875 13.671875 C 11.679688 13.671875 11.513672 13.740234 11.376953 13.876953 C 11.240234 14.013672 11.171875 14.179688 11.171875 14.375 C 11.171875 14.570312 11.240234 14.736328 11.376953 14.873047 L 13.876953 17.373047 C 14.013672 17.509766 14.179688 17.578125 14.375 17.578125 Z " />
|
||||||
@@ -84,14 +71,17 @@
|
|||||||
</controls:SettingsCard>
|
</controls:SettingsCard>
|
||||||
<controls:SettingsCard Header="{x:Bind domain:Translator.SettingsLoadPlaintextLinks_Title}">
|
<controls:SettingsCard Header="{x:Bind domain:Translator.SettingsLoadPlaintextLinks_Title}">
|
||||||
<controls:SettingsCard.HeaderIcon>
|
<controls:SettingsCard.HeaderIcon>
|
||||||
<PathIcon Data="{StaticResource AddLinkPathIcon}" />
|
<PathIcon Data="{StaticResource AddLinkPathIcon}" />
|
||||||
</controls:SettingsCard.HeaderIcon>
|
</controls:SettingsCard.HeaderIcon>
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.RenderPlaintextLinks, Mode=TwoWay}" />
|
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.RenderPlaintextLinks, Mode=TwoWay}" />
|
||||||
</controls:SettingsCard>
|
</controls:SettingsCard>
|
||||||
</controls:SettingsExpander.Items>
|
</controls:SettingsExpander.Items>
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsComposer_Title}" />
|
<TextBlock
|
||||||
|
Margin="0,20,0,0"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind domain:Translator.SettingsComposer_Title}" />
|
||||||
<controls:SettingsExpander
|
<controls:SettingsExpander
|
||||||
Description="{x:Bind domain:Translator.SettingsComposerFontFamily_Description}"
|
Description="{x:Bind domain:Translator.SettingsComposerFontFamily_Description}"
|
||||||
Header="{x:Bind domain:Translator.SettingsComposerFont_Title}"
|
Header="{x:Bind domain:Translator.SettingsComposerFont_Title}"
|
||||||
@@ -132,4 +122,4 @@
|
|||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</abstract:ReadComposePanePageAbstract>
|
</abstract:ReadComposePanePageAbstract>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<Grid>
|
<Grid RowSpacing="41">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsSignatureEnabled, Mode=TwoWay}" />
|
<ToggleSwitch IsOn="{x:Bind ViewModel.IsSignatureEnabled, Mode=TwoWay}" />
|
||||||
</controls1:SettingsCard>
|
</controls1:SettingsCard>
|
||||||
|
|
||||||
<StackPanel Grid.Row="1" Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel Grid.Row="1" Spacing="4">
|
||||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsSignature_SignatureDefaults}" />
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.SettingsSignature_SignatureDefaults}" />
|
||||||
<controls1:SettingsCard
|
<controls1:SettingsCard
|
||||||
Header="{x:Bind domain:Translator.SettingsSignature_ForNewMessages_Title}"
|
Header="{x:Bind domain:Translator.SettingsSignature_ForNewMessages_Title}"
|
||||||
IsActionIconVisible="False"
|
IsActionIconVisible="False"
|
||||||
@@ -100,8 +100,8 @@
|
|||||||
</controls1:SettingsCard>
|
</controls1:SettingsCard>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Row="2" Spacing="{StaticResource SettingsCardSpacing}">
|
<StackPanel Grid.Row="2" Spacing="3">
|
||||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsSignature_Signatures}" />
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.SettingsSignature_Signatures}" />
|
||||||
<controls1:SettingsCard
|
<controls1:SettingsCard
|
||||||
Header="{x:Bind domain:Translator.SettingsSignature_AddCustomSignature_Title}"
|
Header="{x:Bind domain:Translator.SettingsSignature_AddCustomSignature_Title}"
|
||||||
IsActionIconVisible="False"
|
IsActionIconVisible="False"
|
||||||
|
|||||||
@@ -26,13 +26,19 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Assets\EML\eml.png" />
|
<None Remove="Assets\EML\eml.png" />
|
||||||
<None Remove="Assets\NotificationIcons\archive.png" />
|
|
||||||
<None Remove="Assets\NotificationIcons\delete.png" />
|
<None Remove="Assets\NotificationIcons\delete.png" />
|
||||||
<None Remove="Assets\NotificationIcons\dismiss.png" />
|
<None Remove="Assets\NotificationIcons\dismiss.png" />
|
||||||
<None Remove="Assets\NotificationIcons\markread.png" />
|
<None Remove="Assets\NotificationIcons\markread.png" />
|
||||||
<None Remove="Assets\NotificationIcons\profile-dark.png" />
|
<None Remove="Assets\NotificationIcons\profile-dark.png" />
|
||||||
<None Remove="Assets\NotificationIcons\profile-light.png" />
|
<None Remove="Assets\NotificationIcons\profile-light.png" />
|
||||||
<None Remove="Assets\ReleaseNotes\1102.md" />
|
<None Remove="Assets\ReleaseNotes\1102.md" />
|
||||||
|
<None Remove="Assets\Thumbnails\airbnb.com.png" />
|
||||||
|
<None Remove="Assets\Thumbnails\apple.com.png" />
|
||||||
|
<None Remove="Assets\Thumbnails\google.com.png" />
|
||||||
|
<None Remove="Assets\Thumbnails\microsoft.com.png" />
|
||||||
|
<None Remove="Assets\Thumbnails\steampowered.com.png" />
|
||||||
|
<None Remove="Assets\Thumbnails\uber.com.png" />
|
||||||
|
<None Remove="Assets\Thumbnails\youtube.com.png" />
|
||||||
<None Remove="JS\editor.html" />
|
<None Remove="JS\editor.html" />
|
||||||
<None Remove="JS\editor.js" />
|
<None Remove="JS\editor.js" />
|
||||||
<None Remove="JS\global.css" />
|
<None Remove="JS\global.css" />
|
||||||
@@ -45,13 +51,19 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Assets\EML\eml.png" />
|
<Content Include="Assets\EML\eml.png" />
|
||||||
<Content Include="Assets\NotificationIcons\archive.png" />
|
|
||||||
<Content Include="Assets\NotificationIcons\delete.png" />
|
<Content Include="Assets\NotificationIcons\delete.png" />
|
||||||
<Content Include="Assets\NotificationIcons\dismiss.png" />
|
<Content Include="Assets\NotificationIcons\dismiss.png" />
|
||||||
<Content Include="Assets\NotificationIcons\markread.png" />
|
<Content Include="Assets\NotificationIcons\markread.png" />
|
||||||
<Content Include="Assets\NotificationIcons\profile-dark.png" />
|
<Content Include="Assets\NotificationIcons\profile-dark.png" />
|
||||||
<Content Include="Assets\NotificationIcons\profile-light.png" />
|
<Content Include="Assets\NotificationIcons\profile-light.png" />
|
||||||
<Content Include="Assets\ReleaseNotes\1102.md" />
|
<Content Include="Assets\ReleaseNotes\1102.md" />
|
||||||
|
<Content Include="Assets\Thumbnails\airbnb.com.png" />
|
||||||
|
<Content Include="Assets\Thumbnails\apple.com.png" />
|
||||||
|
<Content Include="Assets\Thumbnails\google.com.png" />
|
||||||
|
<Content Include="Assets\Thumbnails\microsoft.com.png" />
|
||||||
|
<Content Include="Assets\Thumbnails\steampowered.com.png" />
|
||||||
|
<Content Include="Assets\Thumbnails\uber.com.png" />
|
||||||
|
<Content Include="Assets\Thumbnails\youtube.com.png" />
|
||||||
<Content Include="JS\editor.html" />
|
<Content Include="JS\editor.html" />
|
||||||
<Content Include="JS\editor.js" />
|
<Content Include="JS\editor.js" />
|
||||||
<Content Include="JS\global.css" />
|
<Content Include="JS\global.css" />
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
namespace Wino.Messaging.UI;
|
|
||||||
public record ThumbnailAdded(string Email): IUIMessage;
|
|
||||||
@@ -79,7 +79,6 @@ public partial class App : Application
|
|||||||
services.AddTransient<INotificationBuilder, NotificationBuilder>();
|
services.AddTransient<INotificationBuilder, NotificationBuilder>();
|
||||||
services.AddTransient<IUnderlyingThemeService, UnderlyingThemeService>();
|
services.AddTransient<IUnderlyingThemeService, UnderlyingThemeService>();
|
||||||
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
|
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
|
||||||
services.AddSingleton<IThumbnailService, ThumbnailService>();
|
|
||||||
|
|
||||||
// Register server message handler factory.
|
// Register server message handler factory.
|
||||||
var serverMessageHandlerFactory = new ServerMessageHandlerFactory();
|
var serverMessageHandlerFactory = new ServerMessageHandlerFactory();
|
||||||
|
|||||||
@@ -34,12 +34,11 @@
|
|||||||
</Resource>
|
</Resource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
<PackageReference Include="gravatar-dotnet" />
|
<PackageReference Include="H.NotifyIcon.Wpf" />
|
||||||
<PackageReference Include="H.NotifyIcon.Wpf" />
|
<PackageReference Include="CommunityToolkit.WinUI.Notifications" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" />
|
<PackageReference Include="Microsoft.Identity.Client" />
|
||||||
<PackageReference Include="Microsoft.Identity.Client" />
|
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Wino.Authentication\Wino.Authentication.csproj" />
|
<ProjectReference Include="..\Wino.Authentication\Wino.Authentication.csproj" />
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Ical.Net.CalendarComponents;
|
||||||
|
using Ical.Net.DataTypes;
|
||||||
using SqlKata;
|
using SqlKata;
|
||||||
|
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;
|
||||||
@@ -51,7 +58,27 @@ public class CalendarService : BaseDatabaseService, ICalendarService
|
|||||||
|
|
||||||
public async Task DeleteCalendarItemAsync(Guid calendarItemId)
|
public async Task DeleteCalendarItemAsync(Guid calendarItemId)
|
||||||
{
|
{
|
||||||
|
var calendarItem = await Connection.GetAsync<CalendarItem>(calendarItemId);
|
||||||
|
|
||||||
|
if (calendarItem == null) return;
|
||||||
|
|
||||||
|
List<CalendarItem> eventsToRemove = new() { calendarItem };
|
||||||
|
|
||||||
|
// In case of parent event, delete all child events as well.
|
||||||
|
if (!string.IsNullOrEmpty(calendarItem.Recurrence))
|
||||||
|
{
|
||||||
|
var recurringEvents = await Connection.Table<CalendarItem>().Where(a => a.RecurringCalendarItemId == calendarItemId).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
eventsToRemove.AddRange(recurringEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var @event in eventsToRemove)
|
||||||
|
{
|
||||||
|
await Connection.Table<CalendarItem>().DeleteAsync(x => x.Id == @event.Id).ConfigureAwait(false);
|
||||||
|
await Connection.Table<CalendarEventAttendee>().DeleteAsync(a => a.CalendarItemId == @event.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarItemDeleted(@event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees)
|
public async Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees)
|
||||||
@@ -71,8 +98,83 @@ public class CalendarService : BaseDatabaseService, ICalendarService
|
|||||||
|
|
||||||
public async Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel)
|
public async Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel)
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO: We might need to implement caching here.
|
||||||
return new List<CalendarItem>();
|
// I don't know how much of the events we'll have in total, but this logic scans all events every time for given calendar.
|
||||||
|
|
||||||
|
var accountEvents = await Connection.Table<CalendarItem>()
|
||||||
|
.Where(x => x.CalendarId == calendar.Id && !x.IsHidden).ToListAsync();
|
||||||
|
|
||||||
|
var result = new List<CalendarItem>();
|
||||||
|
|
||||||
|
foreach (var ev in accountEvents)
|
||||||
|
{
|
||||||
|
ev.AssignedCalendar = calendar;
|
||||||
|
|
||||||
|
// Parse recurrence rules
|
||||||
|
var calendarEvent = new CalendarEvent
|
||||||
|
{
|
||||||
|
Start = new CalDateTime(ev.StartDate),
|
||||||
|
End = new CalDateTime(ev.EndDate),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ev.Recurrence))
|
||||||
|
{
|
||||||
|
// No recurrence, only check if we fall into the given period.
|
||||||
|
|
||||||
|
if (ev.Period.OverlapsWith(dayRangeRenderModel.Period))
|
||||||
|
{
|
||||||
|
result.Add(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This event has recurrences.
|
||||||
|
// Wino stores exceptional recurrent events as a separate calendar item, without the recurrence rule.
|
||||||
|
// Because each instance of recurrent event can have different attendees, properties etc.
|
||||||
|
// Even though the event is recurrent, each updated instance is a separate calendar item.
|
||||||
|
// Calculate the all recurrences, and remove the exceptional instances like hidden ones.
|
||||||
|
|
||||||
|
var recurrenceLines = Regex.Split(ev.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator);
|
||||||
|
|
||||||
|
foreach (var line in recurrenceLines)
|
||||||
|
{
|
||||||
|
calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate occurrences in the range.
|
||||||
|
var occurrences = calendarEvent.GetOccurrences(dayRangeRenderModel.Period.Start, dayRangeRenderModel.Period.End);
|
||||||
|
|
||||||
|
// Get all recurrent exceptional calendar events.
|
||||||
|
var exceptionalRecurrences = await Connection.Table<CalendarItem>()
|
||||||
|
.Where(a => a.RecurringCalendarItemId == ev.Id)
|
||||||
|
.ToListAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var occurrence in occurrences)
|
||||||
|
{
|
||||||
|
var exactInstanceCheck = exceptionalRecurrences.FirstOrDefault(a =>
|
||||||
|
a.Period.OverlapsWith(dayRangeRenderModel.Period));
|
||||||
|
|
||||||
|
if (exactInstanceCheck == null)
|
||||||
|
{
|
||||||
|
// There is no exception for the period.
|
||||||
|
// Change the instance StartDate and Duration.
|
||||||
|
|
||||||
|
var recurrence = ev.CreateRecurrence(occurrence.Period.StartTime.Value, occurrence.Period.EndTime.Value);
|
||||||
|
|
||||||
|
result.Add(recurrence);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There is a single instance of this recurrent event.
|
||||||
|
// It will be added as single item if it's not hidden.
|
||||||
|
// We don't need to do anything here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId)
|
public Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId)
|
||||||
@@ -119,7 +221,7 @@ public class CalendarService : BaseDatabaseService, ICalendarService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<CalendarEventAttendee>> GetAttendeesAsync(Guid calendarEventTrackingId)
|
public Task<List<CalendarEventAttendee>> GetAttendeesAsync(Guid calendarEventTrackingId)
|
||||||
=> Connection.Table<CalendarEventAttendee>().Where(x => x.EventId == calendarEventTrackingId).ToListAsync();
|
=> Connection.Table<CalendarEventAttendee>().Where(x => x.CalendarItemId == calendarEventTrackingId).ToListAsync();
|
||||||
|
|
||||||
public async Task<List<CalendarEventAttendee>> ManageEventAttendeesAsync(Guid calendarItemId, List<CalendarEventAttendee> allAttendees)
|
public async Task<List<CalendarEventAttendee>> ManageEventAttendeesAsync(Guid calendarItemId, List<CalendarEventAttendee> allAttendees)
|
||||||
{
|
{
|
||||||
@@ -128,7 +230,7 @@ public class CalendarService : BaseDatabaseService, ICalendarService
|
|||||||
// Clear all attendees.
|
// Clear all attendees.
|
||||||
var query = new Query()
|
var query = new Query()
|
||||||
.From(nameof(CalendarEventAttendee))
|
.From(nameof(CalendarEventAttendee))
|
||||||
.Where(nameof(CalendarEventAttendee.EventId), calendarItemId)
|
.Where(nameof(CalendarEventAttendee.CalendarItemId), calendarItemId)
|
||||||
.AsDelete();
|
.AsDelete();
|
||||||
|
|
||||||
connection.Execute(query.GetRawQuery());
|
connection.Execute(query.GetRawQuery());
|
||||||
@@ -137,12 +239,67 @@ public class CalendarService : BaseDatabaseService, ICalendarService
|
|||||||
connection.InsertAll(allAttendees);
|
connection.InsertAll(allAttendees);
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Connection.Table<CalendarEventAttendee>().Where(a => a.EventId == calendarItemId).ToListAsync();
|
return await Connection.Table<CalendarEventAttendee>().Where(a => a.CalendarItemId == calendarItemId).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CalendarItem> GetCalendarItemTargetAsync(CalendarItemTarget targetDetails)
|
public async Task<CalendarItem> GetCalendarItemTargetAsync(CalendarItemTarget targetDetails)
|
||||||
{
|
{
|
||||||
// TODO
|
var eventId = targetDetails.Item.Id;
|
||||||
return null;
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ public class DatabaseService : IDatabaseService
|
|||||||
typeof(AccountCalendar),
|
typeof(AccountCalendar),
|
||||||
typeof(CalendarEventAttendee),
|
typeof(CalendarEventAttendee),
|
||||||
typeof(CalendarItem),
|
typeof(CalendarItem),
|
||||||
typeof(Reminder),
|
typeof(Reminder)
|
||||||
typeof(Thumbnail)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ public static class ServicesContainerSetup
|
|||||||
services.AddSingleton<IMimeFileService, MimeFileService>();
|
services.AddSingleton<IMimeFileService, MimeFileService>();
|
||||||
|
|
||||||
services.AddTransient<ICalendarService, CalendarService>();
|
services.AddTransient<ICalendarService, CalendarService>();
|
||||||
services.AddTransient<ICalendarServiceEx, CalendarServiceEx>();
|
|
||||||
services.AddTransient<IMailService, MailService>();
|
services.AddTransient<IMailService, MailService>();
|
||||||
services.AddTransient<IFolderService, FolderService>();
|
services.AddTransient<IFolderService, FolderService>();
|
||||||
services.AddTransient<IAccountService, AccountService>();
|
services.AddTransient<IAccountService, AccountService>();
|
||||||
@@ -30,5 +29,7 @@ public static class ServicesContainerSetup
|
|||||||
services.AddTransient<IOutlookThreadingStrategy, OutlookThreadingStrategy>();
|
services.AddTransient<IOutlookThreadingStrategy, OutlookThreadingStrategy>();
|
||||||
services.AddTransient<IGmailThreadingStrategy, GmailThreadingStrategy>();
|
services.AddTransient<IGmailThreadingStrategy, GmailThreadingStrategy>();
|
||||||
services.AddTransient<IImapThreadingStrategy, ImapThreadingStrategy>();
|
services.AddTransient<IImapThreadingStrategy, ImapThreadingStrategy>();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
289
Wino.sln
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.32112.339
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Mail", "Wino.Mail\Wino.Mail.csproj", "{68A432B8-C1B7-494C-8D6D-230788EA683E}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core", "Wino.Core\Wino.Core.csproj", "{E6B1632A-8901-41E8-9DDF-6793C7698B0B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Core.UWP", "Wino.Core.UWP\Wino.Core.UWP.csproj", "{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core.Domain", "Wino.Core.Domain\Wino.Core.Domain.csproj", "{CF3312E5-5DA0-4867-9945-49EA7598AF1F}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949} = {D4919A19-E70F-4916-83D2-5D5F87BEB949}
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.BackgroundTasks", "Wino.BackgroundTasks\Wino.BackgroundTasks.csproj", "{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Mail.ViewModels", "Wino.Mail.ViewModels\Wino.Mail.ViewModels.csproj", "{D62F1C03-DA57-4709-A640-0283296A8E66}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Messaging", "Wino.Messages\Wino.Messaging.csproj", "{0C307D7E-256F-448C-8265-5622A812FBCC}"
|
||||||
|
EndProject
|
||||||
|
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Wino.Packaging", "Wino.Packaging\Wino.Packaging.wapproj", "{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Server", "Wino.Server\Wino.Server.csproj", "{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.SourceGenerators", "Wino.SourceGenerators\Wino.SourceGenerators.csproj", "{D4919A19-E70F-4916-83D2-5D5F87BEB949}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Core.ViewModels", "Wino.Core.ViewModels\Wino.Core.ViewModels.csproj", "{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Calendar.ViewModels", "Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj", "{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Authentication", "Wino.Authentication\Wino.Authentication.csproj", "{A4DBA01A-F315-49E0-8428-BB99D32B20F9}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Calendar", "Wino.Calendar\Wino.Calendar.csproj", "{600F4979-DB7E-409D-B7DA-B60BE4C55C35}"
|
||||||
|
EndProject
|
||||||
|
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Wino.Calendar.Packaging", "Wino.Calendar.Packaging\Wino.Calendar.Packaging.wapproj", "{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Services", "Wino.Services\Wino.Services.csproj", "{4000A374-59FE-4400-ACF6-D40473BECD73}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|ARM64 = Debug|ARM64
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|ARM64 = Release|ARM64
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|x64.Deploy.0 = Debug|x64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Debug|x86.Deploy.0 = Debug|x86
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|x64.Build.0 = Release|x64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|x64.Deploy.0 = Release|x64
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|x86.Build.0 = Release|x86
|
||||||
|
{68A432B8-C1B7-494C-8D6D-230788EA683E}.Release|x86.Deploy.0 = Release|x86
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Release|x64.Build.0 = Release|x64
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B}.Release|x86.Build.0 = Release|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.Build.0 = Release|x64
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.Build.0 = Release|x86
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Release|x64.Build.0 = Release|x64
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F}.Release|x86.Build.0 = Release|x86
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Release|x64.Build.0 = Release|x64
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08}.Release|x86.Build.0 = Release|x86
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Release|x64.Build.0 = Release|x64
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66}.Release|x86.Build.0 = Release|x86
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Release|x64.Build.0 = Release|x64
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC}.Release|x86.Build.0 = Release|x86
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|x64.Deploy.0 = Debug|x64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Debug|x86.Deploy.0 = Debug|x86
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|x64.Build.0 = Release|x64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|x64.Deploy.0 = Release|x64
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|x86.Build.0 = Release|x86
|
||||||
|
{760F5F31-8EE3-4B83-80F3-0E4FFBCC737C}.Release|x86.Deploy.0 = Release|x86
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Release|x64.Build.0 = Release|x64
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{3D1942E5-1A3B-4062-B4BB-156A40DA47FE}.Release|x86.Build.0 = Release|x86
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Deploy.0 = Debug|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Deploy.0 = Debug|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Build.0 = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Deploy.0 = Release|x64
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Build.0 = Release|x86
|
||||||
|
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Deploy.0 = Release|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Deploy.0 = Debug|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Deploy.0 = Debug|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Build.0 = Release|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Deploy.0 = Release|x64
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Build.0 = Release|x86
|
||||||
|
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Deploy.0 = Release|x86
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{E6B1632A-8901-41E8-9DDF-6793C7698B0B} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{CF3312E5-5DA0-4867-9945-49EA7598AF1F} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{D9EF0F59-F5F2-4D6C-A5BA-84043D8F3E08} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{D62F1C03-DA57-4709-A640-0283296A8E66} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{0C307D7E-256F-448C-8265-5622A812FBCC} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{D4919A19-E70F-4916-83D2-5D5F87BEB949} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{53723AE8-7E7E-4D54-ADAB-0A6033255CC8} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{039AFFA8-C1CC-4E3B-8A31-6814D7557F74} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{A4DBA01A-F315-49E0-8428-BB99D32B20F9} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
{4000A374-59FE-4400-ACF6-D40473BECD73} = {17FF5FAE-F1AC-4572-BAA3-8B86F01EA758}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {721F946E-F69F-4987-823A-D084B436FC1E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||