Event details UI improvements.
This commit is contained in:
@@ -817,15 +817,10 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
|
||||
private IEnumerable<CalendarItemViewModel> GetCalendarItems(CalendarItemViewModel calendarItemViewModel, CalendarDayModel selectedDay)
|
||||
{
|
||||
// All-day and multi-day events are selected collectively.
|
||||
// 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.
|
||||
// Multi-day events, all-day events, and recurring events are rendered across multiple days.
|
||||
// We need to find all instances with the same ID across all visible date ranges.
|
||||
|
||||
if (!calendarItemViewModel.IsRecurringEvent)
|
||||
{
|
||||
return [calendarItemViewModel];
|
||||
}
|
||||
else
|
||||
if (calendarItemViewModel.IsRecurringEvent || calendarItemViewModel.IsMultiDayEvent)
|
||||
{
|
||||
return DayRanges
|
||||
.SelectMany(a => a.CalendarDays)
|
||||
@@ -834,6 +829,11 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
.Cast<CalendarItemViewModel>()
|
||||
.Distinct();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single-day, non-recurring events only appear once
|
||||
return [calendarItemViewModel];
|
||||
}
|
||||
}
|
||||
|
||||
private void UnselectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null)
|
||||
@@ -877,6 +877,13 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
|
||||
Debug.WriteLine($"Calendar item deleted: {calendarItem.Id}");
|
||||
|
||||
// Check if the deleted item is currently displayed in details view
|
||||
if (DisplayDetailsCalendarItemViewModel?.Id == calendarItem.Id)
|
||||
{
|
||||
// Clear the details view since this item was deleted
|
||||
DisplayDetailsCalendarItemViewModel = null;
|
||||
}
|
||||
|
||||
// Remove the event and its occurrences from all visible date ranges.
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
@@ -956,6 +963,47 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
// Check if event falls into the current date range.
|
||||
if (DayRanges.DisplayRange == null) return;
|
||||
|
||||
// Check if this is a server-synced item that matches a local preview
|
||||
// Local previews don't have RemoteEventId, server-synced items do
|
||||
if (!string.IsNullOrEmpty(calendarItem.RemoteEventId))
|
||||
{
|
||||
// Find local preview items that match this event's properties
|
||||
var localPreviewItems = DayRanges
|
||||
.SelectMany(a => a.CalendarDays)
|
||||
.SelectMany(b => b.EventsCollection.RegularEvents.Concat(b.EventsCollection.AllDayEvents))
|
||||
.OfType<CalendarItemViewModel>()
|
||||
.Where(c => c.AssignedCalendar.Id == calendarItem.CalendarId &&
|
||||
c.CalendarItem.IsLocalPreview && // Local preview (no RemoteEventId)
|
||||
c.Title == calendarItem.Title &&
|
||||
Math.Abs((c.StartDate - calendarItem.LocalStartDate).TotalSeconds) < 60 &&
|
||||
Math.Abs(c.DurationInSeconds - calendarItem.DurationInSeconds) < 1)
|
||||
.ToList();
|
||||
|
||||
if (localPreviewItems.Any())
|
||||
{
|
||||
Debug.WriteLine($"Found {localPreviewItems.Count} matching local preview items for {calendarItem.Title}, removing them.");
|
||||
|
||||
// Remove all matching local preview items
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
foreach (var dayRange in DayRanges)
|
||||
{
|
||||
foreach (var calendarDay in dayRange.CalendarDays)
|
||||
{
|
||||
foreach (var localPreview in localPreviewItems)
|
||||
{
|
||||
var itemInDay = calendarDay.EventsCollection.GetCalendarItem(localPreview.Id);
|
||||
if (itemInDay != null)
|
||||
{
|
||||
calendarDay.EventsCollection.RemoveCalendarItem(itemInDay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get all periods from the visible day ranges
|
||||
var visiblePeriods = DayRanges.Select(dr => dr.Period).ToList();
|
||||
|
||||
|
||||
@@ -18,54 +18,51 @@ public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, IC
|
||||
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start date in local time based on the event's timezone.
|
||||
/// The underlying CalendarItem stores dates in UTC.
|
||||
/// Gets or sets the start date converted to user's local timezone for display.
|
||||
/// The underlying CalendarItem stores dates according to their timezone.
|
||||
/// </summary>
|
||||
public DateTime StartDate
|
||||
{
|
||||
get
|
||||
{
|
||||
// Convert from UTC stored in database to local time using the event's timezone
|
||||
var startDateTimeOffset = CalendarItem.StartDateTimeOffset;
|
||||
return startDateTimeOffset.LocalDateTime;
|
||||
// Get start date in user's local timezone
|
||||
return CalendarItem.LocalStartDate;
|
||||
}
|
||||
set
|
||||
{
|
||||
// When setting, convert from local time to UTC for storage
|
||||
// Preserve the timezone information
|
||||
// When setting from UI (in local time), convert to event's timezone for storage
|
||||
if (!string.IsNullOrEmpty(CalendarItem.StartTimeZone))
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(CalendarItem.StartTimeZone);
|
||||
var utcDateTime = TimeZoneInfo.ConvertTimeToUtc(value, timeZoneInfo);
|
||||
CalendarItem.StartDate = utcDateTime;
|
||||
var sourceTimeZone = TimeZoneInfo.Local;
|
||||
var targetTimeZone = TimeZoneInfo.FindSystemTimeZoneById(CalendarItem.StartTimeZone);
|
||||
CalendarItem.StartDate = TimeZoneInfo.ConvertTime(value, sourceTimeZone, targetTimeZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If timezone lookup fails, assume value is already in UTC
|
||||
// If timezone lookup fails, set as-is
|
||||
CalendarItem.StartDate = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No timezone info, assume UTC
|
||||
// No timezone info, set as-is
|
||||
CalendarItem.StartDate = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end date in local time based on the event's timezone.
|
||||
/// The underlying CalendarItem stores dates in UTC.
|
||||
/// Gets the end date converted to user's local timezone for display.
|
||||
/// The underlying CalendarItem stores dates according to their timezone.
|
||||
/// </summary>
|
||||
public DateTime EndDate
|
||||
{
|
||||
get
|
||||
{
|
||||
// Convert from UTC stored in database to local time using the event's timezone
|
||||
var endDateTimeOffset = CalendarItem.EndDateTimeOffset;
|
||||
return endDateTimeOffset.LocalDateTime;
|
||||
// Get end date in user's local timezone
|
||||
return CalendarItem.LocalEndDate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +97,4 @@ public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, IC
|
||||
}
|
||||
|
||||
public override string ToString() => CalendarItem.Title;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
@@ -19,6 +21,9 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||
private readonly ICalendarService _calendarService;
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
private readonly IMailDialogService _dialogService;
|
||||
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
||||
private readonly INavigationService _navigationService;
|
||||
|
||||
public CalendarSettings CurrentSettings { get; }
|
||||
|
||||
@@ -26,20 +31,55 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
||||
[NotifyPropertyChangedFor(nameof(CanEditSeries))]
|
||||
private CalendarItemViewModel _currentEvent;
|
||||
|
||||
[ObservableProperty]
|
||||
private CalendarItemViewModel _seriesParent;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the event is part of a recurring series (as a child occurrence).
|
||||
/// Used to enable "View Series" functionality.
|
||||
/// </summary>
|
||||
public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the "Edit Series" button should be visible.
|
||||
/// Only visible for child occurrences of recurring events, not for master events or single events.
|
||||
/// </summary>
|
||||
public bool CanEditSeries => CurrentEvent?.IsRecurringChild ?? false;
|
||||
|
||||
#endregion
|
||||
|
||||
public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService)
|
||||
#region Show As Options
|
||||
|
||||
public List<CalendarItemShowAs> ShowAsOptions { get; } =
|
||||
[
|
||||
CalendarItemShowAs.Free,
|
||||
CalendarItemShowAs.Tentative,
|
||||
CalendarItemShowAs.Busy,
|
||||
CalendarItemShowAs.OutOfOffice,
|
||||
CalendarItemShowAs.WorkingElsewhere
|
||||
];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial CalendarItemShowAs SelectedShowAs { get; set; } = CalendarItemShowAs.Busy;
|
||||
|
||||
#endregion
|
||||
|
||||
public EventDetailsPageViewModel(ICalendarService calendarService,
|
||||
INativeAppService nativeAppService,
|
||||
IPreferencesService preferencesService,
|
||||
IMailDialogService dialogService,
|
||||
IWinoRequestDelegator winoRequestDelegator,
|
||||
INavigationService navigationService)
|
||||
{
|
||||
_calendarService = calendarService;
|
||||
_nativeAppService = nativeAppService;
|
||||
_preferencesService = preferencesService;
|
||||
_dialogService = dialogService;
|
||||
_winoRequestDelegator = winoRequestDelegator;
|
||||
_navigationService = navigationService;
|
||||
|
||||
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
||||
}
|
||||
@@ -90,26 +130,57 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||
[RelayCommand]
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
|
||||
// TODO: Implement saving
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
if (CurrentEvent == null) return;
|
||||
|
||||
// If the event is a master recurring event, ask for confirmation
|
||||
if (CurrentEvent.IsRecurringParent)
|
||||
{
|
||||
var confirmed = await _dialogService.ShowConfirmationDialogAsync(
|
||||
Translator.DialogMessage_DeleteRecurringSeriesMessage,
|
||||
Translator.DialogMessage_DeleteRecurringSeriesTitle,
|
||||
Translator.Buttons_Delete);
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var preparationRequest = new CalendarOperationPreparationRequest(
|
||||
CalendarSynchronizerOperation.DeleteEvent,
|
||||
CurrentEvent.CalendarItem,
|
||||
null);
|
||||
|
||||
await _winoRequestDelegator.ExecuteAsync(preparationRequest);
|
||||
|
||||
// Navigate back after successful deletion
|
||||
_navigationService.GoBack();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error deleting calendar event: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task JoinOnline()
|
||||
private Task JoinOnlineAsync()
|
||||
{
|
||||
if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask;
|
||||
if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink))
|
||||
return Task.CompletedTask;
|
||||
|
||||
return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Respond(CalendarItemStatus status)
|
||||
private async Task RespondAsync(CalendarItemStatus status)
|
||||
{
|
||||
if (CurrentEvent == null) return;
|
||||
|
||||
// TODO: Implement response
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user