Range thing.

This commit is contained in:
Burak Kaan Köse
2026-03-21 00:58:01 +01:00
parent 01f7a09cb7
commit 51fef043ee
45 changed files with 1327 additions and 3753 deletions
-3
View File
@@ -116,12 +116,9 @@
<ResourceDictionary Source="Styles/CalendarThemeResources.xaml" />
<ResourceDictionary Source="Styles/WinoDayTimelineCanvas.xaml" />
<ResourceDictionary Source="Styles/WinoCalendarView.xaml" />
<ResourceDictionary Source="Styles/WinoCalendarTypeSelectorControl.xaml" />
<styles:WinoExpanderStyle />
<styles:WinoCalendarResources />
<ResourceDictionary Source="Styles/CalendarShellNavigationViewStyle.xaml" />
<ResourceDictionary Source="AppThemes/Default.xaml" />
</ResourceDictionary.MergedDictionaries>
+2
View File
@@ -283,6 +283,8 @@ public partial class App : WinoApplication,
services.AddTransient<IProviderService, ProviderService>();
services.AddSingleton<IAuthenticatorConfig, MailAuthenticatorConfiguration>();
services.AddSingleton<IAccountCalendarStateService, AccountCalendarStateService>();
services.AddSingleton<IDateContextProvider, SystemDateContextProvider>();
services.AddSingleton<ICalendarRangeTextFormatter, CalendarRangeTextFormatter>();
}
private void RegisterViewModels(IServiceCollection services)
@@ -1,381 +0,0 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Calendar.Args;
using Wino.Calendar.ViewModels.Data;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Calendar;
using Wino.Helpers;
namespace Wino.Calendar.Controls;
public partial class WinoCalendarControl : Control, IDisposable
{
private const string PART_WinoFlipView = nameof(PART_WinoFlipView);
private const string PART_IdleGrid = nameof(PART_IdleGrid);
public event EventHandler<TimelineCellSelectedArgs>? TimelineCellSelected;
public event EventHandler<TimelineCellUnselectedArgs>? TimelineCellUnselected;
public event EventHandler? ScrollPositionChanging;
#region Dependency Properties
/// <summary>
/// Gets or sets the collection of day ranges to render.
/// Each day range usually represents a week, but it may support other ranges.
/// </summary>
[GeneratedDependencyProperty]
public partial ObservableCollection<DayRangeRenderModel>? DayRanges { get; set; }
[GeneratedDependencyProperty(DefaultValue = -1)]
public partial int SelectedFlipViewIndex { get; set; }
[GeneratedDependencyProperty]
public partial DayRangeRenderModel? SelectedFlipViewDayRange { get; set; }
[GeneratedDependencyProperty]
public partial WinoDayTimelineCanvas? ActiveCanvas { get; set; }
[GeneratedDependencyProperty(DefaultValue = true)]
public partial bool IsFlipIdle { get; set; }
[GeneratedDependencyProperty]
public partial ScrollViewer? ActiveScrollViewer { get; set; }
[GeneratedDependencyProperty]
public partial ItemsPanelTemplate? VerticalItemsPanelTemplate { get; set; }
[GeneratedDependencyProperty]
public partial ItemsPanelTemplate? HorizontalItemsPanelTemplate { get; set; }
[GeneratedDependencyProperty(DefaultValue = CalendarOrientation.Horizontal)]
public partial CalendarOrientation Orientation { get; set; }
/// <summary>
/// Gets or sets the day-week-month-year display type.
/// Orientation is not determined by this property, but Orientation property.
/// This property is used to determine the template to use for the calendar.
/// </summary>
[GeneratedDependencyProperty(DefaultValue = CalendarDisplayType.Day)]
public partial CalendarDisplayType DisplayType { get; set; }
#endregion
private WinoCalendarFlipView? InternalFlipView;
private Grid? IdleGrid;
private ScrollViewer? _previousScrollViewer;
private WinoDayTimelineCanvas? _previousCanvas;
public WinoCalendarControl()
{
DefaultStyleKey = typeof(WinoCalendarControl);
SizeChanged += CalendarSizeChanged;
}
partial void OnVerticalItemsPanelTemplateChanged(ItemsPanelTemplate? newValue)
=> ManageCalendarOrientation();
partial void OnHorizontalItemsPanelTemplateChanged(ItemsPanelTemplate? newValue)
=> ManageCalendarOrientation();
partial void OnOrientationChanged(CalendarOrientation newValue)
=> ManageCalendarOrientation();
partial void OnDisplayTypeChanged(CalendarDisplayType newValue)
=> ManageDisplayType();
partial void OnIsFlipIdleChanged(bool newValue)
=> UpdateIdleState();
partial void OnDayRangesChanged(ObservableCollection<DayRangeRenderModel>? newValue)
=> EnsureStableSelection();
partial void OnSelectedFlipViewDayRangeChanged(DayRangeRenderModel? newValue)
=> EnsureStableSelection();
partial void OnActiveScrollViewerPropertyChanged(DependencyPropertyChangedEventArgs e)
{
var newValue = e.NewValue as ScrollViewer;
if (_previousScrollViewer != null)
{
DeregisterScrollChanges(_previousScrollViewer);
}
if (newValue != null)
{
RegisterScrollChanges(newValue);
}
_previousScrollViewer = newValue;
ManageHighlightedDateRange();
}
partial void OnActiveCanvasPropertyChanged(DependencyPropertyChangedEventArgs e)
{
var newValue = e.NewValue as WinoDayTimelineCanvas;
if (_previousCanvas != null)
{
DeregisterCanvas(_previousCanvas);
}
if (newValue != null)
{
RegisterCanvas(newValue);
}
_previousCanvas = newValue;
ManageHighlightedDateRange();
}
private void ManageCalendarOrientation()
{
if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return;
InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate;
}
private void ManageDisplayType()
{
if (InternalFlipView == null) return;
InternalFlipView.DisplayType = DisplayType;
}
private void ManageHighlightedDateRange()
{
if (InternalFlipView?.IsProgrammaticNavigationInProgress == true)
{
return;
}
SelectedFlipViewDayRange = InternalFlipView?.SelectedItem as DayRangeRenderModel;
}
private void DeregisterCanvas(WinoDayTimelineCanvas canvas)
{
if (canvas == null) return;
canvas.SelectedDateTime = null;
canvas.TimelineCellSelected -= ActiveTimelineCellSelected;
canvas.TimelineCellUnselected -= ActiveTimelineCellUnselected;
}
private void RegisterCanvas(WinoDayTimelineCanvas canvas)
{
if (canvas == null) return;
canvas.SelectedDateTime = null;
canvas.TimelineCellSelected += ActiveTimelineCellSelected;
canvas.TimelineCellUnselected += ActiveTimelineCellUnselected;
}
private void RegisterScrollChanges(ScrollViewer scrollViewer)
{
if (scrollViewer == null) return;
scrollViewer.ViewChanging += ScrollViewChanging;
}
private void DeregisterScrollChanges(ScrollViewer scrollViewer)
{
if (scrollViewer == null) return;
scrollViewer.ViewChanging -= ScrollViewChanging;
}
private void ScrollViewChanging(object? sender, ScrollViewerViewChangingEventArgs e)
=> ScrollPositionChanging?.Invoke(this, EventArgs.Empty);
private void CalendarSizeChanged(object sender, SizeChangedEventArgs e)
{
if (ActiveCanvas == null) return;
ActiveCanvas.SelectedDateTime = null;
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (InternalFlipView != null)
{
InternalFlipView.ProgrammaticNavigationCompleted -= InternalFlipViewProgrammaticNavigationCompleted;
if (InternalFlipView is IDisposable disposableFlipView)
{
disposableFlipView.Dispose();
}
}
if (_previousScrollViewer != null)
{
DeregisterScrollChanges(_previousScrollViewer);
_previousScrollViewer = null;
}
if (_previousCanvas != null)
{
DeregisterCanvas(_previousCanvas);
_previousCanvas = null;
}
InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView;
IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid;
if (InternalFlipView != null)
{
InternalFlipView.ProgrammaticNavigationCompleted += InternalFlipViewProgrammaticNavigationCompleted;
}
UpdateIdleState();
ManageCalendarOrientation();
ManageDisplayType();
EnsureStableSelection();
}
private void InternalFlipViewProgrammaticNavigationCompleted(object? sender, ProgrammaticNavigationCompletedEventArgs e)
{
SelectedFlipViewDayRange = e.DayRange;
}
private void UpdateIdleState()
{
if (InternalFlipView != null)
{
InternalFlipView.Opacity = IsFlipIdle ? 0 : 1;
}
if (IdleGrid != null)
{
IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed;
}
}
private void EnsureStableSelection()
{
if (InternalFlipView == null || DayRanges == null || DayRanges.Count == 0)
return;
var targetIndex = SelectedFlipViewIndex;
if (SelectedFlipViewDayRange != null)
{
var selectedRangeIndex = DayRanges.IndexOf(SelectedFlipViewDayRange);
if (selectedRangeIndex >= 0)
{
targetIndex = selectedRangeIndex;
}
}
if (targetIndex < 0 || targetIndex >= DayRanges.Count)
{
targetIndex = 0;
}
if (InternalFlipView.SelectedIndex != targetIndex)
{
InternalFlipView.SelectedIndex = targetIndex;
}
}
private void ActiveTimelineCellUnselected(object? sender, TimelineCellUnselectedArgs e)
=> TimelineCellUnselected?.Invoke(this, e);
private void ActiveTimelineCellSelected(object? sender, TimelineCellSelectedArgs e)
=> TimelineCellSelected?.Invoke(this, e);
public void NavigateToDay(DateTime dateTime) => InternalFlipView?.NavigateToDay(dateTime);
public async void NavigateToHour(TimeSpan timeSpan)
{
if (ActiveScrollViewer == null) return;
// Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers.
await Task.Yield();
await DispatcherQueue.EnqueueAsync(() =>
{
if (ActiveScrollViewer == null) return;
double hourHeght = 60;
double totalHeight = ActiveScrollViewer.ScrollableHeight;
double scrollPosition = timeSpan.TotalHours * hourHeght;
ActiveScrollViewer.ChangeView(null, scrollPosition, null, disableAnimation: false);
});
}
public void ResetTimelineSelection()
{
if (ActiveCanvas == null) return;
ActiveCanvas.SelectedDateTime = null;
}
public void GoNextRange()
{
if (InternalFlipView == null) return;
InternalFlipView.GoNextFlip();
}
public void GoPreviousRange()
{
if (InternalFlipView == null) return;
InternalFlipView.GoPreviousFlip();
}
public void UnselectActiveTimelineCell()
{
if (ActiveCanvas == null) return;
ActiveCanvas.SelectedDateTime = null;
}
public CalendarItemControl GetCalendarItemControl(CalendarItemViewModel calendarItemViewModel)
{
return this.FindDescendants<CalendarItemControl>().FirstOrDefault(a => a.CalendarItem == calendarItemViewModel)!;
}
public void Dispose()
{
SizeChanged -= CalendarSizeChanged;
if (_previousScrollViewer != null)
{
DeregisterScrollChanges(_previousScrollViewer);
_previousScrollViewer = null;
}
if (_previousCanvas != null)
{
DeregisterCanvas(_previousCanvas);
_previousCanvas = null;
}
if (InternalFlipView != null)
{
InternalFlipView.ProgrammaticNavigationCompleted -= InternalFlipViewProgrammaticNavigationCompleted;
if (InternalFlipView is IDisposable disposableFlipView)
{
disposableFlipView.Dispose();
}
InternalFlipView = null;
}
IdleGrid = null;
ActiveCanvas = null;
ActiveScrollViewer = null;
TimelineCellSelected = null;
TimelineCellUnselected = null;
ScrollPositionChanging = null;
}
}
@@ -1,321 +0,0 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Core.Domain.Collections;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Calendar.Controls;
public partial class WinoCalendarFlipView : CustomCalendarFlipView, IDisposable
{
public static readonly DependencyProperty IsIdleProperty = DependencyProperty.Register(nameof(IsIdle), typeof(bool), typeof(WinoCalendarFlipView), new PropertyMetadata(true));
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
public static readonly DependencyProperty ActiveVerticalScrollViewerProperty = DependencyProperty.Register(nameof(ActiveVerticalScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
/// <summary>
/// Gets or sets the active canvas that is currently displayed in the flip view.
/// Each day-range of flip view item has a canvas that displays the day timeline.
/// </summary>
public WinoDayTimelineCanvas? ActiveCanvas
{
get { return (WinoDayTimelineCanvas?)GetValue(ActiveCanvasProperty); }
set { SetValue(ActiveCanvasProperty, value); }
}
/// <summary>
/// Gets or sets the scroll viewer that is currently active in the flip view.
/// It's the vertical scroll that scrolls the timeline only, not the header part that belongs
/// to parent FlipView control.
/// </summary>
public ScrollViewer? ActiveVerticalScrollViewer
{
get { return (ScrollViewer?)GetValue(ActiveVerticalScrollViewerProperty); }
set { SetValue(ActiveVerticalScrollViewerProperty, value); }
}
public bool IsIdle
{
get { return (bool)GetValue(IsIdleProperty); }
set { SetValue(IsIdleProperty, value); }
}
internal bool IsProgrammaticNavigationInProgress { get; private set; }
internal int? PendingTargetIndex { get; private set; }
internal event EventHandler<ProgrammaticNavigationCompletedEventArgs>? ProgrammaticNavigationCompleted;
private INotifyCollectionChanged? _trackedItemsSource;
private readonly long _itemsSourceCallbackToken;
private FrameworkElement? _pendingActiveElementContainer;
private bool _isActiveElementResolutionPending;
public WinoCalendarFlipView()
{
_itemsSourceCallbackToken = RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged));
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyProperty e)
{
if (d is WinoCalendarFlipView flipView)
{
flipView.RegisterItemsSourceChange();
}
}
private void RegisterItemsSourceChange()
{
if (_trackedItemsSource != null)
{
_trackedItemsSource.CollectionChanged -= ItemsSourceUpdated;
}
_trackedItemsSource = GetItemsSource();
if (_trackedItemsSource != null)
{
_trackedItemsSource.CollectionChanged += ItemsSourceUpdated;
}
UpdateIdleState();
}
protected override void OnSelectedItemChanged(object? oldValue, object? newValue)
{
base.OnSelectedItemChanged(oldValue, newValue);
UpdateActiveElements();
}
protected override void OnContainerPrepared(DependencyObject element, object item)
{
base.OnContainerPrepared(element, item);
// Check if this is the currently selected item's container
var index = IndexFromContainer(element);
if (index >= 0 && index == SelectedIndex)
{
// Container for selected item is now ready, update active elements
UpdateActiveElements();
}
}
private void ItemsSourceUpdated(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateIdleState();
}
private void UpdateIdleState()
{
var itemsSource = GetItemsSource();
IsIdle = itemsSource == null || itemsSource.Count == 0;
}
private void UpdateActiveElements()
{
var itemsSource = GetItemsSource();
if (SelectedIndex < 0)
{
CancelPendingActiveElementResolution();
if (itemsSource == null || itemsSource.Count == 0)
{
ActiveCanvas = null;
ActiveVerticalScrollViewer = null;
}
return;
}
if (TryResolveActiveElements())
{
CancelPendingActiveElementResolution();
}
else
{
ScheduleActiveElementResolution();
}
}
private bool TryResolveActiveElements()
{
if (SelectedIndex < 0)
{
return false;
}
if (ContainerFromIndex(SelectedIndex) is not FlipViewItem container)
{
return false;
}
var activeCanvas = container.FindDescendant<WinoDayTimelineCanvas>();
var activeVerticalScrollViewer = container.FindDescendant<ScrollViewer>();
if (activeCanvas == null && activeVerticalScrollViewer == null)
{
return false;
}
ActiveCanvas = activeCanvas;
ActiveVerticalScrollViewer = activeVerticalScrollViewer;
return true;
}
private void ScheduleActiveElementResolution()
{
if (ContainerFromIndex(SelectedIndex) is FrameworkElement container &&
!ReferenceEquals(_pendingActiveElementContainer, container))
{
if (_pendingActiveElementContainer != null)
{
_pendingActiveElementContainer.Loaded -= PendingActiveElementContainerLoaded;
}
_pendingActiveElementContainer = container;
_pendingActiveElementContainer.Loaded += PendingActiveElementContainerLoaded;
}
if (_isActiveElementResolutionPending)
{
return;
}
_isActiveElementResolutionPending = true;
LayoutUpdated += FlipViewLayoutUpdated;
DispatcherQueue.TryEnqueue(RetryActiveElementResolution);
}
private void RetryActiveElementResolution()
{
if (TryResolveActiveElements())
{
CancelPendingActiveElementResolution();
}
}
private void PendingActiveElementContainerLoaded(object sender, RoutedEventArgs e)
=> RetryActiveElementResolution();
private void FlipViewLayoutUpdated(object? sender, object e)
=> RetryActiveElementResolution();
private void CancelPendingActiveElementResolution()
{
if (_pendingActiveElementContainer != null)
{
_pendingActiveElementContainer.Loaded -= PendingActiveElementContainerLoaded;
_pendingActiveElementContainer = null;
}
if (_isActiveElementResolutionPending)
{
LayoutUpdated -= FlipViewLayoutUpdated;
_isActiveElementResolutionPending = false;
}
}
/// <summary>
/// Navigates to the specified date in the calendar.
/// </summary>
/// <param name="dateTime">Date to navigate.</param>
public async void NavigateToDay(DateTime dateTime)
{
await Task.Yield();
await DispatcherQueue.EnqueueAsync(() =>
{
// Find the day range that contains the date.
var dayRanges = GetItemsSource();
var dayRange = dayRanges?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date));
if (dayRange != null && dayRanges != null)
{
var navigationItemIndex = dayRanges.IndexOf(dayRange);
var hasNavigationWork = navigationItemIndex != SelectedIndex;
IsProgrammaticNavigationInProgress = hasNavigationWork;
PendingTargetIndex = navigationItemIndex;
if (!hasNavigationWork)
{
PendingTargetIndex = null;
return;
}
try
{
if (Math.Abs(navigationItemIndex - SelectedIndex) > 4)
{
// Difference between dates are high.
// No need to animate this much, just go without animating.
SelectedIndex = navigationItemIndex;
}
else
{
// Until we reach the day in the flip, simulate next-prev button clicks.
// This will make sure the FlipView animations are triggered.
// Setting SelectedIndex directly doesn't trigger the animations.
while (SelectedIndex != navigationItemIndex)
{
if (SelectedIndex > navigationItemIndex)
{
GoPreviousFlip();
}
else
{
GoNextFlip();
}
}
}
}
finally
{
if (SelectedIndex == PendingTargetIndex)
{
ProgrammaticNavigationCompleted?.Invoke(this, new ProgrammaticNavigationCompletedEventArgs(SelectedItem as DayRangeRenderModel ?? dayRange));
}
IsProgrammaticNavigationInProgress = false;
PendingTargetIndex = null;
}
}
});
}
private ObservableRangeCollection<DayRangeRenderModel>? GetItemsSource()
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
public void Dispose()
{
CancelPendingActiveElementResolution();
if (_trackedItemsSource != null)
{
_trackedItemsSource.CollectionChanged -= ItemsSourceUpdated;
_trackedItemsSource = null;
}
UnregisterPropertyChangedCallback(ItemsSourceProperty, _itemsSourceCallbackToken);
ProgrammaticNavigationCompleted = null;
}
}
internal sealed class ProgrammaticNavigationCompletedEventArgs : EventArgs
{
public ProgrammaticNavigationCompletedEventArgs(DayRangeRenderModel dayRange)
{
DayRange = dayRange;
}
public DayRangeRenderModel DayRange { get; }
}
@@ -1,178 +0,0 @@
using System;
using System.Windows.Input;
using CommunityToolkit.Diagnostics;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
using Wino.Core.Domain.Models.Calendar;
using Wino.Helpers;
namespace Wino.Calendar.Controls;
public partial class WinoCalendarView : Control, IDisposable
{
private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder);
private const string PART_CalendarView = nameof(PART_CalendarView);
public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged)));
public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged)));
public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null));
public static readonly DependencyProperty TodayBackgroundColorProperty = DependencyProperty.Register(nameof(TodayBackgroundColor), typeof(Color), typeof(WinoCalendarView), new PropertyMetadata(new Color()));
public Color TodayBackgroundColor
{
get { return (Color)GetValue(TodayBackgroundColorProperty); }
set { SetValue(TodayBackgroundColorProperty, value); }
}
/// <summary>
/// Gets or sets the command to execute when a date is picked.
/// Unused.
/// </summary>
public ICommand? DateClickedCommand
{
get { return (ICommand?)GetValue(DateClickedCommandProperty); }
set { SetValue(DateClickedCommandProperty, value); }
}
/// <summary>
/// Gets or sets the highlighted range of dates.
/// </summary>
public DateRange? HighlightedDateRange
{
get { return (DateRange?)GetValue(HighlightedDateRangeProperty); }
set { SetValue(HighlightedDateRangeProperty, value); }
}
public Brush? VisibleDateBackground
{
get { return (Brush?)GetValue(VisibleDateBackgroundProperty); }
set { SetValue(VisibleDateBackgroundProperty, value); }
}
private CalendarView? CalendarView;
private long _displayModeCallbackToken = -1;
public WinoCalendarView()
{
DefaultStyleKey = typeof(WinoCalendarView);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (CalendarView != null)
{
CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged;
if (_displayModeCallbackToken != -1)
{
CalendarView.UnregisterPropertyChangedCallback(CalendarView.DisplayModeProperty, _displayModeCallbackToken);
_displayModeCallbackToken = -1;
}
}
CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView;
Guard.IsNotNull(CalendarView, nameof(CalendarView));
if (CalendarView == null) return;
CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged;
CalendarView.SelectedDatesChanged += InternalCalendarViewSelectionChanged;
// TODO: Should come from settings.
CalendarView.FirstDayOfWeek = Windows.Globalization.DayOfWeek.Monday;
// Everytime display mode changes, update the visible date range backgrounds.
// If users go back from year -> month -> day, we need to update the visible date range backgrounds.
_displayModeCallbackToken = CalendarView.RegisterPropertyChangedCallback(CalendarView.DisplayModeProperty, (s, e) => UpdateVisibleDateRangeBackgrounds());
}
private void InternalCalendarViewSelectionChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args)
{
if (args.AddedDates?.Count > 0)
{
var clickedDate = args.AddedDates[0].Date;
SetInnerDisplayDate(clickedDate);
var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate);
DateClickedCommand?.Execute(clickArgs);
}
// Reset selection, we don't show selected dates but react to them.
CalendarView?.SelectedDates.Clear();
}
private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinoCalendarView control)
{
control.UpdateVisibleDateRangeBackgrounds();
}
}
private void SetInnerDisplayDate(DateTime dateTime) => CalendarView?.SetDisplayDate(dateTime);
// Changing selected dates will trigger the selection changed event.
// It will behave like user clicked the date.
public void GoToDay(DateTime dateTime) => CalendarView?.SelectedDates.Add(dateTime);
private static void OnHighlightedDateRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinoCalendarView control)
{
if (control.HighlightedDateRange == null) return;
control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate);
control.UpdateVisibleDateRangeBackgrounds();
}
}
public void UpdateVisibleDateRangeBackgrounds()
{
if (HighlightedDateRange == null || VisibleDateBackground == null || CalendarView == null) return;
var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants<CalendarViewDayItem>(CalendarView);
foreach (var calendarDayItem in markDateCalendarDayItems)
{
var border = WinoVisualTreeHelper.GetChildObject<Border>(calendarDayItem, PART_DayViewItemBorder);
if (border == null) return;
if (calendarDayItem.Date.Date == DateTime.Today.Date)
{
border.Background = new SolidColorBrush(TodayBackgroundColor);
}
else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date)
{
border.Background = VisibleDateBackground;
}
else
{
border.Background = null;
}
}
}
public void Dispose()
{
if (CalendarView == null)
return;
CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged;
if (_displayModeCallbackToken != -1)
{
CalendarView.UnregisterPropertyChangedCallback(CalendarView.DisplayModeProperty, _displayModeCallbackToken);
_displayModeCallbackToken = -1;
}
CalendarView = null;
}
}
@@ -1,332 +0,0 @@
using System;
using System.Diagnostics;
using System.Linq;
using CommunityToolkit.WinUI;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using SkiaSharp;
using SkiaSharp.Views.Windows;
using Windows.Foundation;
using Wino.Calendar.Args;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Calendar.Controls;
public partial class WinoDayTimelineCanvas : Control, IDisposable
{
public event EventHandler<TimelineCellSelectedArgs>? TimelineCellSelected;
public event EventHandler<TimelineCellUnselectedArgs>? TimelineCellUnselected;
private const string PART_InternalCanvas = nameof(PART_InternalCanvas);
private SKXamlCanvas? Canvas;
public static readonly DependencyProperty RenderOptionsProperty = DependencyProperty.Register(nameof(RenderOptions), typeof(CalendarRenderOptions), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
public static readonly DependencyProperty SeperatorColorProperty = DependencyProperty.Register(nameof(SeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
public static readonly DependencyProperty HalfHourSeperatorColorProperty = DependencyProperty.Register(nameof(HalfHourSeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
public static readonly DependencyProperty SelectedCellBackgroundBrushProperty = DependencyProperty.Register(nameof(SelectedCellBackgroundBrush), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
public static readonly DependencyProperty WorkingHourCellBackgroundColorProperty = DependencyProperty.Register(nameof(WorkingHourCellBackgroundColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
public static readonly DependencyProperty SelectedDateTimeProperty = DependencyProperty.Register(nameof(SelectedDateTime), typeof(DateTime?), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedDateTimeChanged)));
public static readonly DependencyProperty PositionerUIElementProperty = DependencyProperty.Register(nameof(PositionerUIElement), typeof(UIElement), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null));
public UIElement? PositionerUIElement
{
get { return (UIElement?)GetValue(PositionerUIElementProperty); }
set { SetValue(PositionerUIElementProperty, value); }
}
public CalendarRenderOptions? RenderOptions
{
get { return (CalendarRenderOptions?)GetValue(RenderOptionsProperty); }
set { SetValue(RenderOptionsProperty, value); }
}
public SolidColorBrush? HalfHourSeperatorColor
{
get { return (SolidColorBrush?)GetValue(HalfHourSeperatorColorProperty); }
set { SetValue(HalfHourSeperatorColorProperty, value); }
}
public SolidColorBrush? SeperatorColor
{
get { return (SolidColorBrush?)GetValue(SeperatorColorProperty); }
set { SetValue(SeperatorColorProperty, value); }
}
public SolidColorBrush? WorkingHourCellBackgroundColor
{
get { return (SolidColorBrush?)GetValue(WorkingHourCellBackgroundColorProperty); }
set { SetValue(WorkingHourCellBackgroundColorProperty, value); }
}
public SolidColorBrush? SelectedCellBackgroundBrush
{
get { return (SolidColorBrush?)GetValue(SelectedCellBackgroundBrushProperty); }
set { SetValue(SelectedCellBackgroundBrushProperty, value); }
}
public DateTime? SelectedDateTime
{
get { return (DateTime?)GetValue(SelectedDateTimeProperty); }
set { SetValue(SelectedDateTimeProperty, value); }
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
Canvas = GetTemplateChild(PART_InternalCanvas) as SKXamlCanvas;
if (Canvas != null)
{
Canvas.PaintSurface += OnCanvasPaintSurface;
Canvas.PointerPressed += OnCanvasPointerPressed;
}
ForceDraw();
}
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinoDayTimelineCanvas control)
{
if (e.OldValue != null && e.NewValue == null)
{
control.RaiseCellUnselected();
}
control.ForceDraw();
}
}
private void RaiseCellUnselected()
{
TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs());
}
private void OnCanvasPointerPressed(object? sender, PointerRoutedEventArgs e)
{
if (RenderOptions == null) return;
var canvas = Canvas;
if (canvas == null) return;
var hourHeight = RenderOptions.CalendarSettings.HourHeight;
// When users click to cell we need to find the day, hour and minutes (first 30 minutes or second 30 minutes) that it represents on the timeline.
if (PositionerUIElement == null)
{
PositionerUIElement = this.FindParents().LastOrDefault(a => a is Grid);
}
if (PositionerUIElement == null)
return;
PointerPoint positionerRootPoint = e.GetCurrentPoint(PositionerUIElement);
PointerPoint canvasPointerPoint = e.GetCurrentPoint(canvas);
Point touchPoint = canvasPointerPoint.Position;
var singleDayWidth = (canvas.ActualWidth / RenderOptions.TotalDayCount);
int day = (int)(touchPoint.X / singleDayWidth);
int hour = (int)(touchPoint.Y / hourHeight);
bool isSecondHalf = touchPoint.Y % hourHeight > (hourHeight / 2);
var diffX = positionerRootPoint.Position.X - touchPoint.X;
var diffY = positionerRootPoint.Position.Y - touchPoint.Y;
var cellStartRelativePositionX = diffX + (day * singleDayWidth);
var cellEndRelativePositionX = cellStartRelativePositionX + singleDayWidth;
var cellStartRelativePositionY = diffY + (hour * hourHeight) + (isSecondHalf ? hourHeight / 2 : 0);
var cellEndRelativePositionY = cellStartRelativePositionY + (isSecondHalf ? (hourHeight / 2) : hourHeight);
var cellSize = new Size(cellEndRelativePositionX - cellStartRelativePositionX, hourHeight / 2);
var positionerPoint = new Point(cellStartRelativePositionX, cellStartRelativePositionY);
var clickedDateTime = RenderOptions.DateRange.StartDate.AddDays(day).AddHours(hour).AddMinutes(isSecondHalf ? 30 : 0);
// If there is already a selected date, in order to mimic the popup behavior, we need to dismiss the previous selection first.
// Next click will be a new selection.
// Raise the events directly here instead of DP to not lose pointer position.
if (clickedDateTime == SelectedDateTime || SelectedDateTime != null)
{
SelectedDateTime = null;
}
else
{
SelectedDateTime = clickedDateTime;
TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize));
}
Debug.WriteLine($"Clicked: {clickedDateTime}");
}
public WinoDayTimelineCanvas()
{
DefaultStyleKey = typeof(WinoDayTimelineCanvas);
}
private static void OnRenderingPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinoDayTimelineCanvas control)
{
control.ForceDraw();
}
}
private void ForceDraw() => Canvas?.Invalidate();
private bool CanDrawTimeline()
{
return RenderOptions != null
&& Canvas != null
&& WorkingHourCellBackgroundColor != null
&& SeperatorColor != null
&& HalfHourSeperatorColor != null
&& SelectedCellBackgroundBrush != null;
}
private void OnCanvasPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
{
if (!CanDrawTimeline()) return;
var renderOptions = RenderOptions!;
var workingHourCellBackgroundColor = WorkingHourCellBackgroundColor!;
var seperatorColor = SeperatorColor!;
var halfHourSeperatorColor = HalfHourSeperatorColor!;
var selectedCellBackgroundBrush = SelectedCellBackgroundBrush!;
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.Transparent);
int hours = 24;
double canvasWidth = e.Info.Width;
double canvasHeight = e.Info.Height;
if (canvasWidth == 0 || canvasHeight == 0) return;
// Calculate the width of each rectangle (1 day column)
// Equal distribution of the whole width.
double rectWidth = canvasWidth / renderOptions.TotalDayCount;
// Calculate the height of each rectangle (1 hour row)
double rectHeight = renderOptions.CalendarSettings.HourHeight;
// Define stroke and fill colors
var strokeColor = ToSKColor(seperatorColor.Color);
float strokeThickness = 0.5f;
// Create paints for drawing
using var strokePaint = new SKPaint
{
Color = strokeColor,
StrokeWidth = strokeThickness,
Style = SKPaintStyle.Stroke,
IsAntialias = true
};
using var fillPaint = new SKPaint
{
Style = SKPaintStyle.Fill,
IsAntialias = true
};
using var dashedPaint = new SKPaint
{
Color = ToSKColor(halfHourSeperatorColor.Color),
StrokeWidth = strokeThickness,
Style = SKPaintStyle.Stroke,
PathEffect = SKPathEffect.CreateDash([2f, 2f], 0),
IsAntialias = true
};
for (int day = 0; day < renderOptions.TotalDayCount; day++)
{
var currentDay = renderOptions.DateRange.StartDate.AddDays(day);
bool isWorkingDay = renderOptions.CalendarSettings.WorkingDays.Contains(currentDay.DayOfWeek);
// Loop through each hour (rows)
for (int hour = 0; hour < hours; hour++)
{
var renderTime = TimeSpan.FromHours(hour);
var representingDateTime = currentDay.AddHours(hour);
// Calculate the position and size of the rectangle
float x = (float)(day * rectWidth);
float y = (float)(hour * rectHeight);
float width = (float)rectWidth;
float height = (float)rectHeight;
var rectangle = new SKRect(x, y, x + width, y + height);
// Draw the rectangle border.
// This is the main rectangle.
canvas.DrawRect(rectangle, strokePaint);
// Fill another rectangle with the working hour background color
// This rectangle must be placed with -1 margin to prevent invisible borders of the main rectangle.
if (isWorkingDay && renderTime >= renderOptions.CalendarSettings.WorkingHourStart && renderTime <= renderOptions.CalendarSettings.WorkingHourEnd)
{
var backgroundRectangle = new SKRect(x + 1, y + 1, x + width - 1, y + height - 1);
canvas.DrawRect(backgroundRectangle, strokePaint);
fillPaint.Color = ToSKColor(workingHourCellBackgroundColor.Color);
canvas.DrawRect(backgroundRectangle, fillPaint);
}
// Draw a line in the center of the rectangle for representing half hours.
float lineY = y + height / 2;
canvas.DrawLine(x, lineY, x + width, lineY, dashedPaint);
}
// Draw selected item background color for the date if possible.
if (SelectedDateTime != null)
{
var selectedDateTime = SelectedDateTime.Value;
if (selectedDateTime.Date == currentDay.Date)
{
var selectionRectHeight = rectHeight / 2;
var selectedY = selectedDateTime.Hour * rectHeight + (selectedDateTime.Minute / 60) * rectHeight;
// Second half of the hour is selected.
if (selectedDateTime.TimeOfDay.Minutes == 30)
{
selectedY += rectHeight / 2;
}
var selectedRectangle = new SKRect(
(float)(day * rectWidth),
(float)selectedY,
(float)(day * rectWidth + rectWidth),
(float)(selectedY + selectionRectHeight));
fillPaint.Color = ToSKColor(selectedCellBackgroundBrush.Color);
canvas.DrawRect(selectedRectangle, fillPaint);
}
}
}
}
private static SKColor ToSKColor(Windows.UI.Color color)
{
return new SKColor(color.R, color.G, color.B, color.A);
}
public void Dispose()
{
if (Canvas == null) return;
Canvas.PaintSurface -= OnCanvasPaintSurface;
Canvas.PointerPressed -= OnCanvasPointerPressed;
Canvas = null;
}
}
@@ -2,7 +2,6 @@
x:Class="Wino.Mail.WinUI.Controls.UpdateNotesFlipViewControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:Wino.Core.Domain.Models.Updates"
@@ -22,14 +21,21 @@
<DataTemplate x:DataType="models:UpdateNoteSection">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel Padding="8" Spacing="12">
<controls:MarkdownTextBlock HorizontalAlignment="Center" Text="{x:Bind Title, Mode=OneTime}" />
<TextBlock
HorizontalAlignment="Center"
Text="{x:Bind Title, Mode=OneTime}"
TextAlignment="Center"
TextWrapping="WrapWholeWords" />
<Image
Width="{x:Bind ActualImageWidth, Mode=OneTime}"
Height="{x:Bind ActualImageHeight, Mode=OneTime}"
HorizontalAlignment="Center"
Source="{x:Bind ImageUrl, Mode=OneTime}"
Stretch="Uniform" />
<controls:MarkdownTextBlock HorizontalAlignment="Stretch" Text="{x:Bind Description, Mode=OneTime}" />
<TextBlock
HorizontalAlignment="Stretch"
Text="{x:Bind Description, Mode=OneTime}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</ScrollViewer>
</DataTemplate>
@@ -474,17 +474,14 @@ public class NavigationService : NavigationServiceBase, INavigationService
return true;
}
private static LoadCalendarMessage CreateLoadCalendarMessage(CalendarPageNavigationArgs args)
private LoadCalendarMessage CreateLoadCalendarMessage(CalendarPageNavigationArgs args)
{
var targetDate = args.RequestDefaultNavigation
? DateTime.Now.Date
: args.NavigationDate;
? DateOnly.FromDateTime(DateTime.Now.Date)
: DateOnly.FromDateTime(args.NavigationDate.Date);
var initiative = args.RequestDefaultNavigation
? CalendarInitInitiative.App
: CalendarInitInitiative.User;
return new LoadCalendarMessage(targetDate, initiative);
var displayRequest = new CalendarDisplayRequest(_statePersistanceService.CalendarDisplayType, targetDate);
return new LoadCalendarMessage(displayRequest);
}
private bool NavigateInnerShellFrame(Frame frame, Type pageType, object? parameter, NavigationTransitionType transition)
@@ -396,6 +396,8 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
return new CalendarSettings(FirstDayOfWeek,
workingDays,
WorkingDayStart,
WorkingDayEnd,
WorkingHourStart,
WorkingHourEnd,
HourHeight,
@@ -1,447 +0,0 @@
<ResourceDictionary
x:Class="Wino.Styles.WinoCalendarResources"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:calendar="using:Wino.Mail.WinUI.Controls.Calendar"
xmlns:controls="using:Wino.Calendar.Controls"
xmlns:controls2="using:Wino.Mail.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:data="using:Wino.Calendar.ViewModels.Data"
xmlns:helpers="using:Wino.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:Wino.Core.Domain.Models.Calendar"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:selectors="using:Wino.Calendar.Selectors"
xmlns:selectors1="using:Wino.Selectors"
xmlns:toolkitControls="using:CommunityToolkit.WinUI.Controls">
<!-- 08:00 or 8 AM/PM on the left etc. -->
<DataTemplate x:Key="DayCalendarHourHeaderTemplate" x:DataType="models:DayHeaderRenderModel">
<Grid Height="{x:Bind HourHeight}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Top"
Text="{x:Bind DayHeader}" />
</Grid>
</DataTemplate>
<!-- ShowAs Status Stripe Templates -->
<DataTemplate x:Key="FreeStripeTemplate" x:DataType="data:CalendarItemViewModel">
<Border Background="#4CAF50" />
</DataTemplate>
<DataTemplate x:Key="TentativeStripeTemplate" x:DataType="data:CalendarItemViewModel">
<Border Background="#FFC107" />
</DataTemplate>
<DataTemplate x:Key="BusyStripeTemplate" x:DataType="data:CalendarItemViewModel">
<Border Background="#ff7675" />
</DataTemplate>
<DataTemplate x:Key="OutOfOfficeStripeTemplate" x:DataType="data:CalendarItemViewModel">
<Border Background="#9C27B0" />
</DataTemplate>
<DataTemplate x:Key="WorkingElsewhereStripeTemplate" x:DataType="data:CalendarItemViewModel">
<Border Background="#2196F3" />
</DataTemplate>
<!-- ShowAs Status Stripe Selector -->
<selectors1:CalendarItemShowAsStripeTemplateSelector
x:Key="ShowAsStripeSelector"
BusyTemplate="{StaticResource BusyStripeTemplate}"
FreeTemplate="{StaticResource FreeStripeTemplate}"
OutOfOfficeTemplate="{StaticResource OutOfOfficeStripeTemplate}"
TentativeTemplate="{StaticResource TentativeStripeTemplate}"
WorkingElsewhereTemplate="{StaticResource WorkingElsewhereStripeTemplate}" />
<!-- Vertical panel that renders items on canvas. -->
<DataTemplate x:Key="DayCalendarItemVerticalRenderTemplate" x:DataType="models:CalendarDayModel">
<ItemsControl
x:Name="RegularEventItemsControl"
ItemsSource="{x:Bind EventsCollection.RegularEvents}"
Loaded="OnRegularEventItemsControlLoaded">
<ItemsControl.ItemTemplate>
<!-- Default Calendar Item View Model Template -->
<DataTemplate x:DataType="data:CalendarItemViewModel">
<controls:CalendarItemControl CalendarItem="{x:Bind}" IsCustomEventArea="False" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WinoCalendarPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
<!-- Equally distributed days of week representation in FlipView. -->
<!-- Used for day-week-work week templates. -->
<!-- Horizontal template -->
<DataTemplate x:Key="FlipTemplate" x:DataType="models:DayRangeRenderModel">
<Grid
Background="Transparent"
ColumnSpacing="0"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="100" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ItemsControl
x:Name="DayColumnsItemsControl"
Margin="50,0,16,0"
ItemsSource="{x:Bind CalendarDays}"
Loaded="OnDayColumnsItemsControlLoaded">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:CalendarDayModel">
<controls:DayColumnControl DayModel="{x:Bind}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkitControls:UniformGrid Orientation="Horizontal" Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ScrollViewer
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="0"
Padding="0,0,16,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Rendering left hour headers. -->
<ItemsControl ItemTemplate="{StaticResource DayCalendarHourHeaderTemplate}" ItemsSource="{x:Bind DayHeaders}" />
<!-- Drawing canvas for timeline. -->
<controls:WinoDayTimelineCanvas
Grid.Column="1"
HalfHourSeperatorColor="{ThemeResource CalendarSeperatorBrush}"
RenderOptions="{x:Bind CalendarRenderOptions}"
SelectedCellBackgroundBrush="{ThemeResource CalendarFieldSelectedBackgroundBrush}"
SeperatorColor="{ThemeResource CalendarSeperatorBrush}"
WorkingHourCellBackgroundColor="{ThemeResource CalendarFieldWorkingHoursBackgroundBrush}" />
<!-- Each vertical day grids that renders events. -->
<ItemsControl
x:Name="EventGridsItemsControl"
Grid.Column="1"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
ItemTemplate="{StaticResource DayCalendarItemVerticalRenderTemplate}"
ItemsSource="{x:Bind CalendarDays}"
Loaded="OnEventGridsItemsControlLoaded">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkitControls:UniformGrid Orientation="Horizontal" Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</ScrollViewer>
</Grid>
</DataTemplate>
<!-- Template that displays 35 days in total. -->
<!-- Used for monthly view -->
<!-- Vertical template -->
<DataTemplate x:Key="MonthlyFlipTemplate" x:DataType="models:DayRangeRenderModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Height="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind CalendarDays}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:CalendarDayModel">
<controls:DayColumnControl DayModel="{x:Bind Mode=OneWay}" Template="{StaticResource MonthlyColumnControlTemplate}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls2:EqualGridPanel Columns="7" Rows="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="VerticalFlipViewItemsPanel">
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="HorizontalFlipViewItemsPanel">
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
<!-- Default style for WinoCalendarControl -->
<Style TargetType="controls:WinoCalendarControl">
<Style.Setters>
<Setter Property="HorizontalItemsPanelTemplate" Value="{StaticResource HorizontalFlipViewItemsPanel}" />
<Setter Property="VerticalItemsPanelTemplate" Value="{StaticResource VerticalFlipViewItemsPanel}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:WinoCalendarControl">
<Grid>
<controls:WinoCalendarFlipView
x:Name="PART_WinoFlipView"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
ActiveCanvas="{x:Bind ActiveCanvas, Mode=TwoWay}"
ActiveVerticalScrollViewer="{x:Bind ActiveScrollViewer, Mode=TwoWay}"
Background="Transparent"
IsIdle="{x:Bind IsFlipIdle, Mode=TwoWay}"
IsTabStop="False"
ItemsSource="{TemplateBinding DayRanges}"
SelectedIndex="{Binding SelectedFlipViewIndex, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}">
<controls:WinoCalendarFlipView.ItemTemplateSelector>
<selectors1:WinoCalendarItemTemplateSelector
DayWeekWorkWeekTemplate="{StaticResource FlipTemplate}"
DisplayType="{x:Bind DisplayType, Mode=OneWay}"
MonthlyTemplate="{StaticResource MonthlyFlipTemplate}" />
</controls:WinoCalendarFlipView.ItemTemplateSelector>
</controls:WinoCalendarFlipView>
<Grid x:Name="PART_IdleGrid" Visibility="Collapsed">
<muxc:ProgressRing
Width="50"
Height="50"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsActive="True" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
<!-- Top header control for days. -->
<ControlTemplate x:Key="DailyColumnControlTemplate" TargetType="controls:DayColumnControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="7" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Name of the day. Monday, Tuesday etc. at the top. -->
<TextBlock
x:Name="PART_ColumnHeaderText"
Margin="8,0,0,0"
FontSize="16"
TextTrimming="CharacterEllipsis" />
<Grid
Grid.Row="2"
Grid.RowSpan="2"
BorderBrush="{ThemeResource CalendarSeperatorBrush}"
BorderThickness="1,1,0,1" />
<!-- Border for today indication. -->
<Border
x:Name="PART_IsTodayBorder"
Grid.Row="1"
Height="5"
Margin="2,0,2,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="{ThemeResource SystemAccentColor}"
CornerRadius="2"
Visibility="Collapsed" />
<!-- Place where full day events go. -->
<Grid
x:Name="PART_DayDataAreaGrid"
Grid.Row="2"
Padding="6"
BorderBrush="{ThemeResource CalendarSeperatorBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="35" />
</Grid.RowDefinitions>
<!-- Day number -->
<TextBlock x:Name="PART_HeaderDateDayText" FontSize="17" />
<!-- Extras -->
<StackPanel Grid.Column="1" HorizontalAlignment="Right" />
<!-- All-Multi Day Events -->
<ItemsControl
x:Name="PART_AllDayItemsControl"
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="0,6">
<ItemsControl.ItemTemplateSelector>
<selectors:CustomAreaCalendarItemSelector>
<selectors:CustomAreaCalendarItemSelector.AllDayTemplate>
<DataTemplate x:DataType="data:CalendarItemViewModel">
<controls:CalendarItemControl CalendarItem="{x:Bind}" IsCustomEventArea="True" />
</DataTemplate>
</selectors:CustomAreaCalendarItemSelector.AllDayTemplate>
<selectors:CustomAreaCalendarItemSelector.MultiDayTemplate>
<DataTemplate x:DataType="data:CalendarItemViewModel">
<controls:CalendarItemControl CalendarItem="{x:Bind}" IsCustomEventArea="True" />
</DataTemplate>
</selectors:CustomAreaCalendarItemSelector.MultiDayTemplate>
</selectors:CustomAreaCalendarItemSelector>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Spacing="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="TodayOrNotStates">
<VisualState x:Name="NotTodayState" />
<VisualState x:Name="TodayState">
<VisualState.Setters>
<Setter Target="PART_IsTodayBorder.Visibility" Value="Visible" />
<Setter Target="PART_HeaderDateDayText.Foreground" Value="{ThemeResource SystemAccentColor}" />
<Setter Target="PART_HeaderDateDayText.FontWeight" Value="Semibold" />
<Setter Target="PART_ColumnHeaderText.FontWeight" Value="Semibold" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
<!-- Monthly data control for months -->
<ControlTemplate x:Key="MonthlyColumnControlTemplate" TargetType="controls:DayColumnControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="5" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Border for today indication. -->
<Border
x:Name="PART_IsTodayBorder"
Grid.Row="0"
Height="5"
Margin="2,0,2,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="{ThemeResource SystemAccentColor}"
CornerRadius="2"
Visibility="Collapsed" />
<!-- Border -->
<Grid
Grid.Row="0"
Grid.RowSpan="2"
BorderBrush="{ThemeResource CalendarSeperatorBrush}"
BorderThickness="1,1,0,1" />
<!-- Place where full day events go. -->
<Grid
x:Name="PART_DayDataAreaGrid"
Grid.Row="1"
Padding="6"
BorderBrush="{ThemeResource CalendarSeperatorBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="35" />
</Grid.RowDefinitions>
<!-- Day number -->
<TextBlock x:Name="PART_HeaderDateDayText" FontSize="17" />
<!-- Extras -->
<StackPanel Grid.Column="1" HorizontalAlignment="Right" />
<!-- All events summary. -->
<ScrollViewer
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="0,6"
Padding="0,0,16,0">
<ItemsControl x:Name="PART_AllDayItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="data:CalendarItemViewModel">
<controls:CalendarItemControl CalendarItem="{x:Bind}" IsCustomEventArea="True" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition />
</TransitionCollection>
</ItemsControl.ItemContainerTransitions>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Spacing="2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="TodayOrNotStates">
<VisualState x:Name="NotTodayState" />
<VisualState x:Name="TodayState">
<VisualState.Setters>
<Setter Target="PART_IsTodayBorder.Visibility" Value="Visible" />
<Setter Target="PART_HeaderDateDayText.Foreground" Value="{ThemeResource SystemAccentColor}" />
<Setter Target="PART_HeaderDateDayText.FontWeight" Value="Semibold" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
<!-- Default style for DayColumnControl -->
<Style TargetType="controls:DayColumnControl">
<Setter Property="Template" Value="{StaticResource DailyColumnControlTemplate}" />
</Style>
</ResourceDictionary>
@@ -1,49 +0,0 @@
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Calendar.Controls;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Styles;
public sealed partial class WinoCalendarResources : ResourceDictionary
{
public WinoCalendarResources()
{
this.InitializeComponent();
}
private void OnRegularEventItemsControlLoaded(object sender, RoutedEventArgs e)
{
if (sender is ItemsControl itemsControl && itemsControl.DataContext is CalendarDayModel dayModel)
{
if (itemsControl.ItemsPanelRoot is WinoCalendarPanel panel)
{
panel.HourHeight = dayModel.CalendarRenderOptions.CalendarSettings.HourHeight;
panel.Period = dayModel.Period;
}
}
}
private void OnDayColumnsItemsControlLoaded(object sender, RoutedEventArgs e)
{
if (sender is ItemsControl itemsControl && itemsControl.DataContext is DayRangeRenderModel rangeModel)
{
if (itemsControl.ItemsPanelRoot is UniformGrid uniformGrid)
{
uniformGrid.Columns = rangeModel.CalendarRenderOptions.TotalDayCount;
}
}
}
private void OnEventGridsItemsControlLoaded(object sender, RoutedEventArgs e)
{
if (sender is ItemsControl itemsControl && itemsControl.DataContext is DayRangeRenderModel rangeModel)
{
if (itemsControl.ItemsPanelRoot is UniformGrid uniformGrid)
{
uniformGrid.Columns = rangeModel.CalendarRenderOptions.TotalDayCount;
}
}
}
}
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Wino.Calendar.Controls">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CalendarThemeResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="controls:WinoCalendarView">
<Setter Property="VisibleDateBackground" Value="{ThemeResource WinoCalendarViewVisibleDayBackgroundBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:WinoCalendarView">
<CalendarView
x:Name="PART_CalendarView"
BorderBrush="{ThemeResource WinoCalendarViewBorderBrush}"
CalendarItemCornerRadius="5"
CornerRadius="4"
DayItemMargin="0"
IsTodayHighlighted="False"
SelectionMode="Single">
<CalendarView.CalendarViewDayItemStyle>
<Style TargetType="CalendarViewDayItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CalendarViewDayItem">
<Grid>
<Border
x:Name="PART_DayViewItemBorder"
Margin="0,-1,0,-3"
CornerRadius="5">
<ContentPresenter />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CalendarView.CalendarViewDayItemStyle>
</CalendarView>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -1,19 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Wino.Calendar.Controls"
xmlns:skia="using:SkiaSharp.Views.Windows">
<!-- Background Timeline Canvas -->
<Style TargetType="controls:WinoDayTimelineCanvas">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:WinoDayTimelineCanvas">
<skia:SKXamlCanvas x:Name="PART_InternalCanvas" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ResourceDictionary>
@@ -131,13 +131,26 @@
Text="{x:Bind domain:Translator.CalendarEventCompose_NewEventButton, Mode=OneTime}" />
</coreControls:WinoNavigationViewItem>
<calendarControls:WinoCalendarView
x:Name="CalendarView"
<Border
x:Name="RangeSummaryCard"
Grid.Row="1"
HorizontalAlignment="Center"
DateClickedCommand="{x:Bind ViewModel.DateClickedCommand}"
HighlightedDateRange="{x:Bind ViewModel.HighlightedDateRange, Mode=OneWay}"
TodayBackgroundColor="{ThemeResource SystemAccentColor}" />
Margin="16,0,16,12"
Padding="12"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8">
<StackPanel Spacing="12">
<TextBlock
FontWeight="SemiBold"
Text="{x:Bind ViewModel.VisibleDateRangeText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
<muxc:CalendarView
x:Name="VisibleDateRangeCalendarView"
HorizontalAlignment="Stretch"
SelectionMode="Multiple"
SelectedDatesChanged="VisibleDateRangeCalendarViewSelectedDatesChanged" />
</StackPanel>
</Border>
<!-- Account Calendars Host -->
<ListView
@@ -261,8 +274,6 @@
</VisualState>
<VisualState x:Name="SmallScreen">
<VisualState.Setters>
<Setter Target="NavigationTitleStack.Visibility" Value="Collapsed" />
<Setter Target="SearchBox.(Grid.ColumnSpan)" Value="2" />
<Setter Target="navigationView.IsPaneOpen" Value="False" />
</VisualState.Setters>
<VisualState.StateTriggers>
@@ -273,28 +284,14 @@
<VisualStateGroup x:Name="CalendarOrientationStates">
<VisualState x:Name="HorizontalCalendar" />
<VisualState x:Name="VerticalCalendar">
<VisualState.Setters>
<Setter Target="DayHeaderNavigationItemsFlipView.ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Target="PreviousDateButtonPathIcon.Data" Value="F1 M 3.2 10.52 C 2.986666 10.733333 2.92 10.98 3 11.259999 C 3.08 11.54 3.266666 11.713333 3.56 11.78 C 3.853333 11.846666 4.093333 11.773333 4.28 11.559999 L 9.24 6.32 L 9.24 19.039999 C 9.24 19.253332 9.313333 19.433332 9.46 19.58 C 9.606666 19.726665 9.786666 19.799999 10 19.799999 C 10.213333 19.799999 10.393332 19.726665 10.54 19.58 C 10.686666 19.433332 10.76 19.253332 10.76 19.039999 L 10.76 6.32 L 15.719999 11.559999 C 15.906666 11.773333 16.139999 11.846666 16.42 11.78 C 16.700001 11.713333 16.886665 11.54 16.98 11.259999 C 17.073332 10.98 17.013332 10.733333 16.799999 10.52 L 10.719999 4.119999 C 10.559999 3.959999 10.373333 3.853333 10.16 3.799999 C 10.053333 3.799999 9.946667 3.799999 9.84 3.799999 C 9.626666 3.853333 9.439999 3.959999 9.28 4.119999 Z " />
<Setter Target="NextDateButtonPathIcon.Data" Value="F1 M 16.799999 13.079999 C 16.933332 12.92 16.993332 12.74 16.98 12.539999 C 16.966665 12.34 16.886665 12.166667 16.74 12.02 C 16.593334 11.873333 16.42 11.799999 16.219999 11.799999 C 16.02 11.799999 15.853333 11.879999 15.719999 12.039999 L 10.76 17.279999 L 10.76 4.559999 C 10.76 4.346666 10.686666 4.166668 10.54 4.02 C 10.393332 3.873333 10.213333 3.799999 10 3.799999 C 9.786666 3.799999 9.606666 3.873333 9.46 4.02 C 9.313333 4.166668 9.24 4.346666 9.24 4.559999 L 9.24 17.279999 L 4.28 12.039999 C 4.146667 11.879999 3.98 11.799999 3.78 11.799999 C 3.58 11.799999 3.4 11.873333 3.24 12.02 C 3.08 12.166667 3 12.34 3 12.539999 C 3 12.74 3.066667 12.92 3.2 13.079999 L 9.28 19.48 C 9.439999 19.639999 9.626666 19.746666 9.84 19.799999 C 9.946667 19.799999 10.053333 19.799999 10.16 19.799999 C 10.373333 19.746666 10.559999 19.639999 10.719999 19.48 Z " />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="VerticalCalendar" />
</VisualStateGroup>
<VisualStateGroup x:Name="ShellStateContentGroup">
<VisualState x:Name="DefaultShellContentState" />
<VisualState x:Name="EventDetailsContentState">
<VisualState.Setters>
<Setter Target="ShellContentArea.Visibility" Value="Collapsed" />
<Setter Target="CalendarTypeSelector.Visibility" Value="Collapsed" />
<Setter Target="CalendarView.IsEnabled" Value="False" />
<Setter Target="RangeSummaryCard.IsEnabled" Value="False" />
<Setter Target="CalendarHostListView.IsEnabled" Value="False" />
</VisualState.Setters>
<VisualState.StateTriggers>
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
@@ -11,6 +12,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.MenuItems;
using Wino.Core.Domain.Models;
using Wino.Core.Domain.Models.Calendar;
using Wino.Mail.Views.Abstract;
using Wino.Messaging.Client.Calendar;
using Windows.System;
@@ -22,6 +24,7 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
{
private const string STATE_HorizontalCalendar = "HorizontalCalendar";
private const string STATE_VerticalCalendar = "VerticalCalendar";
private bool _isSynchronizingVisibleDateRangeCalendar;
public Frame GetShellFrame() => InnerShellFrame;
@@ -29,12 +32,14 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
{
InitializeComponent();
ViewModel.PropertyChanged += ViewModelPropertyChanged;
ManageCalendarDisplayType(ViewModel.StatePersistenceService.CalendarDisplayType);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
UpdateNavigationPaneLayout(navigationView.DisplayMode);
SynchronizeVisibleDateRangeCalendar();
}
private void ManageCalendarDisplayType(Core.Domain.Enums.CalendarDisplayType displayType)
@@ -49,9 +54,11 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
}
}
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
private void PreviousDateClicked(object sender, RoutedEventArgs e)
=> ViewModel.PreviousDateRangeCommand.Execute(null);
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
private void NextDateClicked(object sender, RoutedEventArgs e)
=> ViewModel.NextDateRangeCommand.Execute(null);
private async void NewCalendarEventNavigationItemTapped(object sender, TappedRoutedEventArgs e)
{
@@ -59,6 +66,33 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
await InvokeNewCalendarEventAsync();
}
private void VisibleDateRangeCalendarViewSelectedDatesChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args)
{
if (_isSynchronizingVisibleDateRangeCalendar)
return;
DateTimeOffset? interactedDate = null;
if (args.AddedDates.Count > 0)
{
interactedDate = args.AddedDates[0];
}
else if (args.RemovedDates.Count > 0)
{
interactedDate = args.RemovedDates[0];
}
if (interactedDate is null)
return;
var clickedArgs = new CalendarViewDayClickedEventArgs(interactedDate.Value.DateTime);
if (ViewModel.DateClickedCommand.CanExecute(clickedArgs))
{
ViewModel.DateClickedCommand.Execute(clickedArgs);
}
}
private async void NewCalendarEventNavigationItemKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key is not (VirtualKey.Enter or VirtualKey.Space))
@@ -102,6 +136,15 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
public void Receive(CalendarDisplayTypeChangedMessage message)
{
ManageCalendarDisplayType(message.NewDisplayType);
SynchronizeVisibleDateRangeCalendar();
}
private void ViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(ViewModel.CurrentVisibleRange) or nameof(ViewModel.VisibleDateRangeText))
{
SynchronizeVisibleDateRangeCalendar();
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
@@ -226,4 +269,39 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
_ => key.ToString()
};
}
private void SynchronizeVisibleDateRangeCalendar()
{
if (!DispatcherQueue.HasThreadAccess)
{
var enqueued = DispatcherQueue.TryEnqueue(SynchronizeVisibleDateRangeCalendar);
if (!enqueued)
throw new InvalidOperationException("Could not marshal visible date range calendar synchronization onto the UI thread.");
return;
}
_isSynchronizingVisibleDateRangeCalendar = true;
try
{
VisibleDateRangeCalendarView.SelectedDates.Clear();
var currentRange = ViewModel.CurrentVisibleRange;
if (currentRange == null)
return;
foreach (var date in currentRange.Dates)
{
VisibleDateRangeCalendarView.SelectedDates.Add(new DateTimeOffset(date.ToDateTime(TimeOnly.MinValue)));
}
VisibleDateRangeCalendarView.SetDisplayDate(new DateTimeOffset(currentRange.AnchorDate.ToDateTime(TimeOnly.MinValue)));
}
finally
{
_isSynchronizingVisibleDateRangeCalendar = false;
}
}
}
File diff suppressed because one or more lines are too long
@@ -1,210 +1,42 @@
using System;
using System;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Navigation;
using Wino.Calendar.Args;
using Wino.Calendar.Views.Abstract;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Calendar;
using Wino.Messaging.Client.Calendar;
namespace Wino.Calendar.Views;
public sealed partial class CalendarPage : CalendarPageAbstract,
IRecipient<ScrollToDateMessage>,
IRecipient<ScrollToHourMessage>,
IRecipient<GoNextDateRequestedMessage>,
IRecipient<GoPreviousDateRequestedMessage>
public sealed partial class CalendarPage : CalendarPageAbstract
{
private const int PopupDialogOffset = 12;
public CalendarPage()
{
InitializeComponent();
ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged;
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
ViewModel.DetailsShowCalendarItemChanged -= CalendarItemDetailContextChanged;
QuickEventPopupDialog.IsOpen = false;
EventDetailsPopup.IsOpen = false;
EventDetailsPopup.PlacementTarget = null;
CalendarControl.ResetTimelineSelection();
if (CalendarControl is IDisposable disposableCalendarControl)
{
disposableCalendarControl.Dispose();
}
Bindings.StopTracking();
}
private void CalendarItemDetailContextChanged(object? sender, EventArgs e)
{
if (ViewModel.DisplayDetailsCalendarItemViewModel != null)
{
var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel);
if (control != null)
{
EventDetailsPopup.PlacementTarget = control;
}
}
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<ScrollToDateMessage>(this);
WeakReferenceMessenger.Default.Register<ScrollToHourMessage>(this);
WeakReferenceMessenger.Default.Register<GoNextDateRequestedMessage>(this);
WeakReferenceMessenger.Default.Register<GoPreviousDateRequestedMessage>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<ScrollToDateMessage>(this);
WeakReferenceMessenger.Default.Unregister<ScrollToHourMessage>(this);
WeakReferenceMessenger.Default.Unregister<GoNextDateRequestedMessage>(this);
WeakReferenceMessenger.Default.Unregister<GoPreviousDateRequestedMessage>(this);
}
public void Receive(ScrollToHourMessage message) => DispatcherQueue.TryEnqueue(() => CalendarControl.NavigateToHour(message.TimeSpan));
public void Receive(ScrollToDateMessage message) => DispatcherQueue.TryEnqueue(() => CalendarControl.NavigateToDay(message.Date));
public void Receive(GoNextDateRequestedMessage message) => DispatcherQueue.TryEnqueue(() => CalendarControl.GoNextRange());
public void Receive(GoPreviousDateRequestedMessage message) => DispatcherQueue.TryEnqueue(() => CalendarControl.GoPreviousRange());
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.Back)
if (e.NavigationMode == NavigationMode.Back && ViewModel.RestoreVisibleState())
{
if (ViewModel.RestoreVisibleState())
{
var restoreDate = ViewModel.GetRestoreDate();
DispatcherQueue.TryEnqueue(() => CalendarControl.NavigateToDay(restoreDate));
}
else
{
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(DateTime.Now.Date, CalendarInitInitiative.App));
}
return;
}
if (e.Parameter is CalendarPageNavigationArgs args)
var anchorDate = DateOnly.FromDateTime(DateTime.Now.Date);
if (e.Parameter is CalendarPageNavigationArgs args && !args.RequestDefaultNavigation)
{
if (args.RequestDefaultNavigation)
{
// Go today.
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(DateTime.Now.Date, CalendarInitInitiative.App));
}
else
{
// Go specified date.
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(args.NavigationDate, CalendarInitInitiative.User));
}
}
}
private void CellSelected(object sender, TimelineCellSelectedArgs e)
{
// Dismiss event details if exists and cancel the selection.
// This is to prevent the event details from being displayed when the user clicks somewhere else.
if (EventDetailsPopup.IsOpen)
{
CalendarControl.UnselectActiveTimelineCell();
ViewModel.DisplayDetailsCalendarItemViewModel = null;
return;
anchorDate = DateOnly.FromDateTime(args.NavigationDate.Date);
}
ViewModel.SelectedQuickEventDate = e.ClickedDate;
TeachingTipPositionerGrid.Width = e.CellSize.Width;
TeachingTipPositionerGrid.Height = e.CellSize.Height;
Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X);
Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y);
// Adjust the start and end time in the flyout.
var startTime = ViewModel.SelectedQuickEventDate.Value.TimeOfDay;
var endTime = startTime.Add(TimeSpan.FromMinutes(30));
ViewModel.SelectQuickEventTimeRange(startTime, endTime);
QuickEventPopupDialog.IsOpen = true;
}
private void CellUnselected(object sender, TimelineCellUnselectedArgs e)
{
QuickEventPopupDialog.IsOpen = false;
}
private void QuickEventAccountSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
QuickEventAccountSelectorFlyout.Hide();
}
private void QuickEventPopupClosed(object sender, object e)
{
// Reset the timeline selection when the tip is closed.
CalendarControl.ResetTimelineSelection();
}
private void PopupPlacementChanged(object sender, object e)
{
if (sender is Popup senderPopup)
{
// When the quick event Popup is positioned for different calendar types,
// we must adjust the offset to make sure the tip is not hidden and has nice
// spacing from the cell.
switch (senderPopup.ActualPlacement)
{
case PopupPlacementMode.Top:
senderPopup.VerticalOffset = PopupDialogOffset * -1;
break;
case PopupPlacementMode.Bottom:
senderPopup.VerticalOffset = PopupDialogOffset;
break;
case PopupPlacementMode.Left:
senderPopup.HorizontalOffset = PopupDialogOffset * -1;
break;
case PopupPlacementMode.Right:
senderPopup.HorizontalOffset = PopupDialogOffset;
break;
default:
break;
}
}
}
private void StartTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
=> ViewModel.SelectedStartTimeString = args.Text;
private void EndTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
=> ViewModel.SelectedEndTimeString = args.Text;
private void EventDetailsPopupClosed(object sender, object e)
{
ViewModel.DisplayDetailsCalendarItemViewModel = null;
}
private void CalendarScrolling(object sender, EventArgs e)
{
// In case of scrolling, we must dismiss the event details dialog.
ViewModel.DisplayDetailsCalendarItemViewModel = null;
var request = new CalendarDisplayRequest(ViewModel.StatePersistanceService.CalendarDisplayType, anchorDate);
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(request));
}
}
@@ -1,6 +1,6 @@
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using CommunityToolkit.WinUI.Controls;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Settings;
using Wino.Core.ViewModels.Data;
@@ -51,11 +51,6 @@ public sealed partial class SettingOptionsPage : SettingOptionsPageAbstract
ViewModel.NavigateToAddAccount();
}
private void WinoAccountManagementClicked(object sender, RoutedEventArgs e)
{
ViewModel.NavigateToWinoAccountManagementCommand.Execute(null);
}
private void SettingsSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput || string.IsNullOrWhiteSpace(sender.Text))
+7 -3
View File
@@ -3,7 +3,6 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Views.Abstract"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -93,8 +92,13 @@
Grid.Column="1"
VerticalAlignment="Center"
Spacing="8">
<controls:MarkdownTextBlock Text="{x:Bind Title, Mode=OneTime}" />
<controls:MarkdownTextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind Description, Mode=OneTime}" />
<TextBlock
Text="{x:Bind Title, Mode=OneTime}"
TextWrapping="WrapWholeWords" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Description, Mode=OneTime}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Grid>
</DataTemplate>
+20 -5
View File
@@ -568,11 +568,26 @@
Text="{x:Bind domain:Translator.CalendarEventCompose_NewEventButton, Mode=OneTime}" />
</coreControls:WinoNavigationViewItem>
<calendarControls:WinoCalendarView
x:Name="CalendarView"
<Border
x:Name="RangeSummaryCard"
Grid.Row="1"
HorizontalAlignment="Center"
TodayBackgroundColor="{ThemeResource SystemAccentColor}" />
Margin="16,0,16,12"
Padding="12"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8">
<StackPanel Spacing="12">
<TextBlock
FontWeight="SemiBold"
Text="{x:Bind ViewModel.CalendarClient.VisibleDateRangeText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
<muxc:CalendarView
x:Name="VisibleDateRangeCalendarView"
HorizontalAlignment="Stretch"
SelectionMode="Multiple"
SelectedDatesChanged="VisibleDateRangeCalendarViewSelectedDatesChanged" />
</StackPanel>
</Border>
<ListView
x:Name="CalendarHostListView"
@@ -764,7 +779,7 @@
<VisualState.Setters>
<Setter Target="ShellContentArea.Visibility" Value="Collapsed" />
<Setter Target="CalendarTypeSelector.Visibility" Value="Collapsed" />
<Setter Target="CalendarView.IsEnabled" Value="False" />
<Setter Target="RangeSummaryCard.IsEnabled" Value="False" />
<Setter Target="CalendarHostListView.IsEnabled" Value="False" />
</VisualState.Setters>
</VisualState>
+74 -13
View File
@@ -12,19 +12,18 @@ using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.System;
using Wino.Calendar.Controls;
using Wino.Calendar.Views;
using Wino.Calendar.ViewModels;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.MenuItems;
using Wino.Core.Domain.Models;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Navigation;
using Wino.Mail.ViewModels;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI.Controls;
using Wino.MenuFlyouts;
@@ -34,10 +33,7 @@ using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Mails;
using Wino.Messaging.Client.Shell;
using Wino.Messaging.UI;
using Wino.Views.Mail;
using Wino.Views;
using Wino.Views.Settings;
using Windows.System;
namespace Wino.Mail.WinUI.Views;
@@ -55,6 +51,7 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
private const string StateEventDetailsContent = "EventDetailsContentState";
private WinoApplicationMode? _activeMode;
private bool _isSyncingNavigationViewSelection;
private bool _isSynchronizingVisibleDateRangeCalendar;
public WinoAppShell()
{
@@ -203,7 +200,7 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
if (ViewModel.IsCalendarMode)
{
ViewModel.StatePersistenceService.CoreWindowTitle = string.Empty;
ViewModel.StatePersistenceService.CoreWindowTitle = ViewModel.CalendarClient.VisibleDateRangeText ?? string.Empty;
return;
}
@@ -227,7 +224,6 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
private void InitializeCalendarControls()
{
CalendarTypeSelector.TodayClickedCommand = ViewModel.CalendarClient.TodayClickedCommand;
CalendarView.DateClickedCommand = ViewModel.CalendarClient.DateClickedCommand;
DayHeaderNavigationItemsFlipView.ItemsSource = ViewModel.CalendarClient.DateNavigationHeaderItems;
CalendarHostListView.ItemsSource = ViewModel.CalendarClient.GroupedAccountCalendars;
@@ -239,8 +235,8 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
DayHeaderNavigationItemsFlipView.ItemsSource = ViewModel.CalendarClient.DateNavigationHeaderItems;
DayHeaderNavigationItemsFlipView.SelectedIndex = ViewModel.CalendarClient.SelectedDateNavigationHeaderIndex;
CalendarTypeSelector.DisplayDayCount = ViewModel.CalendarClient.StatePersistenceService.DayDisplayCount;
CalendarView.HighlightedDateRange = ViewModel.CalendarClient.HighlightedDateRange;
CalendarHostListView.ItemsSource = ViewModel.CalendarClient.GroupedAccountCalendars;
SynchronizeVisibleDateRangeCalendar();
}
private void RefreshNavigationViewBindings(bool syncMailSelection = true)
@@ -283,9 +279,11 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
await InvokeNewCalendarEventAsync();
}
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
private void PreviousDateClicked(object sender, RoutedEventArgs e)
=> ViewModel.CalendarClient.PreviousDateRangeCommand.Execute(null);
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
private void NextDateClicked(object sender, RoutedEventArgs e)
=> ViewModel.CalendarClient.NextDateRangeCommand.Execute(null);
private Task InvokeNewCalendarEventAsync()
=> ViewModel.CalendarClient.HandleNavigationItemInvokedAsync(new NewCalendarEventMenuItem());
@@ -512,13 +510,76 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
return;
}
if (e.PropertyName == nameof(ICalendarShellClient.HighlightedDateRange))
if (e.PropertyName == nameof(ICalendarShellClient.CurrentVisibleRange) ||
e.PropertyName == nameof(ICalendarShellClient.VisibleDateRangeText))
{
CalendarView.HighlightedDateRange = ViewModel.CalendarClient.HighlightedDateRange;
SynchronizeVisibleDateRangeCalendar();
UpdateTitleBarSubtitle();
}
}
private void VisibleDateRangeCalendarViewSelectedDatesChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args)
{
if (_isSynchronizingVisibleDateRangeCalendar)
return;
DateTimeOffset? interactedDate = null;
if (args.AddedDates.Count > 0)
{
interactedDate = args.AddedDates[0];
}
else if (args.RemovedDates.Count > 0)
{
interactedDate = args.RemovedDates[0];
}
if (interactedDate is null)
return;
var clickedArgs = new CalendarViewDayClickedEventArgs(interactedDate.Value.DateTime);
if (ViewModel.CalendarClient.DateClickedCommand.CanExecute(clickedArgs))
{
ViewModel.CalendarClient.DateClickedCommand.Execute(clickedArgs);
}
}
private void SynchronizeVisibleDateRangeCalendar()
{
if (!DispatcherQueue.HasThreadAccess)
{
var enqueued = DispatcherQueue.TryEnqueue(SynchronizeVisibleDateRangeCalendar);
if (!enqueued)
throw new InvalidOperationException("Could not marshal visible date range calendar synchronization onto the UI thread.");
return;
}
_isSynchronizingVisibleDateRangeCalendar = true;
try
{
VisibleDateRangeCalendarView.SelectedDates.Clear();
var currentRange = ViewModel.CalendarClient.CurrentVisibleRange;
if (currentRange == null)
return;
foreach (var date in currentRange.Dates)
{
VisibleDateRangeCalendarView.SelectedDates.Add(new DateTimeOffset(date.ToDateTime(TimeOnly.MinValue)));
}
VisibleDateRangeCalendarView.SetDisplayDate(new DateTimeOffset(currentRange.AnchorDate.ToDateTime(TimeOnly.MinValue)));
}
finally
{
_isSynchronizingVisibleDateRangeCalendar = false;
}
}
private void ViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(ViewModel.SelectedMenuItem) || !ViewModel.CurrentClient.HandlesNavigationSelection)
-14
View File
@@ -119,10 +119,7 @@
<None Remove="Styles\CalendarShellNavigationViewStyle.xaml" />
<None Remove="Styles\CalendarThemeResources.xaml" />
<None Remove="Styles\DayHeaderControl.xaml" />
<None Remove="Styles\WinoCalendarResources.xaml" />
<None Remove="Styles\WinoCalendarTypeSelectorControl.xaml" />
<None Remove="Styles\WinoCalendarView.xaml" />
<None Remove="Styles\WinoDayTimelineCanvas.xaml" />
<None Remove="Views\Calendar\CalendarAppShell.xaml" />
<None Remove="Views\Calendar\CalendarPage.xaml" />
<None Remove="Views\Calendar\CalendarSettingsPage.xaml" />
@@ -205,10 +202,8 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.TabbedCommandBar" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" />
<PackageReference Include="Microsoft.Graphics.Win2D" />
<PackageReference Include="SkiaSharp.Views.WinUI" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
@@ -362,18 +357,9 @@
<Page Update="Styles\DayHeaderControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\WinoCalendarResources.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\WinoCalendarTypeSelectorControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\WinoCalendarView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\WinoDayTimelineCanvas.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\Calendar\CalendarAppShell.xaml">