diff --git a/Wino.Calendar.ViewModels/CalendarEventComposePageViewModel.cs b/Wino.Calendar.ViewModels/CalendarEventComposePageViewModel.cs index a23ff4a9..951493be 100644 --- a/Wino.Calendar.ViewModels/CalendarEventComposePageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarEventComposePageViewModel.cs @@ -327,7 +327,18 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel CalendarSynchronizerOperation.CreateEvent, ComposeResult: createdResult)); - _navigationService.GoBack(); + NavigateBackToCalendar(createdResult.StartDate); + } + + private void NavigateBackToCalendar(DateTime targetDate) + { + _navigationService.Navigate( + WinoPage.CalendarPage, + new CalendarPageNavigationArgs + { + NavigationDate = targetDate, + ForceReload = true + }); } public async Task> SearchContactsAsync(string queryText) diff --git a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs index f8c684ef..bae67b53 100644 --- a/Wino.Calendar.ViewModels/CalendarPageViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarPageViewModel.cs @@ -569,33 +569,39 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel, if (!IsPageActive(lifetimeVersion)) return; - RefreshSettings(); + var currentSettings = CurrentSettings; + if (currentSettings == null) + { + RefreshSettings(); + currentSettings = CurrentSettings; + } var today = _dateContextProvider.GetToday(); - var visibleRange = CalendarRangeResolver.Resolve(request, CurrentSettings, today); - var previousRange = CalendarRangeResolver.Navigate(visibleRange, -1, CurrentSettings, today); - var nextRange = CalendarRangeResolver.Navigate(visibleRange, 1, CurrentSettings, today); + var visibleRange = CalendarRangeResolver.Resolve(request, currentSettings, today); + var previousRange = CalendarRangeResolver.Navigate(visibleRange, -1, currentSettings, today); + var nextRange = CalendarRangeResolver.Navigate(visibleRange, 1, currentSettings, today); var loadedDateWindow = new DateRange( previousRange.StartDate.ToDateTime(TimeOnly.MinValue), nextRange.EndDate.AddDays(1).ToDateTime(TimeOnly.MinValue)); var shouldReload = forceReload || !IsSameVisibleRange(CurrentVisibleRange, visibleRange) || !IsSameDateRange(LoadedDateWindow, loadedDateWindow); + List loadedItems = null; if (shouldReload) { - var loadedItems = await LoadCalendarItemsAsync(loadedDateWindow, lifetimeVersion).ConfigureAwait(false); + loadedItems = await LoadCalendarItemsAsync(loadedDateWindow, lifetimeVersion).ConfigureAwait(false); if (!IsPageActive(lifetimeVersion)) return; - - await ExecuteUIThreadIfActiveAsync(lifetimeVersion, () => - { - _loadedCalendarItems = loadedItems; - CalendarItems = loadedItems; - }).ConfigureAwait(false); } await ExecuteUIThreadIfActiveAsync(lifetimeVersion, () => { + if (loadedItems != null) + { + _loadedCalendarItems = loadedItems; + CalendarItems = loadedItems; + } + CurrentVisibleRange = visibleRange; LoadedDateWindow = loadedDateWindow; VisibleDateRangeText = _calendarRangeTextFormatter.Format(visibleRange, _dateContextProvider); diff --git a/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs b/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs index 5840a669..2cc5a0d2 100644 --- a/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs +++ b/Wino.Calendar.ViewModels/EventDetailsPageViewModel.cs @@ -228,7 +228,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel // If the current event was deleted, navigate back if (CurrentEvent?.CalendarItem?.Id == calendarItem.Id || CurrentEvent?.CalendarItem.RecurringCalendarItemId == calendarItem.Id) { - _navigationService.GoBack(); + NavigateBackToCalendar(forceReload: true); } } @@ -453,7 +453,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel await _winoRequestDelegator.ExecuteAsync(preparationRequest); - _navigationService.GoBack(); + NavigateBackToCalendar(forceReload: true); } catch (Exception ex) { @@ -490,8 +490,7 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel await _winoRequestDelegator.ExecuteAsync(preparationRequest); - // Navigate back after successful deletion - _navigationService.GoBack(); + NavigateBackToCalendar(forceReload: true); } catch (Exception ex) { @@ -499,6 +498,19 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel } } + private void NavigateBackToCalendar(bool forceReload) + { + var navigationDate = CurrentEvent?.CalendarItem.LocalStartDate ?? DateTime.Now; + + _navigationService.Navigate( + WinoPage.CalendarPage, + new CalendarPageNavigationArgs + { + NavigationDate = navigationDate, + ForceReload = forceReload + }); + } + public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args) { if (args.Handled || args.Mode != WinoApplicationMode.Calendar || args.Action != KeyboardShortcutAction.Delete) diff --git a/Wino.Core.Domain/Models/Calendar/CalendarPageNavigationArgs.cs b/Wino.Core.Domain/Models/Calendar/CalendarPageNavigationArgs.cs index a39d536b..6e1c0ba7 100644 --- a/Wino.Core.Domain/Models/Calendar/CalendarPageNavigationArgs.cs +++ b/Wino.Core.Domain/Models/Calendar/CalendarPageNavigationArgs.cs @@ -13,4 +13,9 @@ public class CalendarPageNavigationArgs /// Display the calendar view for the specified date. /// public DateTime NavigationDate { get; set; } + + /// + /// Force reloading the calendar data even when the target range does not change. + /// + public bool ForceReload { get; set; } } diff --git a/Wino.Mail.WinUI/AppThemes/Acrylic.xaml b/Wino.Mail.WinUI/AppThemes/Acrylic.xaml index f04c560a..67386db8 100644 --- a/Wino.Mail.WinUI/AppThemes/Acrylic.xaml +++ b/Wino.Mail.WinUI/AppThemes/Acrylic.xaml @@ -13,6 +13,8 @@ #ecf0f1 + #B2FCFCFC + #D9ECEFF1 #2C2C2C + #662C2C2C + #992C2C2C #b2dffc + #33B2DFFC + #66B2DFFC #222f3e #b2dffc + #33B2DFFC + #66B2DFFC #222f3e diff --git a/Wino.Mail.WinUI/AppThemes/Custom.xaml b/Wino.Mail.WinUI/AppThemes/Custom.xaml index 31de02ad..280ea4c9 100644 --- a/Wino.Mail.WinUI/AppThemes/Custom.xaml +++ b/Wino.Mail.WinUI/AppThemes/Custom.xaml @@ -22,6 +22,8 @@ #ecf0f1 #D9FFFFFF + + @@ -33,6 +35,8 @@ #1f1f1f #E61F1F1F + + diff --git a/Wino.Mail.WinUI/AppThemes/Default.xaml b/Wino.Mail.WinUI/AppThemes/Default.xaml index c81b0d09..cc21924a 100644 --- a/Wino.Mail.WinUI/AppThemes/Default.xaml +++ b/Wino.Mail.WinUI/AppThemes/Default.xaml @@ -13,9 +13,13 @@ #ecf0f1 + #F7F9FA + #DFE4EA #1f1f1f + #1F1F1F + #262626 diff --git a/Wino.Mail.WinUI/AppThemes/Forest.xaml b/Wino.Mail.WinUI/AppThemes/Forest.xaml index 8c8a26ba..aa5f2556 100644 --- a/Wino.Mail.WinUI/AppThemes/Forest.xaml +++ b/Wino.Mail.WinUI/AppThemes/Forest.xaml @@ -12,9 +12,13 @@ #A800D608 + #2200D608 + #4D00D608 #59001C01 + #22001C01 + #59001C01 diff --git a/Wino.Mail.WinUI/AppThemes/Garden.xaml b/Wino.Mail.WinUI/AppThemes/Garden.xaml index 3dbe1256..eaa17333 100644 --- a/Wino.Mail.WinUI/AppThemes/Garden.xaml +++ b/Wino.Mail.WinUI/AppThemes/Garden.xaml @@ -12,12 +12,16 @@ #dcfad8 + #26DCFAD8 + #59DCFAD8 #576574 #dcfad8 + #26576574 + #59576574 diff --git a/Wino.Mail.WinUI/AppThemes/Nighty.xaml b/Wino.Mail.WinUI/AppThemes/Nighty.xaml index b611d0c6..933ec6a6 100644 --- a/Wino.Mail.WinUI/AppThemes/Nighty.xaml +++ b/Wino.Mail.WinUI/AppThemes/Nighty.xaml @@ -13,10 +13,14 @@ #fdcb6e + #33FDCB6E + #66FDCB6E #5413191F + #2213191F + #5413191F diff --git a/Wino.Mail.WinUI/AppThemes/Snowflake.xaml b/Wino.Mail.WinUI/AppThemes/Snowflake.xaml index 34137686..04d1e076 100644 --- a/Wino.Mail.WinUI/AppThemes/Snowflake.xaml +++ b/Wino.Mail.WinUI/AppThemes/Snowflake.xaml @@ -13,10 +13,14 @@ #b0c6dd + #33B0C6DD + #66B0C6DD #b0c6dd + #33B0C6DD + #66B0C6DD diff --git a/Wino.Mail.WinUI/AppThemes/TestTheme.xaml b/Wino.Mail.WinUI/AppThemes/TestTheme.xaml deleted file mode 100644 index 152c2f93..00000000 --- a/Wino.Mail.WinUI/AppThemes/TestTheme.xaml +++ /dev/null @@ -1,22 +0,0 @@ - - - TestTheme.xaml - - - - - ms-appx:///BackgroundImages/bg6.jpg - #A3FFFFFF - #A3FFFFFF - #fdcb6e - - - - ms-appx:///BackgroundImages/bg6.jpg - - #A3000000 - #A3000000 - #A3262626 - - - diff --git a/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml b/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml index 1278966c..fb5c5001 100644 --- a/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml +++ b/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml @@ -9,12 +9,14 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:skia="using:SkiaSharp.Views.Windows" xmlns:viewModels="using:Wino.Calendar.ViewModels.Data" + x:Name="Root" - Loaded="ControlLoaded" SizeChanged="ControlSizeChanged" mc:Ignorable="d"> + + diff --git a/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml.cs b/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml.cs index 56253051..53bc7be0 100644 --- a/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml.cs +++ b/Wino.Mail.WinUI/Controls/Calendar/CalendarPeriodControl.xaml.cs @@ -3,12 +3,15 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using CommunityToolkit.WinUI; using Itenso.TimePeriod; +using Microsoft.UI; using Microsoft.UI.Composition; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Hosting; @@ -27,6 +30,8 @@ namespace Wino.Calendar.Controls; public sealed partial class CalendarPeriodControl : UserControl, INotifyPropertyChanged { + private static readonly TimeSpan SizeRefreshDebounceInterval = TimeSpan.FromMilliseconds(75); + private const double SizeChangeThreshold = 0.5d; private const double TimedHourColumnWidth = 64d; private const double TimedGridIntervalMinutes = 30d; private const double TimedSelectionIntervalMinutes = 30d; @@ -48,9 +53,12 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty private double _monthCellWidth; private double _monthCellHeight; private bool _hasPresentedState; + private bool _refreshPending = true; + private bool _refreshScheduled; private CalendarDisplayType _lastDisplayMode = CalendarDisplayType.Month; private DateOnly _lastDisplayDate = DateOnly.FromDateTime(DateTime.Today); private DayOfWeek _lastFirstDayOfWeek = DayOfWeek.Monday; + private readonly DispatcherQueueTimer _sizeRefreshTimer; [GeneratedDependencyProperty] public partial VisibleDateRange? VisibleRange { get; set; } @@ -64,7 +72,21 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty [GeneratedDependencyProperty] public partial string? TimedHeaderDateFormat { get; set; } - public CalendarPeriodControl() => InitializeComponent(); + [GeneratedDependencyProperty] + public partial Brush? DefaultHourBackground { get; set; } + + [GeneratedDependencyProperty] + public partial Brush? WorkHourBackground { get; set; } + + public CalendarPeriodControl() + { + InitializeComponent(); + + _sizeRefreshTimer = DispatcherQueue.CreateTimer(); + _sizeRefreshTimer.Interval = SizeRefreshDebounceInterval; + _sizeRefreshTimer.IsRepeating = false; + _sizeRefreshTimer.Tick += SizeRefreshTimerTick; + } public event PropertyChangedEventHandler? PropertyChanged; public event EventHandler? EmptySlotTapped; @@ -128,33 +150,51 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty public double TimelineHeight => TimedCalendarLayoutCalculator.GetTimelineHeight(GetHourHeight()); - partial void OnVisibleRangeChanged(VisibleDateRange? newValue) => Refresh(); - partial void OnCalendarSettingsChanged(CalendarSettings? newValue) => Refresh(); - partial void OnTimedHeaderDateFormatChanged(string? newValue) => Refresh(); + partial void OnVisibleRangeChanged(VisibleDateRange? newValue) => RequestRefresh(); + partial void OnCalendarSettingsChanged(CalendarSettings? newValue) => RequestRefresh(); + partial void OnTimedHeaderDateFormatChanged(string? newValue) => RequestRefresh(); partial void OnCalendarItemsChanged(IReadOnlyList? newValue) { DetachCurrentItemsSource(); AttachItemsSource(newValue); - Refresh(); + RequestRefresh(); } - private void ControlLoaded(object sender, RoutedEventArgs e) + private void ControlSizeChanged(object sender, SizeChangedEventArgs e) { - AttachItemsSource(CalendarItems); - Refresh(); - } + if (!HasMeaningfulSizeChange(e)) + { + return; + } - private void ControlSizeChanged(object sender, SizeChangedEventArgs e) => Refresh(); + var isLiveResize = _hasPresentedState && + e.PreviousSize.Width > 0 && + e.PreviousSize.Height > 0; + + if (isLiveResize) + { + _refreshPending = true; + _sizeRefreshTimer.Stop(); + _sizeRefreshTimer.Start(); + return; + } + + if (!_refreshPending) + { + return; + } + + QueueRefresh(); + } private IEnumerable CurrentItems => CalendarItems ?? []; private void AttachItemsSource(IReadOnlyList? itemsSource) { - _observableItemsSource = itemsSource as INotifyCollectionChanged; - - if (_observableItemsSource is not null) + if (itemsSource is INotifyCollectionChanged observableItemsSource) { + _observableItemsSource = observableItemsSource; _observableItemsSource.CollectionChanged += ItemsSourceCollectionChanged; } } @@ -183,11 +223,17 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty } } - private void ItemsSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => Refresh(); + private void ItemsSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => RequestRefresh(); + + private void RequestRefresh() + { + _refreshPending = true; + QueueRefresh(); + } private void Refresh() { - if (!IsLoaded || ActualWidth <= 0 || VisibleRange is null || CalendarSettings is null) + if (!_refreshPending || !IsLoaded || ActualWidth <= 0 || VisibleRange is null || CalendarSettings is null) { return; } @@ -206,9 +252,37 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty RunTransition(transition); _hasPresentedState = true; + _refreshPending = false; _lastDisplayMode = VisibleRange.DisplayType; _lastDisplayDate = VisibleRange.AnchorDate; _lastFirstDayOfWeek = CalendarSettings.FirstDayOfWeek; + + Debug.WriteLine($"Refreshed control."); + } + + private static bool HasMeaningfulSizeChange(SizeChangedEventArgs e) + => Math.Abs(e.NewSize.Width - e.PreviousSize.Width) > SizeChangeThreshold || + Math.Abs(e.NewSize.Height - e.PreviousSize.Height) > SizeChangeThreshold; + + private void QueueRefresh() + { + if (_refreshScheduled) + { + return; + } + + _refreshScheduled = true; + DispatcherQueue.TryEnqueue(() => + { + _refreshScheduled = false; + Refresh(); + }); + } + + private void SizeRefreshTimerTick(DispatcherQueueTimer sender, object args) + { + sender.Stop(); + QueueRefresh(); } private void RefreshTimedView() @@ -385,8 +459,8 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty { using var linePaint = CreateLinePaint(); using var minorLinePaint = CreateMinorLinePaint(); - using var defaultFillPaint = CreateFillPaint(GetDefaultHourBackground()); - using var workFillPaint = CreateFillPaint(GetWorkHourBackground()); + using var defaultFillPaint = CreateFillPaint(DefaultHourBackground ?? new SolidColorBrush(Colors.Transparent)); + using var workFillPaint = CreateFillPaint(WorkHourBackground ?? new SolidColorBrush(Colors.Transparent)); var canvas = e.Surface.Canvas; canvas.Clear(SKColors.Transparent); @@ -937,26 +1011,6 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty : SKColors.Transparent; } - private Brush GetDefaultHourBackground() - { - if (Application.Current.Resources.TryGetValue("LayerFillColorDefaultBrush", out var resource) && resource is Brush brush) - { - return brush; - } - - return new SolidColorBrush(Color.FromArgb(255, 28, 34, 42)); - } - - private Brush GetWorkHourBackground() - { - if (Application.Current.Resources.TryGetValue("SolidBackgroundFillColorBaseBrush", out var resource) && resource is SolidColorBrush solidBrush) - { - return new SolidColorBrush(Color.FromArgb(64, solidBrush.Color.R, solidBrush.Color.G, solidBrush.Color.B)); - } - - return new SolidColorBrush(Color.FromArgb(255, 34, 40, 52)); - } - private static double GetTimedGridIntervalHeight(double hourHeight) => hourHeight * (TimedGridIntervalMinutes / 60d); private double GetTimedGridIntervalHeight() => GetTimedGridIntervalHeight(GetHourHeight()); diff --git a/Wino.Mail.WinUI/Controls/CalendarTitleBarContent.xaml b/Wino.Mail.WinUI/Controls/CalendarTitleBarContent.xaml index ad157080..5f23310f 100644 --- a/Wino.Mail.WinUI/Controls/CalendarTitleBarContent.xaml +++ b/Wino.Mail.WinUI/Controls/CalendarTitleBarContent.xaml @@ -14,55 +14,59 @@ BasedOn="{StaticResource DefaultButtonStyle}" TargetType="Button"> - - - - + + + + + + - + + + + + + + + + + + - - - - - - - - - - + VerticalAlignment="Center" /> diff --git a/Wino.Mail.WinUI/Services/NavigationService.cs b/Wino.Mail.WinUI/Services/NavigationService.cs index 994b3e5c..1d22f495 100644 --- a/Wino.Mail.WinUI/Services/NavigationService.cs +++ b/Wino.Mail.WinUI/Services/NavigationService.cs @@ -487,7 +487,7 @@ public class NavigationService : NavigationServiceBase, INavigationService : DateOnly.FromDateTime(args.NavigationDate.Date); var displayRequest = new CalendarDisplayRequest(_statePersistanceService.CalendarDisplayType, targetDate); - return new LoadCalendarMessage(displayRequest); + return new LoadCalendarMessage(displayRequest, args.ForceReload); } private bool NavigateInnerShellFrame(Frame frame, Type pageType, object? parameter, NavigationTransitionType transition) diff --git a/Wino.Mail.WinUI/ShellWindow.xaml b/Wino.Mail.WinUI/ShellWindow.xaml index fd07e243..c0743d20 100644 --- a/Wino.Mail.WinUI/ShellWindow.xaml +++ b/Wino.Mail.WinUI/ShellWindow.xaml @@ -41,7 +41,7 @@ --> + VisibleRange="{x:Bind ViewModel.CurrentVisibleRange, Mode=OneWay}" + WorkHourBackground="{ThemeResource CalendarWorkHourBackgroundBrush}" /> - @@ -147,7 +146,6 @@ -