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
@@ -60,29 +60,30 @@ public partial class CalendarAccountSettingsPageViewModel : CalendarBaseViewMode
{
base.OnNavigatedTo(mode, parameters);
if (parameters is not Guid accountId)
if (parameters is AccountCalendar selectedCalendar)
{
Account = await _accountService.GetAccountAsync(selectedCalendar.AccountId);
AccountCalendar = await _calendarService.GetAccountCalendarAsync(selectedCalendar.Id) ?? selectedCalendar;
}
else if (parameters is Guid accountId)
{
Account = await _accountService.GetAccountAsync(accountId);
var calendars = await _calendarService.GetAccountCalendarsAsync(accountId);
AccountCalendar = calendars.FirstOrDefault(c => c.IsPrimary) ?? calendars.FirstOrDefault();
}
else
{
return;
}
// Load account
Account = await _accountService.GetAccountAsync(accountId);
if (Account == null)
return;
// Load first primary calendar for this account
var calendars = await _calendarService.GetAccountCalendarsAsync(accountId);
AccountCalendar = calendars.FirstOrDefault(c => c.IsPrimary) ?? calendars.FirstOrDefault();
if (AccountCalendar == null)
if (Account == null || AccountCalendar == null)
return;
// Initialize properties from AccountCalendar
AccountColorHex = AccountCalendar.BackgroundColorHex ?? "#0078D4";
IsSyncEnabled = AccountCalendar.IsExtended;
IsSyncEnabled = AccountCalendar.IsSynchronizationEnabled;
IsPrimaryCalendar = AccountCalendar.IsPrimary;
// TODO: Default ShowAs is not stored in AccountCalendar yet, defaulting to Busy
SelectedDefaultShowAsOption = ShowAsOptions[2]; // Busy
SelectedDefaultShowAsOption = ShowAsOptions.FirstOrDefault(o => o.ShowAs == AccountCalendar.DefaultShowAs) ?? ShowAsOptions[2];
}
partial void OnAccountColorHexChanged(string value)
@@ -98,7 +99,7 @@ public partial class CalendarAccountSettingsPageViewModel : CalendarBaseViewMode
{
if (AccountCalendar != null)
{
AccountCalendar.IsExtended = value;
AccountCalendar.IsSynchronizationEnabled = value;
SaveChangesAsync();
}
}
@@ -114,11 +115,10 @@ public partial class CalendarAccountSettingsPageViewModel : CalendarBaseViewMode
partial void OnSelectedDefaultShowAsOptionChanged(ShowAsOption value)
{
// TODO: Default ShowAs should be stored in AccountCalendar or account preferences
// For now, this is just a placeholder as the property doesn't exist yet
if (value != null)
if (AccountCalendar != null && value != null)
{
// Future: Store value.ShowAs somewhere
AccountCalendar.DefaultShowAs = value.ShowAs;
SaveChangesAsync();
}
}
@@ -286,6 +286,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
Description = string.Empty,
Location = Location ?? string.Empty,
Title = EventName,
ShowAs = SelectedQuickEventAccountCalendar.DefaultShowAs,
IsHidden = false,
AssignedCalendar = SelectedQuickEventAccountCalendar
};
@@ -907,9 +908,9 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
});
}
protected override async void OnCalendarItemUpdated(CalendarItem calendarItem)
protected override async void OnCalendarItemUpdated(CalendarItem calendarItem, CalendarItemUpdateSource source)
{
base.OnCalendarItemUpdated(calendarItem);
base.OnCalendarItemUpdated(calendarItem, source);
Debug.WriteLine($"Calendar item updated: {calendarItem.Id}");
// Series master events should not be visible on the UI.
@@ -2,6 +2,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Calendar.ViewModels.Data;
@@ -54,6 +55,12 @@ public partial class AccountCalendarViewModel : ObservableObject, IAccountCalend
set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i);
}
public bool IsSynchronizationEnabled
{
get => AccountCalendar.IsSynchronizationEnabled;
set => SetProperty(AccountCalendar.IsSynchronizationEnabled, value, AccountCalendar, (u, i) => u.IsSynchronizationEnabled = i);
}
public Guid AccountId
{
get => AccountCalendar.AccountId;
@@ -65,6 +72,12 @@ public partial class AccountCalendarViewModel : ObservableObject, IAccountCalend
get => AccountCalendar.RemoteCalendarId;
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
}
public CalendarItemShowAs DefaultShowAs
{
get => AccountCalendar.DefaultShowAs;
set => SetProperty(AccountCalendar.DefaultShowAs, value, AccountCalendar, (u, s) => u.DefaultShowAs = s);
}
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
public MailAccount MailAccount { get => MailAccount; set => MailAccount = value; }
}
@@ -105,6 +105,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
public bool IncludeRsvpMessage => !string.IsNullOrEmpty(RsvpMessage);
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IncludeRsvpMessage))]
public partial string RsvpMessage { get; set; } = string.Empty;
public ObservableCollection<RsvpStatusOption> RsvpStatusOptions { get; } = new ObservableCollection<RsvpStatusOption>();
@@ -129,7 +130,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
CalendarItemStatus.Tentative => Translator.CalendarEventResponse_TentativeResponse,
CalendarItemStatus.Cancelled => Translator.CalendarEventResponse_DeclinedResponse,
CalendarItemStatus.NotResponded => Translator.CalendarEventResponse_NotResponded,
_ => throw new NotImplementedException()
_ => Translator.CalendarEventResponse_NotResponded
};
}
}
@@ -179,19 +180,33 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
await LoadCalendarItemTargetAsync(args);
}
protected override async void OnCalendarItemUpdated(CalendarItem calendarItem)
protected override async void OnCalendarItemUpdated(CalendarItem calendarItem, CalendarItemUpdateSource source)
{
base.OnCalendarItemUpdated(calendarItem);
base.OnCalendarItemUpdated(calendarItem, source);
// If the current event was updated, reload it
if (CurrentEvent?.CalendarItem?.Id == calendarItem.Id || CurrentEvent?.CalendarItem.RecurringCalendarItemId == calendarItem.Id)
{
// Refresh the current event data by reloading from service
// Reflect client-side optimistic changes immediately; fallback to DB for server updates.
if (source == CalendarItemUpdateSource.ClientUpdated || source == CalendarItemUpdateSource.ClientReverted)
{
var previousAttendees = CurrentEvent?.Attendees?.ToList() ?? [];
CurrentEvent = new CalendarItemViewModel(calendarItem);
foreach (var attendee in previousAttendees)
{
CurrentEvent.Attendees.Add(attendee);
}
return;
}
// Refresh from DB when update comes from server sync.
var refreshedEvent = await _calendarService.GetCalendarItemAsync(calendarItem.Id);
if (refreshedEvent != null)
{
CurrentEvent = new CalendarItemViewModel(refreshedEvent);
await LoadAttendeesAsync(refreshedEvent.EventTrackingId, refreshedEvent);
await LoadAttendeesAsync(refreshedEvent.Id, refreshedEvent);
}
}
}
@@ -218,17 +233,17 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
CurrentEvent = new CalendarItemViewModel(currentEventItem);
await LoadAttendeesAsync(currentEventItem.EventTrackingId, currentEventItem);
await LoadAttendeesAsync(currentEventItem.Id, currentEventItem);
// Initialize SelectedShowAsOption based on current event's ShowAs
SelectedShowAsOption = ShowAsOptions.FirstOrDefault(o => o.ShowAs == currentEventItem.ShowAs) ?? ShowAsOptions[2];
// Load reminders for this calendar item
Reminders = await _calendarService.GetRemindersAsync(currentEventItem.EventTrackingId);
Reminders = await _calendarService.GetRemindersAsync(currentEventItem.Id);
InitializeReminderOptions();
// Load attachments
await LoadAttachmentsAsync(currentEventItem.EventTrackingId);
await LoadAttachmentsAsync(currentEventItem.Id);
}
catch (Exception ex)
{
@@ -236,11 +251,11 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
}
}
private async Task LoadAttendeesAsync(Guid eventTrackingId, CalendarItem calendarItem)
private async Task LoadAttendeesAsync(Guid calendarItemId, CalendarItem calendarItem)
{
CurrentEvent.Attendees.Clear();
var attendees = await _calendarService.GetAttendeesAsync(eventTrackingId);
var attendees = await _calendarService.GetAttendeesAsync(calendarItemId);
// Separate organizer from other attendees to ensure organizer is always first
var organizer = attendees.FirstOrDefault(a => a.IsOrganizer);
@@ -348,7 +363,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
{
// Capture original state BEFORE making any changes for potential revert
var originalItem = await _calendarService.GetCalendarItemAsync(CurrentEvent.CalendarItem.Id);
var originalAttendees = await _calendarService.GetAttendeesAsync(CurrentEvent.CalendarItem.EventTrackingId);
var originalAttendees = await _calendarService.GetAttendeesAsync(CurrentEvent.CalendarItem.Id);
// Get selected reminder options
var selectedOptions = ReminderOptions.Where(o => o.IsSelected).ToList();
@@ -370,7 +385,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
}
// Save reminders to database
await _calendarService.SaveRemindersAsync(CurrentEvent.CalendarItem.EventTrackingId, newReminders);
await _calendarService.SaveRemindersAsync(CurrentEvent.CalendarItem.Id, newReminders);
Reminders = newReminders;
// Update ShowAs if changed
@@ -499,7 +514,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
await _winoRequestDelegator.ExecuteAsync(preparationRequest);
// Reload attendees to get the updated status from the server
await LoadAttendeesAsync(CurrentEvent.CalendarItem.EventTrackingId, CurrentEvent.CalendarItem);
await LoadAttendeesAsync(CurrentEvent.CalendarItem.Id, CurrentEvent.CalendarItem);
OnPropertyChanged(nameof(CurrentRsvpText));
OnPropertyChanged(nameof(CurrentRsvpStatus));