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));
+195 -28
View File
@@ -6,9 +6,12 @@
xmlns:abstract="using:Wino.Calendar.Views.Abstract"
xmlns:calendar="using:Wino.Core.Domain.Entities.Calendar"
xmlns:calendarHelpers="using:Wino.Calendar.Helpers"
xmlns:calendarViewModels="using:Wino.Calendar.ViewModels"
xmlns:coreControls="using:Wino.Core.UWP.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:data="using:Wino.Calendar.ViewModels.Data"
xmlns:domain="using:Wino.Core.Domain"
xmlns:enums="using:Wino.Core.Domain.Enums"
xmlns:helpers="using:Wino.Helpers"
xmlns:local="using:Wino.Calendar.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -43,6 +46,7 @@
</Page.Resources>
<Grid Padding="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@@ -60,16 +64,16 @@
DefaultLabelPosition="Right"
IsSticky="True"
OverflowButtonVisibility="Auto">
<AppBarToggleButton
x:Name="ReadOnlyToggle"
Content="Read-only event"
IsChecked="True" />
<AppBarButton Label="{x:Bind domain:Translator.Buttons_Save}">
<AppBarButton
Command="{x:Bind ViewModel.SaveCommand}"
Label="{x:Bind domain:Translator.Buttons_Save}">
<AppBarButton.Icon>
<coreControls:WinoFontIcon Icon="Save" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="{x:Bind domain:Translator.Buttons_Delete}">
<AppBarButton
Command="{x:Bind ViewModel.DeleteCommand}"
Label="{x:Bind domain:Translator.Buttons_Delete}">
<AppBarButton.Icon>
<coreControls:WinoFontIcon Icon="Delete" />
</AppBarButton.Icon>
@@ -78,7 +82,9 @@
<AppBarSeparator />
<!-- Join Online -->
<AppBarButton Label="Join Online">
<AppBarButton
Command="{x:Bind ViewModel.JoinOnlineCommand}"
Label="Join Online">
<AppBarButton.Icon>
<coreControls:WinoFontIcon Icon="EventJoinOnline" />
</AppBarButton.Icon>
@@ -86,27 +92,37 @@
<AppBarSeparator />
<!-- Join Options -->
<AppBarButton Label="Accept">
<!-- RSVP Actions -->
<AppBarButton
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
CommandParameter="{x:Bind enums:AttendeeStatus.Accepted}"
Label="Accept">
<AppBarButton.Icon>
<coreControls:WinoFontIcon Foreground="#527257" Icon="EventAccept" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="Tentative">
<AppBarButton
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
CommandParameter="{x:Bind enums:AttendeeStatus.Tentative}"
Label="Tentative">
<AppBarButton.Icon>
<coreControls:WinoFontIcon Foreground="#805682" Icon="EventTentative" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="Decline">
<AppBarButton
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
CommandParameter="{x:Bind enums:AttendeeStatus.Declined}"
Label="Decline">
<AppBarButton.Icon>
<PathIcon Data="F1 M 10.253906 9.375 L 16.064453 15.185547 C 16.18815 15.309245 16.25 15.455729 16.25 15.625 C 16.25 15.794271 16.18815 15.940756 16.064453 16.064453 C 15.940754 16.188152 15.79427 16.25 15.625 16.25 C 15.455729 16.25 15.309244 16.188152 15.185547 16.064453 L 9.375 10.253906 L 3.564453 16.064453 C 3.440755 16.188152 3.294271 16.25 3.125 16.25 C 2.955729 16.25 2.809245 16.188152 2.685547 16.064453 C 2.561849 15.940756 2.5 15.794271 2.5 15.625 C 2.5 15.455729 2.561849 15.309245 2.685547 15.185547 L 8.496094 9.375 L 2.685547 3.564453 C 2.561849 3.440756 2.5 3.294271 2.5 3.125 C 2.5 2.95573 2.561849 2.809246 2.685547 2.685547 C 2.809245 2.56185 2.955729 2.5 3.125 2.5 C 3.294271 2.5 3.440755 2.56185 3.564453 2.685547 L 9.375 8.496094 L 15.185547 2.685547 C 15.309244 2.56185 15.455729 2.5 15.625 2.5 C 15.79427 2.5 15.940754 2.56185 16.064453 2.685547 C 16.18815 2.809246 16.25 2.95573 16.25 3.125 C 16.25 3.294271 16.18815 3.440756 16.064453 3.564453 Z " Foreground="#d94b4e" />
</AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="Respond">
<AppBarButton
Command="{x:Bind ViewModel.ToggleRsvpPanelCommand}"
Label="{x:Bind ViewModel.CurrentRsvpText, Mode=OneWay}">
<AppBarButton.Icon>
<coreControls:WinoFontIcon Foreground="#805682" Icon="EventRespond" />
</AppBarButton.Icon>
@@ -118,7 +134,11 @@
<AppBarElementContainer>
<StackPanel Style="{StaticResource ActionBarElementContainerStackStyle}">
<TextBlock VerticalAlignment="Center" Text="Show as" />
<ComboBox Width="150" />
<ComboBox
Width="150"
DisplayMemberPath="DisplayText"
ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
SelectedItem="{x:Bind ViewModel.SelectedShowAsOption, Mode=TwoWay}" />
</StackPanel>
</AppBarElementContainer>
@@ -127,14 +147,33 @@
<StackPanel Style="{StaticResource ActionBarElementContainerStackStyle}">
<coreControls:WinoFontIcon FontSize="16" Icon="Reminder" />
<TextBlock VerticalAlignment="Center" Text="Reminder" />
<ComboBox Width="150" />
<Button Content="Set">
<Button.Flyout>
<Flyout>
<ListView
Width="200"
MaxHeight="300"
ItemsSource="{x:Bind ViewModel.ReminderOptions}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="calendarViewModels:ReminderOption">
<CheckBox Content="{x:Bind DisplayText}" IsChecked="{x:Bind IsSelected, Mode=TwoWay}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</AppBarElementContainer>
<AppBarSeparator />
<!-- Edit Series -->
<AppBarButton Label="Edit Series">
<AppBarButton
Command="{x:Bind ViewModel.ViewSeriesCommand}"
Label="Edit Series"
Visibility="{x:Bind ViewModel.CanEditSeries, Mode=OneWay}">
<AppBarButton.Icon>
<coreControls:WinoFontIcon Icon="EventEditSeries" />
</AppBarButton.Icon>
@@ -142,8 +181,51 @@
</CommandBar>
</Border>
<!-- RSVP Panel -->
<Border
Grid.Row="1"
Margin="0,8,0,0"
Padding="12"
Background="{ThemeResource WinoContentZoneBackgroud}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="7"
Visibility="{x:Bind ViewModel.IsRsvpPanelVisible, Mode=OneWay}">
<Grid RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
CommandParameter="{x:Bind enums:AttendeeStatus.Accepted}"
Content="Accept" />
<Button
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
CommandParameter="{x:Bind enums:AttendeeStatus.Tentative}"
Content="Tentative" />
<Button
Command="{x:Bind ViewModel.SendRsvpResponseCommand}"
CommandParameter="{x:Bind enums:AttendeeStatus.Declined}"
Content="Decline" />
<Button
Command="{x:Bind ViewModel.CloseRsvpPanelCommand}"
Content="Close" />
</StackPanel>
<TextBox
Grid.Row="1"
AcceptsReturn="True"
PlaceholderText="Add a message (optional)"
Text="{x:Bind ViewModel.RsvpMessage, Mode=TwoWay}"
TextWrapping="Wrap" />
</Grid>
</Border>
<!-- Event details -->
<ScrollViewer Grid.Row="1">
<ScrollViewer Grid.Row="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
@@ -170,8 +252,7 @@
<!-- Read-Only Event -->
<Grid
x:Name="ReadOnlyDetailsGrid"
RowSpacing="6"
Visibility="{x:Bind ReadOnlyToggle.IsChecked, Mode=OneWay}">
RowSpacing="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="16" />
@@ -195,7 +276,7 @@
<Grid
Grid.Row="3"
ColumnSpacing="6"
Visibility="{x:Bind ViewModel.CurrentEvent.IsRecurringEvent}">
Visibility="{x:Bind ViewModel.CurrentEvent.IsRecurringEvent, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
@@ -208,12 +289,6 @@
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetRecurrenceString(ViewModel.CurrentEvent), Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
</Grid>
<!-- Editable Event -->
<Grid Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(ReadOnlyDetailsGrid.Visibility), Mode=OneWay}">
<TextBlock Text="editing" />
</Grid>
</Grid>
</Grid>
@@ -238,7 +313,8 @@
<AutoSuggestBox
Margin="6,0"
BorderThickness="0"
PlaceholderText="Invite someone" />
PlaceholderText="Invite someone"
Visibility="{x:Bind ViewModel.IsCurrentUserOrganizer, Mode=OneWay}" />
<ListView
Grid.Row="1"
@@ -251,6 +327,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<PersonPicture
@@ -285,6 +362,19 @@
Text="{x:Bind domain:Translator.CalendarEventDetails_Organizer}" />
</Border>
</Grid>
<Border
Grid.Column="2"
Padding="6,2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Background="{ThemeResource CardStrokeColorDefaultBrush}"
CornerRadius="4">
<TextBlock
FontSize="11"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind AttendenceStatus}" />
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
@@ -296,7 +386,8 @@
<Grid
x:Name="AttachmentsGrid"
Grid.Column="2"
Style="{StaticResource EventDetailsPanelGridStyle}">
Style="{StaticResource EventDetailsPanelGridStyle}"
Visibility="{x:Bind ViewModel.HasAttachments, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@@ -304,6 +395,82 @@
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="Attachments" />
<ListView
Grid.Row="1"
IsItemClickEnabled="True"
ItemClick="AttachmentClicked"
ItemsSource="{x:Bind ViewModel.Attachments, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:CalendarAttachmentViewModel">
<Grid Height="51">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
Height="50"
Background="Transparent"
ColumnSpacing="6">
<Grid.ContextFlyout>
<MenuFlyout Placement="Right">
<MenuFlyoutItem
Click="OpenCalendarAttachment_Click"
CommandParameter="{x:Bind}"
Text="{x:Bind domain:Translator.Buttons_Open}" />
<MenuFlyoutItem
Click="SaveCalendarAttachment_Click"
CommandParameter="{x:Bind}"
Text="{x:Bind domain:Translator.Buttons_Save}" />
</MenuFlyout>
</Grid.ContextFlyout>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<SymbolIcon Symbol="Attach" />
<Grid
Grid.Column="1"
MaxWidth="200"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
FontSize="13"
MaxLines="1"
Text="{x:Bind FileName}"
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
FontSize="11"
Foreground="Gray"
Text="{x:Bind ReadableSize}" />
</Grid>
</Grid>
<ProgressBar
Grid.Row="1"
Margin="0,-5,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
IsIndeterminate="{x:Bind IsBusy, Mode=OneWay}"
ShowError="False"
ShowPaused="False"
Visibility="{x:Bind IsBusy, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
</ScrollViewer>
+26 -1
View File
@@ -1,4 +1,5 @@
using Windows.UI.Xaml.Controls;
using Wino.Calendar.ViewModels.Data;
using Wino.Calendar.Views.Abstract;
namespace Wino.Calendar.Views;
@@ -9,4 +10,28 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract
{
this.InitializeComponent();
}
private void AttachmentClicked(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is CalendarAttachmentViewModel attachmentViewModel)
{
ViewModel?.OpenAttachmentCommand.Execute(attachmentViewModel);
}
}
private void OpenCalendarAttachment_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (sender is MenuFlyoutItem item && item.CommandParameter is CalendarAttachmentViewModel attachment)
{
ViewModel?.OpenAttachmentCommand.Execute(attachment);
}
}
private void SaveCalendarAttachment_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (sender is MenuFlyoutItem item && item.CommandParameter is CalendarAttachmentViewModel attachment)
{
ViewModel?.SaveAttachmentCommand.Execute(attachment);
}
}
}
@@ -1,6 +1,7 @@
using System;
using SQLite;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Entities.Calendar;
@@ -15,7 +16,9 @@ public class AccountCalendar : IAccountCalendar
public string SynchronizationDeltaToken { get; set; }
public string Name { get; set; }
public bool IsPrimary { get; set; }
public bool IsSynchronizationEnabled { get; set; } = true;
public bool IsExtended { get; set; } = true;
public CalendarItemShowAs DefaultShowAs { get; set; } = CalendarItemShowAs.Busy;
/// <summary>
/// Unused for now.
@@ -0,0 +1,22 @@
namespace Wino.Core.Domain.Enums;
/// <summary>
/// Indicates the source of a calendar item update.
/// </summary>
public enum CalendarItemUpdateSource
{
/// <summary>
/// Update originated from client-side UI changes (ApplyUIChanges).
/// </summary>
ClientUpdated,
/// <summary>
/// Update originated from client-side UI revert (RevertUIChanges).
/// </summary>
ClientReverted,
/// <summary>
/// Update originated from server synchronization or database operations.
/// </summary>
Server
}
@@ -1,5 +1,6 @@
using System;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Interfaces;
@@ -9,9 +10,11 @@ public interface IAccountCalendar
string TextColorHex { get; set; }
string BackgroundColorHex { get; set; }
bool IsPrimary { get; set; }
bool IsSynchronizationEnabled { get; set; }
Guid AccountId { get; set; }
string RemoteCalendarId { get; set; }
bool IsExtended { get; set; }
CalendarItemShowAs DefaultShowAs { get; set; }
Guid Id { get; set; }
MailAccount MailAccount { get; set; }
}
+13 -5
View File
@@ -1,5 +1,7 @@
using CommunityToolkit.Mvvm.Messaging;
using System;
using CommunityToolkit.Mvvm.Messaging;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Enums;
using Wino.Messaging.Client.Calendar;
namespace Wino.Core.ViewModels;
@@ -9,14 +11,19 @@ public class CalendarBaseViewModel : CoreBaseViewModel,
IRecipient<CalendarItemUpdated>,
IRecipient<CalendarItemDeleted>
{
public void Receive(CalendarItemAdded message) => OnCalendarItemAdded(message.CalendarItem);
public void Receive(CalendarItemUpdated message) => OnCalendarItemUpdated(message.CalendarItem);
public void Receive(CalendarItemDeleted message) => OnCalendarItemDeleted(message.CalendarItem);
public void Receive(CalendarItemAdded message) => DispatchToUIThread(() => OnCalendarItemAdded(message.CalendarItem));
public void Receive(CalendarItemUpdated message) => DispatchToUIThread(() => OnCalendarItemUpdated(message.CalendarItem, message.Source));
public void Receive(CalendarItemDeleted message) => DispatchToUIThread(() => OnCalendarItemDeleted(message.CalendarItem));
protected virtual void OnCalendarItemAdded(CalendarItem calendarItem) { }
protected virtual void OnCalendarItemUpdated(CalendarItem calendarItem) { }
protected virtual void OnCalendarItemUpdated(CalendarItem calendarItem, CalendarItemUpdateSource source) { }
protected virtual void OnCalendarItemDeleted(CalendarItem calendarItem) { }
private void DispatchToUIThread(Action action)
{
_ = ExecuteUIThread(action);
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
@@ -35,3 +42,4 @@ public class CalendarBaseViewModel : CoreBaseViewModel,
Messenger.Unregister<CalendarItemDeleted>(this);
}
}
+12 -1
View File
@@ -40,7 +40,18 @@ public class CoreBaseViewModel : ObservableRecipient, INavigationAware
public virtual void OnPageLoaded() { }
public async Task ExecuteUIThread(Action action) => await Dispatcher?.ExecuteOnUIThread(action);
public Task ExecuteUIThread(Action action)
{
if (action == null) return Task.CompletedTask;
if (Dispatcher == null)
{
action();
return Task.CompletedTask;
}
return Dispatcher.ExecuteOnUIThread(action);
}
public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IUIMessage => Messenger.Send(message);
protected virtual void OnDispatcherAssigned() { }
@@ -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
@@ -26,6 +26,7 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
private readonly IAccountService _accountService;
private readonly IFolderService _folderService;
private readonly ICalendarService _calendarService;
private readonly IStatePersistanceService _statePersistanceService;
private bool isLoaded = false;
public MailAccount Account { get; set; }
@@ -60,12 +61,14 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
public AccountDetailsPageViewModel(IMailDialogService dialogService,
IAccountService accountService,
IFolderService folderService,
ICalendarService calendarService)
ICalendarService calendarService,
IStatePersistanceService statePersistanceService)
{
_dialogService = dialogService;
_accountService = accountService;
_folderService = folderService;
_calendarService = calendarService;
_statePersistanceService = statePersistanceService;
}
[RelayCommand]
@@ -132,6 +135,8 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
OnPropertyChanged(nameof(IsFocusedInboxSupportedForAccount));
SelectedTabIndex = _statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar ? 2 : 1;
var folderStructures = (await _folderService.GetFolderStructureForAccountAsync(Account.Id, true)).Folders;
foreach (var folder in folderStructures)
@@ -225,6 +225,8 @@ public partial class AccountCalendarStateService : ObservableRecipient,
existingCalendar.BackgroundColorHex = accountCalendar.BackgroundColorHex;
existingCalendar.IsExtended = accountCalendar.IsExtended;
existingCalendar.IsPrimary = accountCalendar.IsPrimary;
existingCalendar.IsSynchronizationEnabled = accountCalendar.IsSynchronizationEnabled;
existingCalendar.DefaultShowAs = accountCalendar.DefaultShowAs;
}
});
}
@@ -241,6 +243,8 @@ public partial class AccountCalendarStateService : ObservableRecipient,
existingCalendar.BackgroundColorHex = accountCalendar.BackgroundColorHex;
existingCalendar.IsExtended = accountCalendar.IsExtended;
existingCalendar.IsPrimary = accountCalendar.IsPrimary;
existingCalendar.IsSynchronizationEnabled = accountCalendar.IsSynchronizationEnabled;
existingCalendar.DefaultShowAs = accountCalendar.DefaultShowAs;
}
}
}
@@ -51,8 +51,13 @@ public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
if (e.NavigationMode == NavigationMode.New)
{
// Set initial tab to Mail (index 1)
TabSelector.SelectedItem = TabSelector.Items[1];
var targetTabIndex = ViewModel.SelectedTabIndex;
if (targetTabIndex < 0 || targetTabIndex >= TabSelector.Items.Count)
{
targetTabIndex = 1;
}
TabSelector.SelectedItem = TabSelector.Items[targetTabIndex];
}
}
}
@@ -2,6 +2,7 @@ using System;
using System.Text.Json;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
@@ -77,6 +78,12 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
private async Task RenderDescriptionAsync()
{
if (DispatcherQueue != null && !DispatcherQueue.HasThreadAccess)
{
await DispatcherQueue.EnqueueAsync(RenderDescriptionAsync);
return;
}
if (ViewModel?.CurrentEvent?.CalendarItem == null)
return;
@@ -141,6 +148,12 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
private async Task UpdateEditorThemeAsync()
{
if (DispatcherQueue != null && !DispatcherQueue.HasThreadAccess)
{
await DispatcherQueue.EnqueueAsync(UpdateEditorThemeAsync);
return;
}
await DOMLoadedTask.Task;
if (ViewModel.IsDarkWebviewRenderer)
@@ -171,9 +184,9 @@ public sealed partial class EventDetailsPage : EventDetailsPageAbstract,
_ = UpdateEditorThemeAsync();
}
async void IRecipient<CalendarDescriptionRenderingRequested>.Receive(CalendarDescriptionRenderingRequested message)
void IRecipient<CalendarDescriptionRenderingRequested>.Receive(CalendarDescriptionRenderingRequested message)
{
await RenderDescriptionAsync();
_ = RenderDescriptionAsync();
}
protected override void RegisterRecipients()
@@ -1,7 +1,8 @@
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Enums;
namespace Wino.Messaging.Client.Calendar;
public record CalendarItemAdded(CalendarItem CalendarItem);
public record CalendarItemUpdated(CalendarItem CalendarItem);
public record CalendarItemUpdated(CalendarItem CalendarItem, CalendarItemUpdateSource Source);
public record CalendarItemDeleted(CalendarItem CalendarItem);
+1 -1
View File
@@ -144,7 +144,7 @@ public class CalendarService : BaseDatabaseService, ICalendarService
}
});
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(calendarItem));
WeakReferenceMessenger.Default.Send(new CalendarItemUpdated(calendarItem, CalendarItemUpdateSource.Server));
}
catch (Exception ex)
{
+1
View File
@@ -64,4 +64,5 @@ public class DatabaseService : IDatabaseService
Connection.CreateTableAsync<MailInvitationCalendarMapping>()
);
}
}