Event details page improvements, calendar item update source.

This commit is contained in:
Burak Kaan Köse
2026-02-12 18:04:29 +01:00
parent 96dcdc8e03
commit b01fa4e4ba
26 changed files with 471 additions and 102 deletions
@@ -141,6 +141,7 @@ public static class GoogleIntegratorExtensions
Id = Guid.NewGuid(),
TimeZone = calendarListEntry.TimeZone,
IsPrimary = calendarListEntry.Primary.GetValueOrDefault(),
IsSynchronizationEnabled = true,
};
// Bg color must present. Generate one if doesnt exists.
@@ -183,6 +183,7 @@ public static class OutlookIntegratorExtensions
RemoteCalendarId = outlookCalendar.Id,
IsPrimary = outlookCalendar.IsDefaultCalendar.GetValueOrDefault(),
Name = outlookCalendar.Name,
IsSynchronizationEnabled = true,
IsExtended = true,
};
@@ -27,13 +27,13 @@ public record AcceptEventRequest(CalendarItem Item, string ResponseMessage = nul
Item.Status = CalendarItemStatus.Accepted;
// Notify UI that the event status was updated
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientUpdated));
}
public override void RevertUIChanges()
{
// If acceptance fails, revert to the previous status
Item.Status = _previousStatus;
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientReverted));
}
}
@@ -27,13 +27,13 @@ public record DeclineEventRequest(CalendarItem Item, string ResponseMessage = nu
Item.Status = CalendarItemStatus.Cancelled;
// Notify UI that the event status was updated
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientUpdated));
}
public override void RevertUIChanges()
{
// If decline fails, revert to the previous status
Item.Status = _previousStatus;
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientReverted));
}
}
@@ -27,13 +27,13 @@ public record TentativeEventRequest(CalendarItem Item, string ResponseMessage =
Item.Status = CalendarItemStatus.Tentative;
// Notify UI that the event status was updated
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientUpdated));
}
public override void RevertUIChanges()
{
// If tentative acceptance fails, revert to the previous status
Item.Status = _previousStatus;
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientReverted));
}
}
@@ -33,7 +33,7 @@ public record UpdateCalendarEventRequest(CalendarItem Item, List<CalendarEventAt
public override void ApplyUIChanges()
{
// Notify UI that the event was updated locally
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientUpdated));
}
public override void RevertUIChanges()
@@ -42,12 +42,12 @@ public record UpdateCalendarEventRequest(CalendarItem Item, List<CalendarEventAt
if (OriginalItem != null && OriginalAttendees != null)
{
// Send the original item back to restore UI state
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(OriginalItem));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(OriginalItem, CalendarItemUpdateSource.ClientReverted));
}
else
{
// Fallback: just notify with current item to trigger refresh
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(Item, CalendarItemUpdateSource.ClientReverted));
}
}
}
+44 -7
View File
@@ -484,7 +484,9 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
_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))
.Where(c => c.IsSynchronizationEnabled)
.ToList();
// TODO: Better logging and exception handling.
foreach (var calendar in localCalendars)
@@ -566,6 +568,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
}
var localCalendars = await _gmailChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
var remotePrimaryCalendarId = GetPrimaryCalendarId(calendarListResponse.Items);
List<AccountCalendar> insertedCalendars = new();
List<AccountCalendar> updatedCalendars = new();
@@ -596,14 +599,21 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
{
// Insert new calendar.
var localCalendar = calendar.AsCalendar(Account.Id);
localCalendar.IsPrimary = string.Equals(localCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
insertedCalendars.Add(localCalendar);
}
else
{
// Update existing calendar. Right now we only update the name.
if (ShouldUpdateCalendar(calendar, existingLocalCalendar))
if (ShouldUpdateCalendar(calendar, existingLocalCalendar, remotePrimaryCalendarId))
{
existingLocalCalendar.Name = calendar.Summary;
existingLocalCalendar.TimeZone = calendar.TimeZone;
if (!string.IsNullOrEmpty(calendar.BackgroundColor))
existingLocalCalendar.BackgroundColorHex = calendar.BackgroundColor;
if (!string.IsNullOrEmpty(calendar.ForegroundColor))
existingLocalCalendar.TextColorHex = calendar.ForegroundColor;
existingLocalCalendar.IsPrimary = string.Equals(existingLocalCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
updatedCalendars.Add(existingLocalCalendar);
}
@@ -770,14 +780,41 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
}
}
private bool ShouldUpdateCalendar(CalendarListEntry calendarListEntry, AccountCalendar accountCalendar)
private bool ShouldUpdateCalendar(CalendarListEntry calendarListEntry, AccountCalendar accountCalendar, string remotePrimaryCalendarId)
{
// TODO: Only calendar name is updated for now. We can add more checks here.
var remoteCalendarName = calendarListEntry.Summary;
var localCalendarName = accountCalendar.Name;
var remoteTimeZone = calendarListEntry.TimeZone;
var remoteBackgroundColor = string.IsNullOrEmpty(calendarListEntry.BackgroundColor) ? accountCalendar.BackgroundColorHex : calendarListEntry.BackgroundColor;
var remoteTextColor = string.IsNullOrEmpty(calendarListEntry.ForegroundColor) ? accountCalendar.TextColorHex : calendarListEntry.ForegroundColor;
var remoteIsPrimary = string.Equals(calendarListEntry.Id, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
return !localCalendarName.Equals(remoteCalendarName, StringComparison.OrdinalIgnoreCase);
bool isNameChanged = !string.Equals(accountCalendar.Name, remoteCalendarName, StringComparison.OrdinalIgnoreCase);
bool isTimeZoneChanged = !string.Equals(accountCalendar.TimeZone, remoteTimeZone, StringComparison.OrdinalIgnoreCase);
bool isBackgroundColorChanged = !string.Equals(accountCalendar.BackgroundColorHex, remoteBackgroundColor, StringComparison.OrdinalIgnoreCase);
bool isTextColorChanged = !string.Equals(accountCalendar.TextColorHex, remoteTextColor, StringComparison.OrdinalIgnoreCase);
bool isPrimaryChanged = accountCalendar.IsPrimary != remoteIsPrimary;
return isNameChanged || isTimeZoneChanged || isBackgroundColorChanged || isTextColorChanged || isPrimaryChanged;
}
private string GetPrimaryCalendarId(IList<CalendarListEntry> remoteCalendars)
{
if (remoteCalendars == null || remoteCalendars.Count == 0)
return string.Empty;
var explicitPrimary = remoteCalendars.FirstOrDefault(c => c.Primary.GetValueOrDefault());
if (explicitPrimary != null)
return explicitPrimary.Id;
var byPrimaryKeyword = remoteCalendars.FirstOrDefault(c => string.Equals(c.Id, "primary", StringComparison.OrdinalIgnoreCase));
if (byPrimaryKeyword != null)
return byPrimaryKeyword.Id;
var byAccountAddress = remoteCalendars.FirstOrDefault(c => string.Equals(c.Id, Account.Address, StringComparison.OrdinalIgnoreCase));
if (byAccountAddress != null)
return byAccountAddress.Id;
return remoteCalendars.First().Id;
}
private bool ShouldUpdateFolder(Label remoteFolder, MailItemFolder existingLocalFolder)
+40 -7
View File
@@ -1961,7 +1961,9 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false);
var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
var localCalendars = (await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false))
.Where(c => c.IsSynchronizationEnabled)
.ToList();
Microsoft.Graph.Me.Calendars.Item.CalendarView.Delta.DeltaGetResponse eventsDeltaResponse = null;
@@ -2078,6 +2080,7 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
private async Task SynchronizeCalendarsAsync(CancellationToken cancellationToken = default)
{
var calendars = await _graphClient.Me.Calendars.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
var remotePrimaryCalendarId = await GetPrimaryCalendarIdAsync(calendars.Value, cancellationToken).ConfigureAwait(false);
var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
@@ -2110,14 +2113,18 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
{
// Insert new calendar.
var localCalendar = calendar.AsCalendar(Account);
localCalendar.IsPrimary = string.Equals(localCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
insertedCalendars.Add(localCalendar);
}
else
{
// Update existing calendar. Right now we only update the name.
if (ShouldUpdateCalendar(calendar, existingLocalCalendar))
if (ShouldUpdateCalendar(calendar, existingLocalCalendar, remotePrimaryCalendarId))
{
existingLocalCalendar.Name = calendar.Name;
existingLocalCalendar.IsPrimary = string.Equals(existingLocalCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
if (!string.IsNullOrEmpty(calendar.HexColor))
existingLocalCalendar.BackgroundColorHex = calendar.HexColor;
updatedCalendars.Add(existingLocalCalendar);
}
@@ -2147,14 +2154,40 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
}
}
private bool ShouldUpdateCalendar(Calendar calendar, AccountCalendar accountCalendar)
private bool ShouldUpdateCalendar(Calendar calendar, AccountCalendar accountCalendar, string remotePrimaryCalendarId)
{
// TODO: Only calendar name is updated for now. We can add more checks here.
var remoteCalendarName = calendar.Name;
var localCalendarName = accountCalendar.Name;
var remoteBackgroundColor = string.IsNullOrEmpty(calendar.HexColor) ? accountCalendar.BackgroundColorHex : calendar.HexColor;
var remoteIsPrimary = string.Equals(calendar.Id, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
return !localCalendarName.Equals(remoteCalendarName, StringComparison.OrdinalIgnoreCase);
bool isNameChanged = !string.Equals(accountCalendar.Name, remoteCalendarName, StringComparison.OrdinalIgnoreCase);
bool isBackgroundColorChanged = !string.Equals(accountCalendar.BackgroundColorHex, remoteBackgroundColor, StringComparison.OrdinalIgnoreCase);
bool isPrimaryChanged = accountCalendar.IsPrimary != remoteIsPrimary;
return isNameChanged || isBackgroundColorChanged || isPrimaryChanged;
}
private async Task<string> GetPrimaryCalendarIdAsync(IList<Calendar> remoteCalendars, CancellationToken cancellationToken)
{
if (remoteCalendars == null || remoteCalendars.Count == 0)
return string.Empty;
var explicitPrimary = remoteCalendars.FirstOrDefault(c => c.IsDefaultCalendar.GetValueOrDefault());
if (explicitPrimary != null)
return explicitPrimary.Id;
try
{
var meCalendar = await _graphClient.Me.Calendar.GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(meCalendar?.Id))
return meCalendar.Id;
}
catch (Exception ex)
{
_logger.Warning(ex, "Failed to fetch default Outlook calendar for {Name}. Falling back to first available calendar.", Account.Name);
}
return remoteCalendars.First().Id;
}
#region Calendar Operations