Outlook calendar/event syncing basics without delta. Bunch of UI updates for the calendar view.

This commit is contained in:
Burak Kaan Köse
2025-01-06 02:15:21 +01:00
parent a7674d436d
commit 125c277c88
46 changed files with 1104 additions and 356 deletions

View File

@@ -224,7 +224,7 @@ namespace Wino.Calendar.ViewModels
{
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
{
AccountId = Guid.Parse("52fae547-0740-4aa3-8d51-519bd31278ca"),
AccountId = Guid.Parse("bd0fc1ab-168a-436d-86ce-0661c0eabaf9"),
Type = CalendarSynchronizationType.CalendarMetadata
}, SynchronizationSource.Client);

View File

@@ -79,8 +79,8 @@ namespace Wino.Calendar.ViewModels
[NotifyCanExecuteChangedFor(nameof(SaveQuickEventCommand))]
private string _eventName;
public DateTime QuickEventStartTime => SelectedQuickEventDate.Value.Date.Add(_currentSettings.GetTimeSpan(SelectedStartTimeString).Value);
public DateTime QuickEventEndTime => SelectedQuickEventDate.Value.Date.Add(_currentSettings.GetTimeSpan(SelectedEndTimeString).Value);
public DateTime QuickEventStartTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedStartTimeString).Value);
public DateTime QuickEventEndTime => SelectedQuickEventDate.Value.Date.Add(CurrentSettings.GetTimeSpan(SelectedEndTimeString).Value);
public bool CanSaveQuickEvent => SelectedQuickEventAccountCalendar != null &&
!string.IsNullOrWhiteSpace(EventName) &&
@@ -90,6 +90,8 @@ namespace Wino.Calendar.ViewModels
#endregion
#region Data Initialization
[ObservableProperty]
private DayRangeCollection _dayRanges = [];
@@ -102,6 +104,20 @@ namespace Wino.Calendar.ViewModels
[ObservableProperty]
private bool _isCalendarEnabled = true;
#endregion
#region Event Details
public event EventHandler DetailsShowCalendarItemChanged;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsEventDetailsVisible))]
private CalendarItemViewModel _displayDetailsCalendarItemViewModel;
public bool IsEventDetailsVisible => DisplayDetailsCalendarItemViewModel != null;
#endregion
// TODO: Get rid of some of the items if we have too many.
private const int maxDayRangeSize = 10;
@@ -115,7 +131,9 @@ namespace Wino.Calendar.ViewModels
private SemaphoreSlim _calendarLoadingSemaphore = new(1);
private bool isLoadMoreBlocked = false;
private CalendarSettings _currentSettings = null;
[ObservableProperty]
private CalendarSettings _currentSettings;
public IStatePersistanceService StatePersistanceService { get; }
public IAccountCalendarStateService AccountCalendarStateService { get; }
@@ -148,6 +166,8 @@ namespace Wino.Calendar.ViewModels
var days = dayRangeRenderModels.SelectMany(a => a.CalendarDays);
days.ForEach(a => a.EventsCollection.FilterByCalendars(AccountCalendarStateService.ActiveCalendars.Select(a => a.Id)));
DisplayDetailsCalendarItemViewModel = null;
}
// TODO: Replace when calendar settings are updated.
@@ -156,8 +176,8 @@ namespace Wino.Calendar.ViewModels
{
return displayType switch
{
CalendarDisplayType.Day => new DayCalendarDrawingStrategy(_currentSettings),
CalendarDisplayType.Week => new WeekCalendarDrawingStrategy(_currentSettings),
CalendarDisplayType.Day => new DayCalendarDrawingStrategy(CurrentSettings),
CalendarDisplayType.Week => new WeekCalendarDrawingStrategy(CurrentSettings),
_ => throw new NotImplementedException(),
};
}
@@ -178,6 +198,26 @@ namespace Wino.Calendar.ViewModels
SelectedQuickEventAccountCalendar = AccountCalendarStateService.ActiveCalendars.FirstOrDefault(a => a.IsPrimary);
}
[RelayCommand]
private void NavigateSeries()
{
}
[RelayCommand]
private void NavigateEventDetails()
{
if (DisplayDetailsCalendarItemViewModel == null) return;
NavigateEvent(DisplayDetailsCalendarItemViewModel);
}
[RelayCommand]
private void NavigateEvent(CalendarItemViewModel calendarItemViewModel)
{
// Double tap or clicked 'view details' of the event detail popup.
}
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
private async Task SaveQuickEventAsync()
{
@@ -211,13 +251,33 @@ namespace Wino.Calendar.ViewModels
{
IsAllDay = false;
SelectedStartTimeString = _currentSettings.GetTimeString(startTime);
SelectedEndTimeString = _currentSettings.GetTimeString(endTime);
SelectedStartTimeString = CurrentSettings.GetTimeString(startTime);
SelectedEndTimeString = CurrentSettings.GetTimeString(endTime);
}
// Manage event detail popup context and select-unselect the proper items.
// Item selection rules are defined in the selection method.
partial void OnDisplayDetailsCalendarItemViewModelChanging(CalendarItemViewModel oldValue, CalendarItemViewModel newValue)
{
if (oldValue != null)
{
UnselectCalendarItem(oldValue);
}
if (newValue != null)
{
SelectCalendarItem(newValue);
}
}
// Notify view that the detail context changed.
// This will align the event detail popup to the selected event.
partial void OnDisplayDetailsCalendarItemViewModelChanged(CalendarItemViewModel value)
=> DetailsShowCalendarItemChanged?.Invoke(this, EventArgs.Empty);
private void RefreshSettings()
{
_currentSettings = _preferencesService.GetCurrentCalendarSettings();
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
// Populate the hour selection strings.
var timeStrings = new List<string>();
@@ -228,7 +288,7 @@ namespace Wino.Calendar.ViewModels
{
var time = new DateTime(1, 1, 1, hour, minute, 0);
if (_currentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour)
if (CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour)
{
timeStrings.Add(time.ToString("HH:mm"));
}
@@ -255,7 +315,6 @@ namespace Wino.Calendar.ViewModels
// 2. Day display count is different.
// 3. Display date is not in the visible range.
if (DayRanges.DisplayRange == null) return false;
return
@@ -289,6 +348,9 @@ namespace Wino.Calendar.ViewModels
await RenderDatesAsync(message.CalendarInitInitiative,
message.DisplayDate,
CalendarLoadDirection.Replace);
// Scroll to the current hour.
Messenger.Send(new ScrollToHourMessage(TimeSpan.FromHours(DateTime.Now.Hour)));
}
catch (Exception ex)
{
@@ -403,7 +465,7 @@ namespace Wino.Calendar.ViewModels
var endDate = startDate.AddDays(eachFlipItemCount);
var range = new DateRange(startDate, endDate);
var renderOptions = new CalendarRenderOptions(range, _currentSettings);
var renderOptions = new CalendarRenderOptions(range, CurrentSettings);
var dayRangeHeaderModel = new DayRangeRenderModel(renderOptions);
renderModels.Add(dayRangeHeaderModel);
@@ -613,14 +675,9 @@ namespace Wino.Calendar.ViewModels
}
}
partial void OnSelectedQuickEventDateChanged(DateTime? value)
{
}
partial void OnSelectedStartTimeStringChanged(string newValue)
{
var parsedTime = _currentSettings.GetTimeSpan(newValue);
var parsedTime = CurrentSettings.GetTimeSpan(newValue);
if (parsedTime == null)
{
@@ -634,7 +691,7 @@ namespace Wino.Calendar.ViewModels
partial void OnSelectedEndTimeStringChanged(string newValue)
{
var parsedTime = _currentSettings.GetTimeSpan(newValue);
var parsedTime = CurrentSettings.GetTimeSpan(newValue);
if (parsedTime == null)
{
@@ -648,6 +705,8 @@ namespace Wino.Calendar.ViewModels
partial void OnSelectedDayRangeChanged(DayRangeRenderModel value)
{
DisplayDetailsCalendarItemViewModel = null;
if (DayRanges.Count == 0 || SelectedDateRangeIndex < 0) return;
var selectedRange = DayRanges[SelectedDateRangeIndex];
@@ -699,71 +758,63 @@ namespace Wino.Calendar.ViewModels
// Messenger.Send(new LoadCalendarMessage(DateTime.UtcNow.Date, CalendarInitInitiative.App, true));
}
private IEnumerable<CalendarItemViewModel> GetCalendarItems(Guid calendarItemId)
private IEnumerable<CalendarItemViewModel> GetCalendarItems(CalendarItemViewModel calendarItemViewModel, CalendarDayModel selectedDay)
{
// Multi-day events are sprated in multiple days.
// All-day and multi-day events are selected collectively.
// Recurring events must be selected as a single instance.
// We need to find the day that the event is in, and then select the event.
return DayRanges
.SelectMany(a => a.CalendarDays)
.Select(b => b.EventsCollection.GetCalendarItem(calendarItemId))
.Where(c => c != null)
.Cast<CalendarItemViewModel>()
.Distinct();
}
private void ResetSelectedItems()
{
foreach (var item in AccountCalendarStateService.SelectedItems)
if (calendarItemViewModel.IsSingleExceptionalInstance)
{
var items = GetCalendarItems(item.Id);
foreach (var childItem in items)
{
childItem.IsSelected = false;
}
return [calendarItemViewModel];
}
else
{
return DayRanges
.SelectMany(a => a.CalendarDays)
.Select(b => b.EventsCollection.GetCalendarItem(calendarItemViewModel.Id))
.Where(c => c != null)
.Cast<CalendarItemViewModel>()
.Distinct();
}
AccountCalendarStateService.SelectedItems.Clear();
}
public async void Receive(CalendarItemTappedMessage message)
private void UnselectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null)
{
if (calendarItemViewModel == null) return;
var itemsToUnselect = GetCalendarItems(calendarItemViewModel, calendarDay);
foreach (var item in itemsToUnselect)
{
item.IsSelected = false;
}
}
private void SelectCalendarItem(CalendarItemViewModel calendarItemViewModel, CalendarDayModel calendarDay = null)
{
if (calendarItemViewModel == null) return;
var itemsToSelect = GetCalendarItems(calendarItemViewModel, calendarDay);
foreach (var item in itemsToSelect)
{
item.IsSelected = true;
}
}
public void Receive(CalendarItemTappedMessage message)
{
if (message.CalendarItemViewModel == null) return;
await ExecuteUIThread(() =>
{
var calendarItems = GetCalendarItems(message.CalendarItemViewModel.Id);
if (!_keyPressService.IsCtrlKeyPressed())
{
ResetSelectedItems();
}
foreach (var item in calendarItems)
{
item.IsSelected = !item.IsSelected;
// Multi-select logic.
if (item.IsSelected && !AccountCalendarStateService.SelectedItems.Contains(item))
{
AccountCalendarStateService.SelectedItems.Add(message.CalendarItemViewModel);
}
else if (!item.IsSelected && AccountCalendarStateService.SelectedItems.Contains(item))
{
AccountCalendarStateService.SelectedItems.Remove(item);
}
}
});
DisplayDetailsCalendarItemViewModel = message.CalendarItemViewModel;
}
public void Receive(CalendarItemDoubleTappedMessage message)
{
// TODO: Navigate to the event details page.
}
public void Receive(CalendarItemDoubleTappedMessage message) => NavigateEvent(message.CalendarItemViewModel);
public void Receive(CalendarItemRightTappedMessage message)
{
}
public async void Receive(CalendarItemDeleted message)
@@ -777,9 +828,7 @@ namespace Wino.Calendar.ViewModels
// Event might be spreaded into multiple days.
// Remove from all.
var calendarItems = GetCalendarItems(deletedItem.Id);
// var calendarItems = GetCalendarItems(deletedItem.Id);
});
}
}

View File

@@ -21,9 +21,6 @@ namespace Wino.Calendar.ViewModels
[ObservableProperty]
private bool _is24HourHeaders;
[ObservableProperty]
private bool _ghostRenderAllDayEvents;
[ObservableProperty]
private TimeSpan _workingHourStart;
@@ -64,7 +61,6 @@ namespace Wino.Calendar.ViewModels
_workingHourStart = preferencesService.WorkingHourStart;
_workingHourEnd = preferencesService.WorkingHourEnd;
_cellHourHeight = preferencesService.HourHeight;
_ghostRenderAllDayEvents = preferencesService.GhostRenderAllDayEvents;
_workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart));
_workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd));
@@ -79,7 +75,6 @@ namespace Wino.Calendar.ViewModels
partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings();
partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings();
partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings();
partial void OnGhostRenderAllDayEventsChanged(bool value) => SaveSettings();
public void SaveSettings()
{

View File

@@ -24,11 +24,13 @@ namespace Wino.Calendar.ViewModels.Data
public ITimePeriod Period => CalendarItem.Period;
public bool IsAllDayEvent => ((ICalendarItem)CalendarItem).IsAllDayEvent;
public bool IsAllDayEvent => CalendarItem.IsAllDayEvent;
public bool IsMultiDayEvent => ((ICalendarItem)CalendarItem).IsMultiDayEvent;
public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent;
public bool IsRecurringEvent => !string.IsNullOrEmpty(CalendarItem.Recurrence);
public bool IsRecurringEvent => CalendarItem.IsRecurringEvent;
public bool IsSingleExceptionalInstance => CalendarItem.IsSingleExceptionalInstance;
[ObservableProperty]
private bool _isSelected;

View File

@@ -22,9 +22,6 @@ namespace Wino.Calendar.ViewModels.Interfaces
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
ObservableCollection<CalendarItemViewModel> SelectedItems { get; }
bool HasMultipleSelectedItems { get; }
/// <summary>
/// Enumeration of currently selected calendars.
/// </summary>

View File

@@ -1,14 +1,17 @@
using Wino.Calendar.ViewModels.Data;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Calendar.ViewModels.Messages
{
public class CalendarItemTappedMessage
{
public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel)
public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod)
{
CalendarItemViewModel = calendarItemViewModel;
ClickedPeriod = clickedPeriod;
}
public CalendarItemViewModel CalendarItemViewModel { get; }
public CalendarDayModel ClickedPeriod { get; }
}
}