dotnet format refactorings.
This commit is contained in:
@@ -21,347 +21,346 @@ using Wino.Messaging.Client.Calendar;
|
|||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels
|
namespace Wino.Calendar.ViewModels;
|
||||||
|
|
||||||
|
public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||||
|
IRecipient<VisibleDateRangeChangedMessage>,
|
||||||
|
IRecipient<CalendarEnableStatusChangedMessage>,
|
||||||
|
IRecipient<NavigateManageAccountsRequested>,
|
||||||
|
IRecipient<CalendarDisplayTypeChangedMessage>,
|
||||||
|
IRecipient<DetailsPageStateChangedMessage>
|
||||||
{
|
{
|
||||||
public partial class AppShellViewModel : CalendarBaseViewModel,
|
public IPreferencesService PreferencesService { get; }
|
||||||
IRecipient<VisibleDateRangeChangedMessage>,
|
public IStatePersistanceService StatePersistenceService { get; }
|
||||||
IRecipient<CalendarEnableStatusChangedMessage>,
|
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||||
IRecipient<NavigateManageAccountsRequested>,
|
public INavigationService NavigationService { get; }
|
||||||
IRecipient<CalendarDisplayTypeChangedMessage>,
|
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
||||||
IRecipient<DetailsPageStateChangedMessage>
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isEventDetailsPageActive;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _selectedMenuItemIndex = -1;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool isCalendarEnabled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the active connection status of the Wino server.
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private WinoServerConnectionStatus activeConnectionStatus;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the display date of the calendar.
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private DateTimeOffset _displayDate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView.
|
||||||
|
/// </summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private DateRange highlightedDateRange;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableRangeCollection<string> dateNavigationHeaderItems = [];
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _selectedDateNavigationHeaderIndex;
|
||||||
|
|
||||||
|
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
|
||||||
|
|
||||||
|
// For updating account calendars asynchronously.
|
||||||
|
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
|
||||||
|
|
||||||
|
public AppShellViewModel(IPreferencesService preferencesService,
|
||||||
|
IStatePersistanceService statePersistanceService,
|
||||||
|
IAccountService accountService,
|
||||||
|
ICalendarService calendarService,
|
||||||
|
IAccountCalendarStateService accountCalendarStateService,
|
||||||
|
INavigationService navigationService,
|
||||||
|
IWinoServerConnectionManager serverConnectionManager)
|
||||||
{
|
{
|
||||||
public IPreferencesService PreferencesService { get; }
|
_accountService = accountService;
|
||||||
public IStatePersistanceService StatePersistenceService { get; }
|
_calendarService = calendarService;
|
||||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
|
||||||
public INavigationService NavigationService { get; }
|
|
||||||
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
AccountCalendarStateService = accountCalendarStateService;
|
||||||
private bool _isEventDetailsPageActive;
|
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||||
|
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
||||||
|
|
||||||
[ObservableProperty]
|
NavigationService = navigationService;
|
||||||
private int _selectedMenuItemIndex = -1;
|
ServerConnectionManager = serverConnectionManager;
|
||||||
|
PreferencesService = preferencesService;
|
||||||
|
|
||||||
[ObservableProperty]
|
StatePersistenceService = statePersistanceService;
|
||||||
private bool isCalendarEnabled;
|
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||||
/// Gets or sets the active connection status of the Wino server.
|
{
|
||||||
/// </summary>
|
throw new NotImplementedException();
|
||||||
[ObservableProperty]
|
}
|
||||||
private WinoServerConnectionStatus activeConnectionStatus;
|
|
||||||
|
|
||||||
/// <summary>
|
private void PrefefencesChanged(object sender, string e)
|
||||||
/// Gets or sets the display date of the calendar.
|
{
|
||||||
/// </summary>
|
if (e == nameof(StatePersistenceService.CalendarDisplayType))
|
||||||
[ObservableProperty]
|
|
||||||
private DateTimeOffset _displayDate;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView.
|
|
||||||
/// </summary>
|
|
||||||
[ObservableProperty]
|
|
||||||
private DateRange highlightedDateRange;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private ObservableRangeCollection<string> dateNavigationHeaderItems = [];
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private int _selectedDateNavigationHeaderIndex;
|
|
||||||
|
|
||||||
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
|
|
||||||
|
|
||||||
// For updating account calendars asynchronously.
|
|
||||||
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
|
|
||||||
|
|
||||||
public AppShellViewModel(IPreferencesService preferencesService,
|
|
||||||
IStatePersistanceService statePersistanceService,
|
|
||||||
IAccountService accountService,
|
|
||||||
ICalendarService calendarService,
|
|
||||||
IAccountCalendarStateService accountCalendarStateService,
|
|
||||||
INavigationService navigationService,
|
|
||||||
IWinoServerConnectionManager serverConnectionManager)
|
|
||||||
{
|
{
|
||||||
_accountService = accountService;
|
Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType));
|
||||||
_calendarService = calendarService;
|
|
||||||
|
|
||||||
AccountCalendarStateService = accountCalendarStateService;
|
|
||||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
|
||||||
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
|
||||||
|
|
||||||
NavigationService = navigationService;
|
// Change the calendar.
|
||||||
ServerConnectionManager = serverConnectionManager;
|
DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
|
||||||
PreferencesService = preferencesService;
|
|
||||||
|
|
||||||
StatePersistenceService = statePersistanceService;
|
|
||||||
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
base.OnNavigatedTo(mode, parameters);
|
||||||
}
|
|
||||||
|
|
||||||
private void PrefefencesChanged(object sender, string e)
|
UpdateDateNavigationHeaderItems();
|
||||||
|
|
||||||
|
await InitializeAccountCalendarsAsync();
|
||||||
|
|
||||||
|
TodayClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
|
||||||
|
{
|
||||||
|
// When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
|
||||||
|
// Reporting all these changes one by one to the UI is not efficient and may cause problems in the future.
|
||||||
|
|
||||||
|
// Update all calendar states at once.
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (e == nameof(StatePersistenceService.CalendarDisplayType))
|
await _accountCalendarUpdateSemaphoreSlim.WaitAsync();
|
||||||
|
|
||||||
|
foreach (var calendar in e.AccountCalendars)
|
||||||
{
|
{
|
||||||
Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType));
|
await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
||||||
// Change the calendar.
|
|
||||||
DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
|
||||||
{
|
{
|
||||||
base.OnNavigatedTo(mode, parameters);
|
Log.Error(ex, "Error while waiting for account calendar update semaphore.");
|
||||||
|
|
||||||
UpdateDateNavigationHeaderItems();
|
|
||||||
|
|
||||||
await InitializeAccountCalendarsAsync();
|
|
||||||
|
|
||||||
TodayClicked();
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
|
|
||||||
{
|
{
|
||||||
// When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
|
_accountCalendarUpdateSemaphoreSlim.Release();
|
||||||
// Reporting all these changes one by one to the UI is not efficient and may cause problems in the future.
|
|
||||||
|
|
||||||
// Update all calendar states at once.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _accountCalendarUpdateSemaphoreSlim.WaitAsync();
|
|
||||||
|
|
||||||
foreach (var calendar in e.AccountCalendars)
|
|
||||||
{
|
|
||||||
await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Error while waiting for account calendar update semaphore.");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_accountCalendarUpdateSemaphoreSlim.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
|
private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
|
||||||
=> await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false);
|
=> await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false);
|
||||||
|
|
||||||
private async Task InitializeAccountCalendarsAsync()
|
private async Task InitializeAccountCalendarsAsync()
|
||||||
|
{
|
||||||
|
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());
|
||||||
|
|
||||||
|
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var account in accounts)
|
||||||
{
|
{
|
||||||
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());
|
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||||
|
var calendarViewModels = new List<AccountCalendarViewModel>();
|
||||||
|
|
||||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
foreach (var calendar in accountCalendars)
|
||||||
|
|
||||||
foreach (var account in accounts)
|
|
||||||
{
|
{
|
||||||
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
var calendarViewModel = new AccountCalendarViewModel(account, calendar);
|
||||||
var calendarViewModels = new List<AccountCalendarViewModel>();
|
|
||||||
|
|
||||||
foreach (var calendar in accountCalendars)
|
calendarViewModels.Add(calendarViewModel);
|
||||||
{
|
|
||||||
var calendarViewModel = new AccountCalendarViewModel(account, calendar);
|
|
||||||
|
|
||||||
calendarViewModels.Add(calendarViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
|
|
||||||
|
|
||||||
await Dispatcher.ExecuteOnUIThread(() =>
|
|
||||||
{
|
|
||||||
AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ForceNavigateCalendarDate()
|
|
||||||
{
|
|
||||||
if (SelectedMenuItemIndex == -1)
|
|
||||||
{
|
|
||||||
var args = new CalendarPageNavigationArgs()
|
|
||||||
{
|
|
||||||
NavigationDate = _navigationDate ?? DateTime.Now.Date
|
|
||||||
};
|
|
||||||
|
|
||||||
// Already on calendar. Just navigate.
|
|
||||||
NavigationService.Navigate(WinoPage.CalendarPage, args);
|
|
||||||
|
|
||||||
_navigationDate = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedMenuItemIndex = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
|
|
||||||
{
|
|
||||||
switch (newValue)
|
|
||||||
{
|
|
||||||
case -1:
|
|
||||||
ForceNavigateCalendarDate();
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
NavigationService.Navigate(WinoPage.ManageAccountsPage);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
NavigationService.Navigate(WinoPage.SettingsPage);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task Sync()
|
|
||||||
{
|
|
||||||
// Sync all calendars.
|
|
||||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
foreach (var account in accounts)
|
|
||||||
{
|
|
||||||
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
|
|
||||||
{
|
|
||||||
AccountId = account.Id,
|
|
||||||
Type = CalendarSynchronizationType.CalendarMetadata
|
|
||||||
}, SynchronizationSource.Client);
|
|
||||||
|
|
||||||
Messenger.Send(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When calendar type switches, we need to navigate to the most ideal date.
|
|
||||||
/// This method returns that date.
|
|
||||||
/// </summary>
|
|
||||||
private DateTime GetDisplayTypeSwitchDate()
|
|
||||||
{
|
|
||||||
var settings = PreferencesService.GetCurrentCalendarSettings();
|
|
||||||
switch (StatePersistenceService.CalendarDisplayType)
|
|
||||||
{
|
|
||||||
case CalendarDisplayType.Day:
|
|
||||||
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;
|
|
||||||
|
|
||||||
return HighlightedDateRange.StartDate;
|
|
||||||
case CalendarDisplayType.Week:
|
|
||||||
if (HighlightedDateRange == null || HighlightedDateRange.IsInRange(DateTime.Now))
|
|
||||||
{
|
|
||||||
return DateTime.Now.Date.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
|
||||||
case CalendarDisplayType.WorkWeek:
|
|
||||||
break;
|
|
||||||
case CalendarDisplayType.Month:
|
|
||||||
break;
|
|
||||||
case CalendarDisplayType.Year:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DateTime.Today.Date;
|
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
|
||||||
}
|
|
||||||
|
|
||||||
private DateTime? _navigationDate;
|
await Dispatcher.ExecuteOnUIThread(() =>
|
||||||
private readonly IAccountService _accountService;
|
|
||||||
private readonly ICalendarService _calendarService;
|
|
||||||
|
|
||||||
#region Commands
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void TodayClicked()
|
|
||||||
{
|
|
||||||
_navigationDate = DateTime.Now.Date;
|
|
||||||
|
|
||||||
ForceNavigateCalendarDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs)
|
|
||||||
{
|
|
||||||
_navigationDate = clickedDateArgs.ClickedDate;
|
|
||||||
|
|
||||||
ForceNavigateCalendarDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the header navigation items based on visible date range and calendar type.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateDateNavigationHeaderItems()
|
|
||||||
{
|
|
||||||
DateNavigationHeaderItems.Clear();
|
|
||||||
|
|
||||||
// TODO: From settings
|
|
||||||
var testInfo = new CultureInfo("en-US");
|
|
||||||
|
|
||||||
switch (StatePersistenceService.CalendarDisplayType)
|
|
||||||
{
|
{
|
||||||
case CalendarDisplayType.Day:
|
AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
|
||||||
case CalendarDisplayType.Week:
|
|
||||||
case CalendarDisplayType.WorkWeek:
|
|
||||||
case CalendarDisplayType.Month:
|
|
||||||
DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames);
|
|
||||||
break;
|
|
||||||
case CalendarDisplayType.Year:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDateNavigationHeaderItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems();
|
|
||||||
|
|
||||||
private void SetDateNavigationHeaderItems()
|
|
||||||
{
|
|
||||||
if (HighlightedDateRange == null) return;
|
|
||||||
|
|
||||||
if (DateNavigationHeaderItems.Count == 0)
|
|
||||||
{
|
|
||||||
UpdateDateNavigationHeaderItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Year view
|
|
||||||
var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex();
|
|
||||||
|
|
||||||
SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Receive(CalendarEnableStatusChangedMessage message)
|
|
||||||
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
|
|
||||||
|
|
||||||
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
|
|
||||||
|
|
||||||
public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar));
|
|
||||||
|
|
||||||
public async void Receive(DetailsPageStateChangedMessage message)
|
|
||||||
{
|
|
||||||
await ExecuteUIThread(() =>
|
|
||||||
{
|
|
||||||
IsEventDetailsPageActive = message.IsActivated;
|
|
||||||
|
|
||||||
// TODO: This is for Wino Mail. Generalize this later on.
|
|
||||||
StatePersistenceService.IsReaderNarrowed = message.IsActivated;
|
|
||||||
StatePersistenceService.IsReadingMail = message.IsActivated;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ForceNavigateCalendarDate()
|
||||||
|
{
|
||||||
|
if (SelectedMenuItemIndex == -1)
|
||||||
|
{
|
||||||
|
var args = new CalendarPageNavigationArgs()
|
||||||
|
{
|
||||||
|
NavigationDate = _navigationDate ?? DateTime.Now.Date
|
||||||
|
};
|
||||||
|
|
||||||
|
// Already on calendar. Just navigate.
|
||||||
|
NavigationService.Navigate(WinoPage.CalendarPage, args);
|
||||||
|
|
||||||
|
_navigationDate = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedMenuItemIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
|
||||||
|
{
|
||||||
|
switch (newValue)
|
||||||
|
{
|
||||||
|
case -1:
|
||||||
|
ForceNavigateCalendarDate();
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
NavigationService.Navigate(WinoPage.ManageAccountsPage);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
NavigationService.Navigate(WinoPage.SettingsPage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Sync()
|
||||||
|
{
|
||||||
|
// Sync all calendars.
|
||||||
|
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var account in accounts)
|
||||||
|
{
|
||||||
|
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
|
||||||
|
{
|
||||||
|
AccountId = account.Id,
|
||||||
|
Type = CalendarSynchronizationType.CalendarMetadata
|
||||||
|
}, SynchronizationSource.Client);
|
||||||
|
|
||||||
|
Messenger.Send(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When calendar type switches, we need to navigate to the most ideal date.
|
||||||
|
/// This method returns that date.
|
||||||
|
/// </summary>
|
||||||
|
private DateTime GetDisplayTypeSwitchDate()
|
||||||
|
{
|
||||||
|
var settings = PreferencesService.GetCurrentCalendarSettings();
|
||||||
|
switch (StatePersistenceService.CalendarDisplayType)
|
||||||
|
{
|
||||||
|
case CalendarDisplayType.Day:
|
||||||
|
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;
|
||||||
|
|
||||||
|
return HighlightedDateRange.StartDate;
|
||||||
|
case CalendarDisplayType.Week:
|
||||||
|
if (HighlightedDateRange == null || HighlightedDateRange.IsInRange(DateTime.Now))
|
||||||
|
{
|
||||||
|
return DateTime.Now.Date.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||||
|
case CalendarDisplayType.WorkWeek:
|
||||||
|
break;
|
||||||
|
case CalendarDisplayType.Month:
|
||||||
|
break;
|
||||||
|
case CalendarDisplayType.Year:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime.Today.Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? _navigationDate;
|
||||||
|
private readonly IAccountService _accountService;
|
||||||
|
private readonly ICalendarService _calendarService;
|
||||||
|
|
||||||
|
#region Commands
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void TodayClicked()
|
||||||
|
{
|
||||||
|
_navigationDate = DateTime.Now.Date;
|
||||||
|
|
||||||
|
ForceNavigateCalendarDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs)
|
||||||
|
{
|
||||||
|
_navigationDate = clickedDateArgs.ClickedDate;
|
||||||
|
|
||||||
|
ForceNavigateCalendarDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the header navigation items based on visible date range and calendar type.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDateNavigationHeaderItems()
|
||||||
|
{
|
||||||
|
DateNavigationHeaderItems.Clear();
|
||||||
|
|
||||||
|
// TODO: From settings
|
||||||
|
var testInfo = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
switch (StatePersistenceService.CalendarDisplayType)
|
||||||
|
{
|
||||||
|
case CalendarDisplayType.Day:
|
||||||
|
case CalendarDisplayType.Week:
|
||||||
|
case CalendarDisplayType.WorkWeek:
|
||||||
|
case CalendarDisplayType.Month:
|
||||||
|
DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames);
|
||||||
|
break;
|
||||||
|
case CalendarDisplayType.Year:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDateNavigationHeaderItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems();
|
||||||
|
|
||||||
|
private void SetDateNavigationHeaderItems()
|
||||||
|
{
|
||||||
|
if (HighlightedDateRange == null) return;
|
||||||
|
|
||||||
|
if (DateNavigationHeaderItems.Count == 0)
|
||||||
|
{
|
||||||
|
UpdateDateNavigationHeaderItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Year view
|
||||||
|
var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex();
|
||||||
|
|
||||||
|
SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Receive(CalendarEnableStatusChangedMessage message)
|
||||||
|
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
|
||||||
|
|
||||||
|
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
|
||||||
|
|
||||||
|
public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar));
|
||||||
|
|
||||||
|
public async void Receive(DetailsPageStateChangedMessage message)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
IsEventDetailsPageActive = message.IsActivated;
|
||||||
|
|
||||||
|
// TODO: This is for Wino Mail. Generalize this later on.
|
||||||
|
StatePersistenceService.IsReaderNarrowed = message.IsActivated;
|
||||||
|
StatePersistenceService.IsReadingMail = message.IsActivated;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,120 +8,119 @@ using Wino.Core.Domain.Translations;
|
|||||||
using Wino.Core.ViewModels;
|
using Wino.Core.ViewModels;
|
||||||
using Wino.Messaging.Client.Calendar;
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels
|
namespace Wino.Calendar.ViewModels;
|
||||||
|
|
||||||
|
public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
|
||||||
{
|
{
|
||||||
public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
|
[ObservableProperty]
|
||||||
|
private double _cellHourHeight;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _selectedFirstDayOfWeekIndex;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _is24HourHeaders;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private TimeSpan _workingHourStart;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private TimeSpan _workingHourEnd;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private List<string> _dayNames = [];
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _workingDayStartIndex;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _workingDayEndIndex;
|
||||||
|
|
||||||
|
public IPreferencesService PreferencesService { get; }
|
||||||
|
|
||||||
|
private readonly bool _isLoaded = false;
|
||||||
|
|
||||||
|
public CalendarSettingsPageViewModel(IPreferencesService preferencesService)
|
||||||
{
|
{
|
||||||
[ObservableProperty]
|
PreferencesService = preferencesService;
|
||||||
private double _cellHourHeight;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage);
|
||||||
private int _selectedFirstDayOfWeekIndex;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
var cultureInfo = new CultureInfo(currentLanguageLanguageCode);
|
||||||
private bool _is24HourHeaders;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
// Populate the day names list
|
||||||
private TimeSpan _workingHourStart;
|
for (var i = 0; i < 7; i++)
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private TimeSpan _workingHourEnd;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private List<string> _dayNames = [];
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private int _workingDayStartIndex;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private int _workingDayEndIndex;
|
|
||||||
|
|
||||||
public IPreferencesService PreferencesService { get; }
|
|
||||||
|
|
||||||
private readonly bool _isLoaded = false;
|
|
||||||
|
|
||||||
public CalendarSettingsPageViewModel(IPreferencesService preferencesService)
|
|
||||||
{
|
{
|
||||||
PreferencesService = preferencesService;
|
_dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]);
|
||||||
|
|
||||||
var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage);
|
|
||||||
|
|
||||||
var cultureInfo = new CultureInfo(currentLanguageLanguageCode);
|
|
||||||
|
|
||||||
// Populate the day names list
|
|
||||||
for (var i = 0; i < 7; i++)
|
|
||||||
{
|
|
||||||
_dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek);
|
|
||||||
|
|
||||||
_selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName);
|
|
||||||
_is24HourHeaders = preferencesService.Prefer24HourTimeFormat;
|
|
||||||
_workingHourStart = preferencesService.WorkingHourStart;
|
|
||||||
_workingHourEnd = preferencesService.WorkingHourEnd;
|
|
||||||
_cellHourHeight = preferencesService.HourHeight;
|
|
||||||
|
|
||||||
_workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart));
|
|
||||||
_workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd));
|
|
||||||
|
|
||||||
_isLoaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings();
|
var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek);
|
||||||
partial void OnIs24HourHeadersChanged(bool value) => SaveSettings();
|
|
||||||
partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings();
|
|
||||||
partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings();
|
|
||||||
partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings();
|
|
||||||
partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings();
|
|
||||||
partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings();
|
|
||||||
|
|
||||||
public void SaveSettings()
|
_selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName);
|
||||||
|
_is24HourHeaders = preferencesService.Prefer24HourTimeFormat;
|
||||||
|
_workingHourStart = preferencesService.WorkingHourStart;
|
||||||
|
_workingHourEnd = preferencesService.WorkingHourEnd;
|
||||||
|
_cellHourHeight = preferencesService.HourHeight;
|
||||||
|
|
||||||
|
_workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart));
|
||||||
|
_workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd));
|
||||||
|
|
||||||
|
_isLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings();
|
||||||
|
partial void OnIs24HourHeadersChanged(bool value) => SaveSettings();
|
||||||
|
partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings();
|
||||||
|
partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings();
|
||||||
|
partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings();
|
||||||
|
partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings();
|
||||||
|
partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings();
|
||||||
|
|
||||||
|
public void SaveSettings()
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch
|
||||||
{
|
{
|
||||||
if (!_isLoaded) return;
|
0 => DayOfWeek.Sunday,
|
||||||
|
1 => DayOfWeek.Monday,
|
||||||
|
2 => DayOfWeek.Tuesday,
|
||||||
|
3 => DayOfWeek.Wednesday,
|
||||||
|
4 => DayOfWeek.Thursday,
|
||||||
|
5 => DayOfWeek.Friday,
|
||||||
|
6 => DayOfWeek.Saturday,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
|
||||||
PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch
|
PreferencesService.WorkingDayStart = WorkingDayStartIndex switch
|
||||||
{
|
{
|
||||||
0 => DayOfWeek.Sunday,
|
0 => DayOfWeek.Sunday,
|
||||||
1 => DayOfWeek.Monday,
|
1 => DayOfWeek.Monday,
|
||||||
2 => DayOfWeek.Tuesday,
|
2 => DayOfWeek.Tuesday,
|
||||||
3 => DayOfWeek.Wednesday,
|
3 => DayOfWeek.Wednesday,
|
||||||
4 => DayOfWeek.Thursday,
|
4 => DayOfWeek.Thursday,
|
||||||
5 => DayOfWeek.Friday,
|
5 => DayOfWeek.Friday,
|
||||||
6 => DayOfWeek.Saturday,
|
6 => DayOfWeek.Saturday,
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
||||||
PreferencesService.WorkingDayStart = WorkingDayStartIndex switch
|
PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch
|
||||||
{
|
{
|
||||||
0 => DayOfWeek.Sunday,
|
0 => DayOfWeek.Sunday,
|
||||||
1 => DayOfWeek.Monday,
|
1 => DayOfWeek.Monday,
|
||||||
2 => DayOfWeek.Tuesday,
|
2 => DayOfWeek.Tuesday,
|
||||||
3 => DayOfWeek.Wednesday,
|
3 => DayOfWeek.Wednesday,
|
||||||
4 => DayOfWeek.Thursday,
|
4 => DayOfWeek.Thursday,
|
||||||
5 => DayOfWeek.Friday,
|
5 => DayOfWeek.Friday,
|
||||||
6 => DayOfWeek.Saturday,
|
6 => DayOfWeek.Saturday,
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
||||||
PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch
|
PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders;
|
||||||
{
|
PreferencesService.WorkingHourStart = WorkingHourStart;
|
||||||
0 => DayOfWeek.Sunday,
|
PreferencesService.WorkingHourEnd = WorkingHourEnd;
|
||||||
1 => DayOfWeek.Monday,
|
PreferencesService.HourHeight = CellHourHeight;
|
||||||
2 => DayOfWeek.Tuesday,
|
|
||||||
3 => DayOfWeek.Wednesday,
|
|
||||||
4 => DayOfWeek.Thursday,
|
|
||||||
5 => DayOfWeek.Friday,
|
|
||||||
6 => DayOfWeek.Saturday,
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
|
|
||||||
PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders;
|
Messenger.Send(new CalendarSettingsUpdatedMessage());
|
||||||
PreferencesService.WorkingHourStart = WorkingHourStart;
|
|
||||||
PreferencesService.WorkingHourEnd = WorkingHourEnd;
|
|
||||||
PreferencesService.HourHeight = CellHourHeight;
|
|
||||||
|
|
||||||
Messenger.Send(new CalendarSettingsUpdatedMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Wino.Core;
|
using Wino.Core;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels
|
namespace Wino.Calendar.ViewModels;
|
||||||
|
|
||||||
|
public static class CalendarViewModelContainerSetup
|
||||||
{
|
{
|
||||||
public static class CalendarViewModelContainerSetup
|
public static void RegisterCalendarViewModelServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
public static void RegisterCalendarViewModelServices(this IServiceCollection services)
|
services.RegisterCoreServices();
|
||||||
{
|
|
||||||
services.RegisterCoreServices();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,67 +4,66 @@ using Wino.Core.Domain.Entities.Calendar;
|
|||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Data
|
namespace Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
|
public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar
|
||||||
{
|
{
|
||||||
public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar
|
public MailAccount Account { get; }
|
||||||
|
public AccountCalendar AccountCalendar { get; }
|
||||||
|
|
||||||
|
public AccountCalendarViewModel(MailAccount account, AccountCalendar accountCalendar)
|
||||||
{
|
{
|
||||||
public MailAccount Account { get; }
|
Account = account;
|
||||||
public AccountCalendar AccountCalendar { get; }
|
AccountCalendar = accountCalendar;
|
||||||
|
|
||||||
public AccountCalendarViewModel(MailAccount account, AccountCalendar accountCalendar)
|
IsChecked = accountCalendar.IsExtended;
|
||||||
{
|
|
||||||
Account = account;
|
|
||||||
AccountCalendar = accountCalendar;
|
|
||||||
|
|
||||||
IsChecked = accountCalendar.IsExtended;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool _isChecked;
|
|
||||||
|
|
||||||
partial void OnIsCheckedChanged(bool value) => IsExtended = value;
|
|
||||||
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get => AccountCalendar.Name;
|
|
||||||
set => SetProperty(AccountCalendar.Name, value, AccountCalendar, (u, n) => u.Name = n);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string TextColorHex
|
|
||||||
{
|
|
||||||
get => AccountCalendar.TextColorHex;
|
|
||||||
set => SetProperty(AccountCalendar.TextColorHex, value, AccountCalendar, (u, t) => u.TextColorHex = t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BackgroundColorHex
|
|
||||||
{
|
|
||||||
get => AccountCalendar.BackgroundColorHex;
|
|
||||||
set => SetProperty(AccountCalendar.BackgroundColorHex, value, AccountCalendar, (u, b) => u.BackgroundColorHex = b);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsExtended
|
|
||||||
{
|
|
||||||
get => AccountCalendar.IsExtended;
|
|
||||||
set => SetProperty(AccountCalendar.IsExtended, value, AccountCalendar, (u, i) => u.IsExtended = i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsPrimary
|
|
||||||
{
|
|
||||||
get => AccountCalendar.IsPrimary;
|
|
||||||
set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Guid AccountId
|
|
||||||
{
|
|
||||||
get => AccountCalendar.AccountId;
|
|
||||||
set => SetProperty(AccountCalendar.AccountId, value, AccountCalendar, (u, a) => u.AccountId = a);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string RemoteCalendarId
|
|
||||||
{
|
|
||||||
get => AccountCalendar.RemoteCalendarId;
|
|
||||||
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
|
|
||||||
}
|
|
||||||
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isChecked;
|
||||||
|
|
||||||
|
partial void OnIsCheckedChanged(bool value) => IsExtended = value;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get => AccountCalendar.Name;
|
||||||
|
set => SetProperty(AccountCalendar.Name, value, AccountCalendar, (u, n) => u.Name = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TextColorHex
|
||||||
|
{
|
||||||
|
get => AccountCalendar.TextColorHex;
|
||||||
|
set => SetProperty(AccountCalendar.TextColorHex, value, AccountCalendar, (u, t) => u.TextColorHex = t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BackgroundColorHex
|
||||||
|
{
|
||||||
|
get => AccountCalendar.BackgroundColorHex;
|
||||||
|
set => SetProperty(AccountCalendar.BackgroundColorHex, value, AccountCalendar, (u, b) => u.BackgroundColorHex = b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExtended
|
||||||
|
{
|
||||||
|
get => AccountCalendar.IsExtended;
|
||||||
|
set => SetProperty(AccountCalendar.IsExtended, value, AccountCalendar, (u, i) => u.IsExtended = i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPrimary
|
||||||
|
{
|
||||||
|
get => AccountCalendar.IsPrimary;
|
||||||
|
set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid AccountId
|
||||||
|
{
|
||||||
|
get => AccountCalendar.AccountId;
|
||||||
|
set => SetProperty(AccountCalendar.AccountId, value, AccountCalendar, (u, a) => u.AccountId = a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RemoteCalendarId
|
||||||
|
{
|
||||||
|
get => AccountCalendar.RemoteCalendarId;
|
||||||
|
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
|
||||||
|
}
|
||||||
|
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,42 +5,41 @@ using Itenso.TimePeriod;
|
|||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Data
|
namespace Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
|
public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel
|
||||||
{
|
{
|
||||||
public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel
|
public CalendarItem CalendarItem { get; }
|
||||||
|
|
||||||
|
public string Title => CalendarItem.Title;
|
||||||
|
|
||||||
|
public Guid Id => CalendarItem.Id;
|
||||||
|
|
||||||
|
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
||||||
|
|
||||||
|
public DateTime StartDate { get => CalendarItem.StartDate; set => CalendarItem.StartDate = value; }
|
||||||
|
|
||||||
|
public DateTime EndDate => CalendarItem.EndDate;
|
||||||
|
|
||||||
|
public double DurationInSeconds { get => CalendarItem.DurationInSeconds; set => CalendarItem.DurationInSeconds = value; }
|
||||||
|
|
||||||
|
public ITimePeriod Period => CalendarItem.Period;
|
||||||
|
|
||||||
|
public bool IsAllDayEvent => CalendarItem.IsAllDayEvent;
|
||||||
|
public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent;
|
||||||
|
public bool IsRecurringEvent => CalendarItem.IsRecurringEvent;
|
||||||
|
public bool IsRecurringChild => CalendarItem.IsRecurringChild;
|
||||||
|
public bool IsRecurringParent => CalendarItem.IsRecurringParent;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isSelected;
|
||||||
|
|
||||||
|
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
|
||||||
|
|
||||||
|
public CalendarItemViewModel(CalendarItem calendarItem)
|
||||||
{
|
{
|
||||||
public CalendarItem CalendarItem { get; }
|
CalendarItem = calendarItem;
|
||||||
|
|
||||||
public string Title => CalendarItem.Title;
|
|
||||||
|
|
||||||
public Guid Id => CalendarItem.Id;
|
|
||||||
|
|
||||||
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
|
||||||
|
|
||||||
public DateTime StartDate { get => CalendarItem.StartDate; set => CalendarItem.StartDate = value; }
|
|
||||||
|
|
||||||
public DateTime EndDate => CalendarItem.EndDate;
|
|
||||||
|
|
||||||
public double DurationInSeconds { get => CalendarItem.DurationInSeconds; set => CalendarItem.DurationInSeconds = value; }
|
|
||||||
|
|
||||||
public ITimePeriod Period => CalendarItem.Period;
|
|
||||||
|
|
||||||
public bool IsAllDayEvent => CalendarItem.IsAllDayEvent;
|
|
||||||
public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent;
|
|
||||||
public bool IsRecurringEvent => CalendarItem.IsRecurringEvent;
|
|
||||||
public bool IsRecurringChild => CalendarItem.IsRecurringChild;
|
|
||||||
public bool IsRecurringParent => CalendarItem.IsRecurringParent;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool _isSelected;
|
|
||||||
|
|
||||||
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
|
|
||||||
|
|
||||||
public CalendarItemViewModel(CalendarItem calendarItem)
|
|
||||||
{
|
|
||||||
CalendarItem = calendarItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => CalendarItem.Title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString() => CalendarItem.Title;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,141 +6,140 @@ using System.Linq;
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Data
|
namespace Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
|
public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
public partial class GroupedAccountCalendarViewModel : ObservableObject
|
public event EventHandler CollectiveSelectionStateChanged;
|
||||||
|
public event EventHandler<AccountCalendarViewModel> CalendarSelectionStateChanged;
|
||||||
|
|
||||||
|
public MailAccount Account { get; }
|
||||||
|
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
|
||||||
|
|
||||||
|
public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable<AccountCalendarViewModel> calendarViewModels)
|
||||||
{
|
{
|
||||||
public event EventHandler CollectiveSelectionStateChanged;
|
Account = account;
|
||||||
public event EventHandler<AccountCalendarViewModel> CalendarSelectionStateChanged;
|
AccountCalendars = new ObservableCollection<AccountCalendarViewModel>(calendarViewModels);
|
||||||
|
|
||||||
public MailAccount Account { get; }
|
ManageIsCheckedState();
|
||||||
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
|
|
||||||
|
|
||||||
public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable<AccountCalendarViewModel> calendarViewModels)
|
foreach (var calendarViewModel in calendarViewModels)
|
||||||
{
|
{
|
||||||
Account = account;
|
calendarViewModel.PropertyChanged += CalendarPropertyChanged;
|
||||||
AccountCalendars = new ObservableCollection<AccountCalendarViewModel>(calendarViewModels);
|
|
||||||
|
|
||||||
ManageIsCheckedState();
|
|
||||||
|
|
||||||
foreach (var calendarViewModel in calendarViewModels)
|
|
||||||
{
|
|
||||||
calendarViewModel.PropertyChanged += CalendarPropertyChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountCalendars.CollectionChanged += CalendarListUpdated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
AccountCalendars.CollectionChanged += CalendarListUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||||
{
|
{
|
||||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
foreach (AccountCalendarViewModel calendar in e.NewItems)
|
||||||
{
|
{
|
||||||
foreach (AccountCalendarViewModel calendar in e.NewItems)
|
calendar.PropertyChanged += CalendarPropertyChanged;
|
||||||
{
|
|
||||||
calendar.PropertyChanged += CalendarPropertyChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.Action == NotifyCollectionChangedAction.Remove)
|
|
||||||
{
|
|
||||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
|
||||||
{
|
|
||||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
|
||||||
{
|
|
||||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
|
||||||
{
|
|
||||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||||
private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (sender is AccountCalendarViewModel viewModel)
|
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||||
{
|
{
|
||||||
if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
|
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||||
{
|
|
||||||
ManageIsCheckedState();
|
|
||||||
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||||
[ObservableProperty]
|
|
||||||
private bool _isExpanded = true;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private bool? isCheckedState = true;
|
|
||||||
|
|
||||||
private bool _isExternalPropChangeBlocked = false;
|
|
||||||
|
|
||||||
private void ManageIsCheckedState()
|
|
||||||
{
|
{
|
||||||
if (_isExternalPropChangeBlocked) return;
|
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||||
|
|
||||||
_isExternalPropChangeBlocked = true;
|
|
||||||
|
|
||||||
if (AccountCalendars.All(c => c.IsChecked))
|
|
||||||
{
|
{
|
||||||
IsCheckedState = true;
|
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||||
}
|
}
|
||||||
else if (AccountCalendars.All(c => !c.IsChecked))
|
|
||||||
{
|
|
||||||
IsCheckedState = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IsCheckedState = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isExternalPropChangeBlocked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnIsCheckedStateChanged(bool? newValue)
|
|
||||||
{
|
|
||||||
if (_isExternalPropChangeBlocked) return;
|
|
||||||
|
|
||||||
// Update is triggered by user on the three-state checkbox.
|
|
||||||
// We should not report all changes one by one.
|
|
||||||
|
|
||||||
_isExternalPropChangeBlocked = true;
|
|
||||||
|
|
||||||
if (newValue == null)
|
|
||||||
{
|
|
||||||
// Only primary calendars must be checked.
|
|
||||||
|
|
||||||
foreach (var calendar in AccountCalendars)
|
|
||||||
{
|
|
||||||
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var calendar in AccountCalendars)
|
|
||||||
{
|
|
||||||
UpdateCalendarCheckedState(calendar, newValue.GetValueOrDefault());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_isExternalPropChangeBlocked = false;
|
|
||||||
|
|
||||||
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateCalendarCheckedState(AccountCalendarViewModel accountCalendarViewModel, bool newValue, bool ignoreValueCheck = false)
|
|
||||||
{
|
|
||||||
var currentValue = accountCalendarViewModel.IsChecked;
|
|
||||||
|
|
||||||
if (currentValue == newValue && !ignoreValueCheck) return;
|
|
||||||
|
|
||||||
accountCalendarViewModel.IsChecked = newValue;
|
|
||||||
|
|
||||||
// No need to report.
|
|
||||||
if (_isExternalPropChangeBlocked == true) return;
|
|
||||||
|
|
||||||
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is AccountCalendarViewModel viewModel)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
|
||||||
|
{
|
||||||
|
ManageIsCheckedState();
|
||||||
|
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isExpanded = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool? isCheckedState = true;
|
||||||
|
|
||||||
|
private bool _isExternalPropChangeBlocked = false;
|
||||||
|
|
||||||
|
private void ManageIsCheckedState()
|
||||||
|
{
|
||||||
|
if (_isExternalPropChangeBlocked) return;
|
||||||
|
|
||||||
|
_isExternalPropChangeBlocked = true;
|
||||||
|
|
||||||
|
if (AccountCalendars.All(c => c.IsChecked))
|
||||||
|
{
|
||||||
|
IsCheckedState = true;
|
||||||
|
}
|
||||||
|
else if (AccountCalendars.All(c => !c.IsChecked))
|
||||||
|
{
|
||||||
|
IsCheckedState = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsCheckedState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isExternalPropChangeBlocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnIsCheckedStateChanged(bool? newValue)
|
||||||
|
{
|
||||||
|
if (_isExternalPropChangeBlocked) return;
|
||||||
|
|
||||||
|
// Update is triggered by user on the three-state checkbox.
|
||||||
|
// We should not report all changes one by one.
|
||||||
|
|
||||||
|
_isExternalPropChangeBlocked = true;
|
||||||
|
|
||||||
|
if (newValue == null)
|
||||||
|
{
|
||||||
|
// Only primary calendars must be checked.
|
||||||
|
|
||||||
|
foreach (var calendar in AccountCalendars)
|
||||||
|
{
|
||||||
|
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var calendar in AccountCalendars)
|
||||||
|
{
|
||||||
|
UpdateCalendarCheckedState(calendar, newValue.GetValueOrDefault());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_isExternalPropChangeBlocked = false;
|
||||||
|
|
||||||
|
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCalendarCheckedState(AccountCalendarViewModel accountCalendarViewModel, bool newValue, bool ignoreValueCheck = false)
|
||||||
|
{
|
||||||
|
var currentValue = accountCalendarViewModel.IsChecked;
|
||||||
|
|
||||||
|
if (currentValue == newValue && !ignoreValueCheck) return;
|
||||||
|
|
||||||
|
accountCalendarViewModel.IsChecked = newValue;
|
||||||
|
|
||||||
|
// No need to report.
|
||||||
|
if (_isExternalPropChangeBlocked == true) return;
|
||||||
|
|
||||||
|
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,105 +12,104 @@ using Wino.Core.Domain.Models.Navigation;
|
|||||||
using Wino.Core.ViewModels;
|
using Wino.Core.ViewModels;
|
||||||
using Wino.Messaging.Client.Calendar;
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels
|
namespace Wino.Calendar.ViewModels;
|
||||||
|
|
||||||
|
public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||||
{
|
{
|
||||||
public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
private readonly ICalendarService _calendarService;
|
||||||
|
private readonly INativeAppService _nativeAppService;
|
||||||
|
private readonly IPreferencesService _preferencesService;
|
||||||
|
|
||||||
|
public CalendarSettings CurrentSettings { get; }
|
||||||
|
|
||||||
|
#region Details
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
||||||
|
private CalendarItemViewModel _currentEvent;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private CalendarItemViewModel _seriesParent;
|
||||||
|
|
||||||
|
public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService)
|
||||||
{
|
{
|
||||||
private readonly ICalendarService _calendarService;
|
_calendarService = calendarService;
|
||||||
private readonly INativeAppService _nativeAppService;
|
_nativeAppService = nativeAppService;
|
||||||
private readonly IPreferencesService _preferencesService;
|
_preferencesService = preferencesService;
|
||||||
|
|
||||||
public CalendarSettings CurrentSettings { get; }
|
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
||||||
|
}
|
||||||
|
|
||||||
#region Details
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
|
{
|
||||||
|
base.OnNavigatedTo(mode, parameters);
|
||||||
|
|
||||||
[ObservableProperty]
|
Messenger.Send(new DetailsPageStateChangedMessage(true));
|
||||||
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
|
||||||
private CalendarItemViewModel _currentEvent;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
if (parameters == null || parameters is not CalendarItemTarget args)
|
||||||
private CalendarItemViewModel _seriesParent;
|
return;
|
||||||
|
|
||||||
public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false;
|
await LoadCalendarItemTargetAsync(args);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target)
|
||||||
|
{
|
||||||
public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService)
|
try
|
||||||
{
|
{
|
||||||
_calendarService = calendarService;
|
var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target);
|
||||||
_nativeAppService = nativeAppService;
|
|
||||||
_preferencesService = preferencesService;
|
|
||||||
|
|
||||||
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
if (currentEventItem == null)
|
||||||
}
|
|
||||||
|
|
||||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
|
||||||
{
|
|
||||||
base.OnNavigatedTo(mode, parameters);
|
|
||||||
|
|
||||||
Messenger.Send(new DetailsPageStateChangedMessage(true));
|
|
||||||
|
|
||||||
if (parameters == null || parameters is not CalendarItemTarget args)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await LoadCalendarItemTargetAsync(args);
|
CurrentEvent = new CalendarItemViewModel(currentEventItem);
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target)
|
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId);
|
||||||
{
|
|
||||||
try
|
foreach (var item in attendees)
|
||||||
{
|
{
|
||||||
var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target);
|
CurrentEvent.Attendees.Add(item);
|
||||||
|
|
||||||
if (currentEventItem == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CurrentEvent = new CalendarItemViewModel(currentEventItem);
|
|
||||||
|
|
||||||
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId);
|
|
||||||
|
|
||||||
foreach (var item in attendees)
|
|
||||||
{
|
|
||||||
CurrentEvent.Attendees.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine(ex.Message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
|
||||||
{
|
{
|
||||||
base.OnNavigatedFrom(mode, parameters);
|
Debug.WriteLine(ex.Message);
|
||||||
|
|
||||||
Messenger.Send(new DetailsPageStateChangedMessage(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task SaveAsync()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task DeleteAsync()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private Task JoinOnline()
|
|
||||||
{
|
|
||||||
if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask;
|
|
||||||
|
|
||||||
return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink));
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task Respond(CalendarItemStatus status)
|
|
||||||
{
|
|
||||||
if (CurrentEvent == null) return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||||
|
{
|
||||||
|
base.OnNavigatedFrom(mode, parameters);
|
||||||
|
|
||||||
|
Messenger.Send(new DetailsPageStateChangedMessage(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SaveAsync()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task DeleteAsync()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private Task JoinOnline()
|
||||||
|
{
|
||||||
|
if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask;
|
||||||
|
|
||||||
|
return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink));
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Respond(CalendarItemStatus status)
|
||||||
|
{
|
||||||
|
if (CurrentEvent == null) return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,25 @@ using System.Linq;
|
|||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Interfaces
|
namespace Wino.Calendar.ViewModels.Interfaces;
|
||||||
|
|
||||||
|
public interface IAccountCalendarStateService : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
public interface IAccountCalendarStateService : INotifyPropertyChanged
|
ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; }
|
||||||
{
|
|
||||||
ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; }
|
|
||||||
|
|
||||||
event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
||||||
event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
||||||
|
|
||||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||||
public void ClearGroupedAccountCalendar();
|
public void ClearGroupedAccountCalendar();
|
||||||
|
|
||||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
|
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
|
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enumeration of currently selected calendars.
|
/// Enumeration of currently selected calendars.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IEnumerable<AccountCalendarViewModel> ActiveCalendars { get; }
|
IEnumerable<AccountCalendarViewModel> ActiveCalendars { get; }
|
||||||
IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable { get; }
|
IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable { get; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Messages
|
namespace Wino.Calendar.ViewModels.Messages;
|
||||||
{
|
|
||||||
public class CalendarItemDoubleTappedMessage
|
|
||||||
{
|
|
||||||
public CalendarItemDoubleTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
|
||||||
{
|
|
||||||
CalendarItemViewModel = calendarItemViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
public class CalendarItemDoubleTappedMessage
|
||||||
|
{
|
||||||
|
public CalendarItemDoubleTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||||
|
{
|
||||||
|
CalendarItemViewModel = calendarItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Messages
|
namespace Wino.Calendar.ViewModels.Messages;
|
||||||
{
|
|
||||||
public class CalendarItemRightTappedMessage
|
|
||||||
{
|
|
||||||
public CalendarItemRightTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
|
||||||
{
|
|
||||||
CalendarItemViewModel = calendarItemViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
public class CalendarItemRightTappedMessage
|
||||||
|
{
|
||||||
|
public CalendarItemRightTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||||
|
{
|
||||||
|
CalendarItemViewModel = calendarItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels.Messages
|
namespace Wino.Calendar.ViewModels.Messages;
|
||||||
{
|
|
||||||
public class CalendarItemTappedMessage
|
|
||||||
{
|
|
||||||
public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod)
|
|
||||||
{
|
|
||||||
CalendarItemViewModel = calendarItemViewModel;
|
|
||||||
ClickedPeriod = clickedPeriod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
public class CalendarItemTappedMessage
|
||||||
public CalendarDayModel ClickedPeriod { get; }
|
{
|
||||||
|
public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod)
|
||||||
|
{
|
||||||
|
CalendarItemViewModel = calendarItemViewModel;
|
||||||
|
ClickedPeriod = clickedPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||||
|
public CalendarDayModel ClickedPeriod { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,18 @@ using Windows.UI.Xaml.Media.Animation;
|
|||||||
using Wino.Activation;
|
using Wino.Activation;
|
||||||
using Wino.Calendar.Views;
|
using Wino.Calendar.Views;
|
||||||
|
|
||||||
namespace Wino.Calendar.Activation
|
namespace Wino.Calendar.Activation;
|
||||||
|
|
||||||
|
public class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||||
{
|
{
|
||||||
public class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
||||||
{
|
|
||||||
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
|
||||||
|
|
||||||
// Only navigate if Frame content doesn't exist.
|
|
||||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
|
||||||
=> (Window.Current?.Content as Frame)?.Content == null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only navigate if Frame content doesn't exist.
|
||||||
|
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||||
|
=> (Window.Current?.Content as Frame)?.Content == null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,40 @@
|
|||||||
using System;
|
using System;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
|
|
||||||
namespace Wino.Calendar.Args
|
namespace Wino.Calendar.Args;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When a new timeline cell is selected.
|
||||||
|
/// </summary>
|
||||||
|
public class TimelineCellSelectedArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
public TimelineCellSelectedArgs(DateTime clickedDate, Point canvasPoint, Point positionerPoint, Size cellSize)
|
||||||
/// When a new timeline cell is selected.
|
|
||||||
/// </summary>
|
|
||||||
public class TimelineCellSelectedArgs : EventArgs
|
|
||||||
{
|
{
|
||||||
public TimelineCellSelectedArgs(DateTime clickedDate, Point canvasPoint, Point positionerPoint, Size cellSize)
|
ClickedDate = clickedDate;
|
||||||
{
|
CanvasPoint = canvasPoint;
|
||||||
ClickedDate = clickedDate;
|
PositionerPoint = positionerPoint;
|
||||||
CanvasPoint = canvasPoint;
|
CellSize = cellSize;
|
||||||
PositionerPoint = positionerPoint;
|
|
||||||
CellSize = cellSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clicked date and time information for the cell.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime ClickedDate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Position relative to the cell drawing part of the canvas.
|
|
||||||
/// Used to detect clicked cell from the position.
|
|
||||||
/// </summary>
|
|
||||||
public Point CanvasPoint { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Position relative to the main root positioner element of the drawing canvas.
|
|
||||||
/// Used to show the create event dialog teaching tip in correct position.
|
|
||||||
/// </summary>
|
|
||||||
public Point PositionerPoint { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Size of the cell.
|
|
||||||
/// </summary>
|
|
||||||
public Size CellSize { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clicked date and time information for the cell.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ClickedDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position relative to the cell drawing part of the canvas.
|
||||||
|
/// Used to detect clicked cell from the position.
|
||||||
|
/// </summary>
|
||||||
|
public Point CanvasPoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position relative to the main root positioner element of the drawing canvas.
|
||||||
|
/// Used to show the create event dialog teaching tip in correct position.
|
||||||
|
/// </summary>
|
||||||
|
public Point PositionerPoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of the cell.
|
||||||
|
/// </summary>
|
||||||
|
public Size CellSize { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Wino.Calendar.Args
|
namespace Wino.Calendar.Args;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When selected timeline cell is unselected.
|
/// When selected timeline cell is unselected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TimelineCellUnselectedArgs : EventArgs { }
|
public class TimelineCellUnselectedArgs : EventArgs { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,30 +2,29 @@
|
|||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class CalendarItemCommandBarFlyout : CommandBarFlyout
|
||||||
{
|
{
|
||||||
public class CalendarItemCommandBarFlyout : CommandBarFlyout
|
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
|
||||||
|
|
||||||
|
public CalendarItemViewModel Item
|
||||||
{
|
{
|
||||||
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
|
get { return (CalendarItemViewModel)GetValue(ItemProperty); }
|
||||||
|
set { SetValue(ItemProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public CalendarItemViewModel Item
|
|
||||||
|
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is CalendarItemCommandBarFlyout flyout)
|
||||||
{
|
{
|
||||||
get { return (CalendarItemViewModel)GetValue(ItemProperty); }
|
flyout.UpdateMenuItems();
|
||||||
set { SetValue(ItemProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is CalendarItemCommandBarFlyout flyout)
|
|
||||||
{
|
|
||||||
flyout.UpdateMenuItems();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateMenuItems()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateMenuItems()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,190 +9,189 @@ using Wino.Calendar.ViewModels.Messages;
|
|||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public sealed partial class CalendarItemControl : UserControl
|
||||||
{
|
{
|
||||||
public sealed partial class CalendarItemControl : UserControl
|
// Single tap has a delay to report double taps properly.
|
||||||
|
private bool isSingleTap = false;
|
||||||
|
|
||||||
|
public static readonly DependencyProperty CalendarItemProperty = DependencyProperty.Register(nameof(CalendarItem), typeof(CalendarItemViewModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarItemChanged)));
|
||||||
|
public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(nameof(IsDragging), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty IsCustomEventAreaProperty = DependencyProperty.Register(nameof(IsCustomEventArea), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||||
|
public static readonly DependencyProperty CalendarItemTitleProperty = DependencyProperty.Register(nameof(CalendarItemTitle), typeof(string), typeof(CalendarItemControl), new PropertyMetadata(string.Empty));
|
||||||
|
public static readonly DependencyProperty DisplayingDateProperty = DependencyProperty.Register(nameof(DisplayingDate), typeof(CalendarDayModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDisplayDateChanged)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the control is displaying as regular event or all-multi day area in the day control.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCustomEventArea
|
||||||
{
|
{
|
||||||
// Single tap has a delay to report double taps properly.
|
get { return (bool)GetValue(IsCustomEventAreaProperty); }
|
||||||
private bool isSingleTap = false;
|
set { SetValue(IsCustomEventAreaProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty CalendarItemProperty = DependencyProperty.Register(nameof(CalendarItem), typeof(CalendarItemViewModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarItemChanged)));
|
/// <summary>
|
||||||
public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(nameof(IsDragging), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
/// Day that the calendar item is rendered at.
|
||||||
public static readonly DependencyProperty IsCustomEventAreaProperty = DependencyProperty.Register(nameof(IsCustomEventArea), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
/// It's needed for title manipulation and some other adjustments later on.
|
||||||
public static readonly DependencyProperty CalendarItemTitleProperty = DependencyProperty.Register(nameof(CalendarItemTitle), typeof(string), typeof(CalendarItemControl), new PropertyMetadata(string.Empty));
|
/// </summary>
|
||||||
public static readonly DependencyProperty DisplayingDateProperty = DependencyProperty.Register(nameof(DisplayingDate), typeof(CalendarDayModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDisplayDateChanged)));
|
public CalendarDayModel DisplayingDate
|
||||||
|
{
|
||||||
|
get { return (CalendarDayModel)GetValue(DisplayingDateProperty); }
|
||||||
|
set { SetValue(DisplayingDateProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
public string CalendarItemTitle
|
||||||
/// Whether the control is displaying as regular event or all-multi day area in the day control.
|
{
|
||||||
/// </summary>
|
get { return (string)GetValue(CalendarItemTitleProperty); }
|
||||||
public bool IsCustomEventArea
|
set { SetValue(CalendarItemTitleProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarItemViewModel CalendarItem
|
||||||
|
{
|
||||||
|
get { return (CalendarItemViewModel)GetValue(CalendarItemProperty); }
|
||||||
|
set { SetValue(CalendarItemProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDragging
|
||||||
|
{
|
||||||
|
get { return (bool)GetValue(IsDraggingProperty); }
|
||||||
|
set { SetValue(IsDraggingProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarItemControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDisplayDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is CalendarItemControl control)
|
||||||
{
|
{
|
||||||
get { return (bool)GetValue(IsCustomEventAreaProperty); }
|
control.UpdateControlVisuals();
|
||||||
set { SetValue(IsCustomEventAreaProperty, value); }
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
/// Day that the calendar item is rendered at.
|
{
|
||||||
/// It's needed for title manipulation and some other adjustments later on.
|
if (d is CalendarItemControl control)
|
||||||
/// </summary>
|
|
||||||
public CalendarDayModel DisplayingDate
|
|
||||||
{
|
{
|
||||||
get { return (CalendarDayModel)GetValue(DisplayingDateProperty); }
|
control.UpdateControlVisuals();
|
||||||
set { SetValue(DisplayingDateProperty, value); }
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string CalendarItemTitle
|
private void UpdateControlVisuals()
|
||||||
{
|
{
|
||||||
get { return (string)GetValue(CalendarItemTitleProperty); }
|
// Depending on the calendar item's duration and attributes, we might need to change the display title.
|
||||||
set { SetValue(CalendarItemTitleProperty, value); }
|
// 1. Multi-Day events should display the start date and end date.
|
||||||
}
|
// 2. Multi-Day events that occupy the whole day just shows 'all day'.
|
||||||
|
// 3. Other events should display the title.
|
||||||
|
|
||||||
public CalendarItemViewModel CalendarItem
|
if (CalendarItem == null) return;
|
||||||
{
|
if (DisplayingDate == null) return;
|
||||||
get { return (CalendarItemViewModel)GetValue(CalendarItemProperty); }
|
|
||||||
set { SetValue(CalendarItemProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsDragging
|
if (CalendarItem.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
get { return (bool)GetValue(IsDraggingProperty); }
|
// Multi day events are divided into 3 categories:
|
||||||
set { SetValue(IsDraggingProperty, value); }
|
// 1. All day events
|
||||||
}
|
// 2. Events that started after the period.
|
||||||
|
// 3. Events that started before the period and finishes within the period.
|
||||||
|
|
||||||
public CalendarItemControl()
|
var periodRelation = CalendarItem.Period.GetRelation(DisplayingDate.Period);
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnDisplayDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
if (periodRelation == Itenso.TimePeriod.PeriodRelation.StartInside ||
|
||||||
{
|
periodRelation == PeriodRelation.EnclosingStartTouching)
|
||||||
if (d is CalendarItemControl control)
|
|
||||||
{
|
{
|
||||||
control.UpdateControlVisuals();
|
// hour -> title
|
||||||
|
CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDate.TimeOfDay)} -> {CalendarItem.Title}";
|
||||||
}
|
}
|
||||||
}
|
else if (
|
||||||
|
periodRelation == PeriodRelation.EndInside ||
|
||||||
private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
periodRelation == PeriodRelation.EnclosingEndTouching)
|
||||||
{
|
|
||||||
if (d is CalendarItemControl control)
|
|
||||||
{
|
{
|
||||||
control.UpdateControlVisuals();
|
// title <- hour
|
||||||
|
CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDate.TimeOfDay)}";
|
||||||
}
|
}
|
||||||
}
|
else if (periodRelation == PeriodRelation.Enclosing)
|
||||||
|
|
||||||
private void UpdateControlVisuals()
|
|
||||||
{
|
|
||||||
// Depending on the calendar item's duration and attributes, we might need to change the display title.
|
|
||||||
// 1. Multi-Day events should display the start date and end date.
|
|
||||||
// 2. Multi-Day events that occupy the whole day just shows 'all day'.
|
|
||||||
// 3. Other events should display the title.
|
|
||||||
|
|
||||||
if (CalendarItem == null) return;
|
|
||||||
if (DisplayingDate == null) return;
|
|
||||||
|
|
||||||
if (CalendarItem.IsMultiDayEvent)
|
|
||||||
{
|
{
|
||||||
// Multi day events are divided into 3 categories:
|
// This event goes all day and it's multi-day.
|
||||||
// 1. All day events
|
// Item must be hidden in the calendar but displayed on the custom area at the top.
|
||||||
// 2. Events that started after the period.
|
|
||||||
// 3. Events that started before the period and finishes within the period.
|
|
||||||
|
|
||||||
var periodRelation = CalendarItem.Period.GetRelation(DisplayingDate.Period);
|
CalendarItemTitle = $"{Translator.CalendarItemAllDay} {CalendarItem.Title}";
|
||||||
|
|
||||||
if (periodRelation == Itenso.TimePeriod.PeriodRelation.StartInside ||
|
|
||||||
periodRelation == PeriodRelation.EnclosingStartTouching)
|
|
||||||
{
|
|
||||||
// hour -> title
|
|
||||||
CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDate.TimeOfDay)} -> {CalendarItem.Title}";
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
periodRelation == PeriodRelation.EndInside ||
|
|
||||||
periodRelation == PeriodRelation.EnclosingEndTouching)
|
|
||||||
{
|
|
||||||
// title <- hour
|
|
||||||
CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDate.TimeOfDay)}";
|
|
||||||
}
|
|
||||||
else if (periodRelation == PeriodRelation.Enclosing)
|
|
||||||
{
|
|
||||||
// This event goes all day and it's multi-day.
|
|
||||||
// Item must be hidden in the calendar but displayed on the custom area at the top.
|
|
||||||
|
|
||||||
CalendarItemTitle = $"{Translator.CalendarItemAllDay} {CalendarItem.Title}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not expected, but there it is.
|
|
||||||
CalendarItemTitle = CalendarItem.Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug.WriteLine($"{CalendarItem.Title} Period relation with {DisplayingDate.Period.ToString()}: {periodRelation}");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Not expected, but there it is.
|
||||||
CalendarItemTitle = CalendarItem.Title;
|
CalendarItemTitle = CalendarItem.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateVisualStates();
|
// Debug.WriteLine($"{CalendarItem.Title} Period relation with {DisplayingDate.Period.ToString()}: {periodRelation}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CalendarItemTitle = CalendarItem.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateVisualStates()
|
UpdateVisualStates();
|
||||||
{
|
}
|
||||||
if (CalendarItem == null) return;
|
|
||||||
|
|
||||||
if (CalendarItem.IsAllDayEvent)
|
private void UpdateVisualStates()
|
||||||
|
{
|
||||||
|
if (CalendarItem == null) return;
|
||||||
|
|
||||||
|
if (CalendarItem.IsAllDayEvent)
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
||||||
|
}
|
||||||
|
else if (CalendarItem.IsMultiDayEvent)
|
||||||
|
{
|
||||||
|
if (IsCustomEventArea)
|
||||||
{
|
{
|
||||||
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true);
|
||||||
}
|
|
||||||
else if (CalendarItem.IsMultiDayEvent)
|
|
||||||
{
|
|
||||||
if (IsCustomEventArea)
|
|
||||||
{
|
|
||||||
VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Hide it.
|
|
||||||
VisualStateManager.GoToState(this, "MultiDayEvent", true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
VisualStateManager.GoToState(this, "RegularEvent", true);
|
// Hide it.
|
||||||
|
VisualStateManager.GoToState(this, "MultiDayEvent", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void ControlDragStarting(UIElement sender, DragStartingEventArgs args) => IsDragging = true;
|
|
||||||
|
|
||||||
private void ControlDropped(UIElement sender, DropCompletedEventArgs args) => IsDragging = false;
|
|
||||||
|
|
||||||
private async void ControlTapped(object sender, TappedRoutedEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (CalendarItem == null) return;
|
VisualStateManager.GoToState(this, "RegularEvent", true);
|
||||||
|
|
||||||
isSingleTap = true;
|
|
||||||
|
|
||||||
await Task.Delay(100);
|
|
||||||
|
|
||||||
if (isSingleTap)
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new CalendarItemTappedMessage(CalendarItem, DisplayingDate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ControlDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (CalendarItem == null) return;
|
|
||||||
|
|
||||||
isSingleTap = false;
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new CalendarItemDoubleTappedMessage(CalendarItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ControlRightTapped(object sender, RightTappedRoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (CalendarItem == null) return;
|
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new CalendarItemRightTappedMessage(CalendarItem));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ControlDragStarting(UIElement sender, DragStartingEventArgs args) => IsDragging = true;
|
||||||
|
|
||||||
|
private void ControlDropped(UIElement sender, DropCompletedEventArgs args) => IsDragging = false;
|
||||||
|
|
||||||
|
private async void ControlTapped(object sender, TappedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (CalendarItem == null) return;
|
||||||
|
|
||||||
|
isSingleTap = true;
|
||||||
|
|
||||||
|
await Task.Delay(100);
|
||||||
|
|
||||||
|
if (isSingleTap)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarItemTappedMessage(CalendarItem, DisplayingDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ControlDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (CalendarItem == null) return;
|
||||||
|
|
||||||
|
isSingleTap = false;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarItemDoubleTappedMessage(CalendarItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ControlRightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (CalendarItem == null) return;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarItemRightTappedMessage(CalendarItem));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,42 @@
|
|||||||
using Windows.UI.Xaml.Automation.Peers;
|
using Windows.UI.Xaml.Automation.Peers;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations.
|
||||||
|
/// </summary>
|
||||||
|
public partial class CustomCalendarFlipView : FlipView
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const string PART_PreviousButton = "PreviousButtonHorizontal";
|
||||||
/// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations.
|
private const string PART_NextButton = "NextButtonHorizontal";
|
||||||
/// </summary>
|
|
||||||
public class CustomCalendarFlipView : FlipView
|
private Button PreviousButton;
|
||||||
|
private Button NextButton;
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
{
|
{
|
||||||
private const string PART_PreviousButton = "PreviousButtonHorizontal";
|
base.OnApplyTemplate();
|
||||||
private const string PART_NextButton = "NextButtonHorizontal";
|
|
||||||
|
|
||||||
private Button PreviousButton;
|
PreviousButton = GetTemplateChild(PART_PreviousButton) as Button;
|
||||||
private Button NextButton;
|
NextButton = GetTemplateChild(PART_NextButton) as Button;
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
// Hide navigation buttons
|
||||||
{
|
PreviousButton.Opacity = NextButton.Opacity = 0;
|
||||||
base.OnApplyTemplate();
|
PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false;
|
||||||
|
|
||||||
PreviousButton = GetTemplateChild(PART_PreviousButton) as Button;
|
var t = FindName("ScrollingHost");
|
||||||
NextButton = GetTemplateChild(PART_NextButton) as Button;
|
}
|
||||||
|
|
||||||
// Hide navigation buttons
|
public void GoPreviousFlip()
|
||||||
PreviousButton.Opacity = NextButton.Opacity = 0;
|
{
|
||||||
PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false;
|
var backPeer = new ButtonAutomationPeer(PreviousButton);
|
||||||
|
backPeer.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
var t = FindName("ScrollingHost");
|
public void GoNextFlip()
|
||||||
}
|
{
|
||||||
|
var nextPeer = new ButtonAutomationPeer(NextButton);
|
||||||
public void GoPreviousFlip()
|
nextPeer.Invoke();
|
||||||
{
|
|
||||||
var backPeer = new ButtonAutomationPeer(PreviousButton);
|
|
||||||
backPeer.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GoNextFlip()
|
|
||||||
{
|
|
||||||
var nextPeer = new ButtonAutomationPeer(NextButton);
|
|
||||||
nextPeer.Invoke();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,76 +3,75 @@ using Windows.UI.Xaml;
|
|||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class DayColumnControl : Control
|
||||||
{
|
{
|
||||||
public class DayColumnControl : Control
|
private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText);
|
||||||
|
private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder);
|
||||||
|
private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText);
|
||||||
|
|
||||||
|
private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl);
|
||||||
|
|
||||||
|
private const string TodayState = nameof(TodayState);
|
||||||
|
private const string NotTodayState = nameof(NotTodayState);
|
||||||
|
|
||||||
|
private TextBlock HeaderDateDayText;
|
||||||
|
private TextBlock ColumnHeaderText;
|
||||||
|
private Border IsTodayBorder;
|
||||||
|
private ItemsControl AllDayItemsControl;
|
||||||
|
|
||||||
|
public CalendarDayModel DayModel
|
||||||
{
|
{
|
||||||
private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText);
|
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
||||||
private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder);
|
set { SetValue(DayModelProperty, value); }
|
||||||
private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText);
|
}
|
||||||
|
|
||||||
private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl);
|
public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(DayColumnControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||||
|
|
||||||
private const string TodayState = nameof(TodayState);
|
public DayColumnControl()
|
||||||
private const string NotTodayState = nameof(NotTodayState);
|
{
|
||||||
|
DefaultStyleKey = typeof(DayColumnControl);
|
||||||
|
}
|
||||||
|
|
||||||
private TextBlock HeaderDateDayText;
|
protected override void OnApplyTemplate()
|
||||||
private TextBlock ColumnHeaderText;
|
{
|
||||||
private Border IsTodayBorder;
|
base.OnApplyTemplate();
|
||||||
private ItemsControl AllDayItemsControl;
|
|
||||||
|
|
||||||
public CalendarDayModel DayModel
|
HeaderDateDayText = GetTemplateChild(PART_HeaderDateDayText) as TextBlock;
|
||||||
|
ColumnHeaderText = GetTemplateChild(PART_ColumnHeaderText) as TextBlock;
|
||||||
|
IsTodayBorder = GetTemplateChild(PART_IsTodayBorder) as Border;
|
||||||
|
AllDayItemsControl = GetTemplateChild(PART_AllDayItemsControl) as ItemsControl;
|
||||||
|
|
||||||
|
UpdateValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (control is DayColumnControl columnControl)
|
||||||
{
|
{
|
||||||
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
columnControl.UpdateValues();
|
||||||
set { SetValue(DayModelProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(DayColumnControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
|
||||||
|
|
||||||
public DayColumnControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DayColumnControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
|
|
||||||
HeaderDateDayText = GetTemplateChild(PART_HeaderDateDayText) as TextBlock;
|
|
||||||
ColumnHeaderText = GetTemplateChild(PART_ColumnHeaderText) as TextBlock;
|
|
||||||
IsTodayBorder = GetTemplateChild(PART_IsTodayBorder) as Border;
|
|
||||||
AllDayItemsControl = GetTemplateChild(PART_AllDayItemsControl) as ItemsControl;
|
|
||||||
|
|
||||||
UpdateValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (control is DayColumnControl columnControl)
|
|
||||||
{
|
|
||||||
columnControl.UpdateValues();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateValues()
|
|
||||||
{
|
|
||||||
if (HeaderDateDayText == null || IsTodayBorder == null || DayModel == null) return;
|
|
||||||
|
|
||||||
HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString();
|
|
||||||
|
|
||||||
// Monthly template does not use it.
|
|
||||||
if (ColumnHeaderText != null)
|
|
||||||
{
|
|
||||||
ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
AllDayItemsControl.ItemsSource = DayModel.EventsCollection.AllDayEvents;
|
|
||||||
|
|
||||||
bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date;
|
|
||||||
|
|
||||||
VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false);
|
|
||||||
|
|
||||||
UpdateLayout();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateValues()
|
||||||
|
{
|
||||||
|
if (HeaderDateDayText == null || IsTodayBorder == null || DayModel == null) return;
|
||||||
|
|
||||||
|
HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString();
|
||||||
|
|
||||||
|
// Monthly template does not use it.
|
||||||
|
if (ColumnHeaderText != null)
|
||||||
|
{
|
||||||
|
ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
AllDayItemsControl.ItemsSource = DayModel.EventsCollection.AllDayEvents;
|
||||||
|
|
||||||
|
bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date;
|
||||||
|
|
||||||
|
VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false);
|
||||||
|
|
||||||
|
UpdateLayout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,55 +3,54 @@ using Windows.UI.Xaml;
|
|||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class DayHeaderControl : Control
|
||||||
{
|
{
|
||||||
public class DayHeaderControl : Control
|
private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock);
|
||||||
|
private TextBlock HeaderTextblock;
|
||||||
|
|
||||||
|
public DayHeaderDisplayType DisplayType
|
||||||
{
|
{
|
||||||
private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock);
|
get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); }
|
||||||
private TextBlock HeaderTextblock;
|
set { SetValue(DisplayTypeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public DayHeaderDisplayType DisplayType
|
public DateTime Date
|
||||||
|
{
|
||||||
|
get { return (DateTime)GetValue(DateProperty); }
|
||||||
|
set { SetValue(DateProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty DateProperty = DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(DayHeaderControl), new PropertyMetadata(default(DateTime), new PropertyChangedCallback(OnHeaderPropertyChanged)));
|
||||||
|
public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(DayHeaderDisplayType), typeof(DayHeaderControl), new PropertyMetadata(DayHeaderDisplayType.TwentyFourHour, new PropertyChangedCallback(OnHeaderPropertyChanged)));
|
||||||
|
|
||||||
|
public DayHeaderControl()
|
||||||
|
{
|
||||||
|
DefaultStyleKey = typeof(DayHeaderControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
HeaderTextblock = GetTemplateChild(PART_DayHeaderTextBlock) as TextBlock;
|
||||||
|
UpdateHeaderText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnHeaderPropertyChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (control is DayHeaderControl headerControl)
|
||||||
{
|
{
|
||||||
get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); }
|
headerControl.UpdateHeaderText();
|
||||||
set { SetValue(DisplayTypeProperty, value); }
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public DateTime Date
|
private void UpdateHeaderText()
|
||||||
|
{
|
||||||
|
if (HeaderTextblock != null)
|
||||||
{
|
{
|
||||||
get { return (DateTime)GetValue(DateProperty); }
|
HeaderTextblock.Text = DisplayType == DayHeaderDisplayType.TwelveHour ? Date.ToString("h tt") : Date.ToString("HH:mm");
|
||||||
set { SetValue(DateProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly DependencyProperty DateProperty = DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(DayHeaderControl), new PropertyMetadata(default(DateTime), new PropertyChangedCallback(OnHeaderPropertyChanged)));
|
|
||||||
public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(DayHeaderDisplayType), typeof(DayHeaderControl), new PropertyMetadata(DayHeaderDisplayType.TwentyFourHour, new PropertyChangedCallback(OnHeaderPropertyChanged)));
|
|
||||||
|
|
||||||
public DayHeaderControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(DayHeaderControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
|
|
||||||
HeaderTextblock = GetTemplateChild(PART_DayHeaderTextBlock) as TextBlock;
|
|
||||||
UpdateHeaderText();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnHeaderPropertyChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (control is DayHeaderControl headerControl)
|
|
||||||
{
|
|
||||||
headerControl.UpdateHeaderText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateHeaderText()
|
|
||||||
{
|
|
||||||
if (HeaderTextblock != null)
|
|
||||||
{
|
|
||||||
HeaderTextblock.Text = DisplayType == DayHeaderDisplayType.TwelveHour ? Date.ToString("h tt") : Date.ToString("HH:mm");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,291 +10,290 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Helpers;
|
using Wino.Helpers;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class WinoCalendarControl : Control
|
||||||
{
|
{
|
||||||
public class WinoCalendarControl : Control
|
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
|
||||||
|
|
||||||
|
public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection<DayRangeRenderModel>), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1));
|
||||||
|
public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged)));
|
||||||
|
public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged)));
|
||||||
|
public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged)));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||||
|
public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||||
|
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||||
|
public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day));
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public CalendarDisplayType DisplayType
|
||||||
{
|
{
|
||||||
private const string PART_WinoFlipView = nameof(PART_WinoFlipView);
|
get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); }
|
||||||
private const string PART_IdleGrid = nameof(PART_IdleGrid);
|
set { SetValue(DisplayTypeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
public CalendarOrientation Orientation
|
||||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
{
|
||||||
|
get { return (CalendarOrientation)GetValue(OrientationProperty); }
|
||||||
|
set { SetValue(OrientationProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public event EventHandler ScrollPositionChanging;
|
public ItemsPanelTemplate VerticalItemsPanelTemplate
|
||||||
|
{
|
||||||
|
get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); }
|
||||||
|
set { SetValue(VerticalItemsPanelTemplateProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
#region Dependency Properties
|
public ItemsPanelTemplate HorizontalItemsPanelTemplate
|
||||||
|
{
|
||||||
|
get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); }
|
||||||
|
set { SetValue(HorizontalItemsPanelTemplateProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection<DayRangeRenderModel>), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
public DayRangeRenderModel SelectedFlipViewDayRange
|
||||||
public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1));
|
{
|
||||||
public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); }
|
||||||
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged)));
|
set { SetValue(SelectedFlipViewDayRangeProperty, value); }
|
||||||
public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged)));
|
}
|
||||||
public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged)));
|
|
||||||
|
|
||||||
public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
public ScrollViewer ActiveScrollViewer
|
||||||
public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
{
|
||||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); }
|
||||||
public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day));
|
set { SetValue(ActiveScrollViewerProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
public WinoDayTimelineCanvas ActiveCanvas
|
||||||
/// Gets or sets the day-week-month-year display type.
|
{
|
||||||
/// Orientation is not determined by this property, but Orientation property.
|
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
||||||
/// This property is used to determine the template to use for the calendar.
|
set { SetValue(ActiveCanvasProperty, value); }
|
||||||
/// </summary>
|
}
|
||||||
public CalendarDisplayType DisplayType
|
|
||||||
|
public bool IsFlipIdle
|
||||||
|
{
|
||||||
|
get { return (bool)GetValue(IsFlipIdleProperty); }
|
||||||
|
set { SetValue(IsFlipIdleProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
public ObservableCollection<DayRangeRenderModel> DayRanges
|
||||||
|
{
|
||||||
|
get { return (ObservableCollection<DayRangeRenderModel>)GetValue(DayRangesProperty); }
|
||||||
|
set { SetValue(DayRangesProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SelectedFlipViewIndex
|
||||||
|
{
|
||||||
|
get { return (int)GetValue(SelectedFlipViewIndexProperty); }
|
||||||
|
set { SetValue(SelectedFlipViewIndexProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private WinoCalendarFlipView InternalFlipView;
|
||||||
|
private Grid IdleGrid;
|
||||||
|
|
||||||
|
public WinoCalendarControl()
|
||||||
|
{
|
||||||
|
DefaultStyleKey = typeof(WinoCalendarControl);
|
||||||
|
SizeChanged += CalendarSizeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnCalendarOrientationPropertiesUpdated(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (calendar is WinoCalendarControl control)
|
||||||
{
|
{
|
||||||
get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); }
|
control.ManageCalendarOrientation();
|
||||||
set { SetValue(DisplayTypeProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public CalendarOrientation Orientation
|
|
||||||
{
|
|
||||||
get { return (CalendarOrientation)GetValue(OrientationProperty); }
|
|
||||||
set { SetValue(OrientationProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemsPanelTemplate VerticalItemsPanelTemplate
|
|
||||||
{
|
|
||||||
get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); }
|
|
||||||
set { SetValue(VerticalItemsPanelTemplateProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemsPanelTemplate HorizontalItemsPanelTemplate
|
|
||||||
{
|
|
||||||
get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); }
|
|
||||||
set { SetValue(HorizontalItemsPanelTemplateProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public DayRangeRenderModel SelectedFlipViewDayRange
|
|
||||||
{
|
|
||||||
get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); }
|
|
||||||
set { SetValue(SelectedFlipViewDayRangeProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public ScrollViewer ActiveScrollViewer
|
|
||||||
{
|
|
||||||
get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); }
|
|
||||||
set { SetValue(ActiveScrollViewerProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public WinoDayTimelineCanvas ActiveCanvas
|
|
||||||
{
|
|
||||||
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
|
||||||
set { SetValue(ActiveCanvasProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsFlipIdle
|
|
||||||
{
|
|
||||||
get { return (bool)GetValue(IsFlipIdleProperty); }
|
|
||||||
set { SetValue(IsFlipIdleProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
public ObservableCollection<DayRangeRenderModel> DayRanges
|
|
||||||
{
|
|
||||||
get { return (ObservableCollection<DayRangeRenderModel>)GetValue(DayRangesProperty); }
|
|
||||||
set { SetValue(DayRangesProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int SelectedFlipViewIndex
|
|
||||||
{
|
|
||||||
get { return (int)GetValue(SelectedFlipViewIndexProperty); }
|
|
||||||
set { SetValue(SelectedFlipViewIndexProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private WinoCalendarFlipView InternalFlipView;
|
|
||||||
private Grid IdleGrid;
|
|
||||||
|
|
||||||
public WinoCalendarControl()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(WinoCalendarControl);
|
|
||||||
SizeChanged += CalendarSizeChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnCalendarOrientationPropertiesUpdated(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (calendar is WinoCalendarControl control)
|
|
||||||
{
|
|
||||||
control.ManageCalendarOrientation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnIdleStateChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (calendar is WinoCalendarControl calendarControl)
|
|
||||||
{
|
|
||||||
calendarControl.UpdateIdleState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void OnActiveVerticalScrollViewerChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (calendar is WinoCalendarControl calendarControl)
|
|
||||||
{
|
|
||||||
if (e.OldValue is ScrollViewer oldScrollViewer)
|
|
||||||
{
|
|
||||||
calendarControl.DeregisterScrollChanges(oldScrollViewer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.NewValue is ScrollViewer newScrollViewer)
|
|
||||||
{
|
|
||||||
calendarControl.RegisterScrollChanges(newScrollViewer);
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarControl.ManageHighlightedDateRange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void OnActiveCanvasChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (calendar is WinoCalendarControl calendarControl)
|
|
||||||
{
|
|
||||||
if (e.OldValue is WinoDayTimelineCanvas oldCanvas)
|
|
||||||
{
|
|
||||||
// Dismiss any selection on the old canvas.
|
|
||||||
calendarControl.DeregisterCanvas(oldCanvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.NewValue is WinoDayTimelineCanvas newCanvas)
|
|
||||||
{
|
|
||||||
calendarControl.RegisterCanvas(newCanvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarControl.ManageHighlightedDateRange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ManageCalendarOrientation()
|
|
||||||
{
|
|
||||||
if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return;
|
|
||||||
|
|
||||||
InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ManageHighlightedDateRange()
|
|
||||||
=> 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();
|
|
||||||
|
|
||||||
InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView;
|
|
||||||
IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid;
|
|
||||||
|
|
||||||
UpdateIdleState();
|
|
||||||
ManageCalendarOrientation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateIdleState()
|
|
||||||
{
|
|
||||||
InternalFlipView.Opacity = IsFlipIdle ? 0 : 1;
|
|
||||||
IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnIdleStateChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (calendar is WinoCalendarControl calendarControl)
|
||||||
|
{
|
||||||
|
calendarControl.UpdateIdleState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void OnActiveVerticalScrollViewerChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (calendar is WinoCalendarControl calendarControl)
|
||||||
|
{
|
||||||
|
if (e.OldValue is ScrollViewer oldScrollViewer)
|
||||||
|
{
|
||||||
|
calendarControl.DeregisterScrollChanges(oldScrollViewer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewValue is ScrollViewer newScrollViewer)
|
||||||
|
{
|
||||||
|
calendarControl.RegisterScrollChanges(newScrollViewer);
|
||||||
|
}
|
||||||
|
|
||||||
|
calendarControl.ManageHighlightedDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void OnActiveCanvasChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (calendar is WinoCalendarControl calendarControl)
|
||||||
|
{
|
||||||
|
if (e.OldValue is WinoDayTimelineCanvas oldCanvas)
|
||||||
|
{
|
||||||
|
// Dismiss any selection on the old canvas.
|
||||||
|
calendarControl.DeregisterCanvas(oldCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewValue is WinoDayTimelineCanvas newCanvas)
|
||||||
|
{
|
||||||
|
calendarControl.RegisterCanvas(newCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
calendarControl.ManageHighlightedDateRange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ManageCalendarOrientation()
|
||||||
|
{
|
||||||
|
if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return;
|
||||||
|
|
||||||
|
InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ManageHighlightedDateRange()
|
||||||
|
=> 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();
|
||||||
|
|
||||||
|
InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView;
|
||||||
|
IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid;
|
||||||
|
|
||||||
|
UpdateIdleState();
|
||||||
|
ManageCalendarOrientation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIdleState()
|
||||||
|
{
|
||||||
|
InternalFlipView.Opacity = IsFlipIdle ? 0 : 1;
|
||||||
|
IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,179 +8,178 @@ using Windows.UI.Xaml.Controls;
|
|||||||
using Wino.Core.Domain.Collections;
|
using Wino.Core.Domain.Collections;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class WinoCalendarFlipView : CustomCalendarFlipView
|
||||||
{
|
{
|
||||||
public class WinoCalendarFlipView : CustomCalendarFlipView
|
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
|
||||||
{
|
{
|
||||||
public static readonly DependencyProperty IsIdleProperty = DependencyProperty.Register(nameof(IsIdle), typeof(bool), typeof(WinoCalendarFlipView), new PropertyMetadata(true));
|
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
||||||
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
|
set { SetValue(ActiveCanvasProperty, value); }
|
||||||
public static readonly DependencyProperty ActiveVerticalScrollViewerProperty = DependencyProperty.Register(nameof(ActiveVerticalScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the active canvas that is currently displayed in the flip view.
|
/// Gets or sets the scroll viewer that is currently active in the flip view.
|
||||||
/// Each day-range of flip view item has a canvas that displays the day timeline.
|
/// It's the vertical scroll that scrolls the timeline only, not the header part that belongs
|
||||||
/// </summary>
|
/// to parent FlipView control.
|
||||||
public WinoDayTimelineCanvas ActiveCanvas
|
/// </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); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinoCalendarFlipView()
|
||||||
|
{
|
||||||
|
RegisterPropertyChangedCallback(SelectedIndexProperty, new DependencyPropertyChangedCallback(OnSelectedIndexUpdated));
|
||||||
|
RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnItemsSourceChanged(DependencyObject d, DependencyProperty e)
|
||||||
|
{
|
||||||
|
if (d is WinoCalendarFlipView flipView)
|
||||||
{
|
{
|
||||||
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
flipView.RegisterItemsSourceChange();
|
||||||
set { SetValue(ActiveCanvasProperty, value); }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnSelectedIndexUpdated(DependencyObject d, DependencyProperty e)
|
||||||
|
{
|
||||||
|
if (d is WinoCalendarFlipView flipView)
|
||||||
|
{
|
||||||
|
flipView.UpdateActiveCanvas();
|
||||||
|
flipView.UpdateActiveScrollViewer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterItemsSourceChange()
|
||||||
|
{
|
||||||
|
if (GetItemsSource() is INotifyCollectionChanged notifyCollectionChanged)
|
||||||
|
{
|
||||||
|
notifyCollectionChanged.CollectionChanged += ItemsSourceUpdated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ItemsSourceUpdated(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
IsIdle = e.Action == NotifyCollectionChangedAction.Reset || e.Action == NotifyCollectionChangedAction.Replace;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<FlipViewItem> GetCurrentFlipViewItem()
|
||||||
|
{
|
||||||
|
// TODO: Refactor this mechanism by listening to PrepareContainerForItemOverride and Loaded events together.
|
||||||
|
while (ContainerFromIndex(SelectedIndex) == null)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return ContainerFromIndex(SelectedIndex) as FlipViewItem;
|
||||||
/// 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); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public WinoCalendarFlipView()
|
}
|
||||||
{
|
|
||||||
RegisterPropertyChangedCallback(SelectedIndexProperty, new DependencyPropertyChangedCallback(OnSelectedIndexUpdated));
|
|
||||||
RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnItemsSourceChanged(DependencyObject d, DependencyProperty e)
|
private void UpdateActiveScrollViewer()
|
||||||
|
{
|
||||||
|
if (SelectedIndex < 0)
|
||||||
|
ActiveVerticalScrollViewer = null;
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (d is WinoCalendarFlipView flipView)
|
GetCurrentFlipViewItem().ContinueWith(task =>
|
||||||
{
|
{
|
||||||
flipView.RegisterItemsSourceChange();
|
if (task.IsCompletedSuccessfully)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnSelectedIndexUpdated(DependencyObject d, DependencyProperty e)
|
|
||||||
{
|
|
||||||
if (d is WinoCalendarFlipView flipView)
|
|
||||||
{
|
|
||||||
flipView.UpdateActiveCanvas();
|
|
||||||
flipView.UpdateActiveScrollViewer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterItemsSourceChange()
|
|
||||||
{
|
|
||||||
if (GetItemsSource() is INotifyCollectionChanged notifyCollectionChanged)
|
|
||||||
{
|
|
||||||
notifyCollectionChanged.CollectionChanged += ItemsSourceUpdated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ItemsSourceUpdated(object sender, NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
IsIdle = e.Action == NotifyCollectionChangedAction.Reset || e.Action == NotifyCollectionChangedAction.Replace;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<FlipViewItem> GetCurrentFlipViewItem()
|
|
||||||
{
|
|
||||||
// TODO: Refactor this mechanism by listening to PrepareContainerForItemOverride and Loaded events together.
|
|
||||||
while (ContainerFromIndex(SelectedIndex) == null)
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ContainerFromIndex(SelectedIndex) as FlipViewItem;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateActiveScrollViewer()
|
|
||||||
{
|
|
||||||
if (SelectedIndex < 0)
|
|
||||||
ActiveVerticalScrollViewer = null;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GetCurrentFlipViewItem().ContinueWith(task =>
|
|
||||||
{
|
{
|
||||||
if (task.IsCompletedSuccessfully)
|
var flipViewItem = task.Result;
|
||||||
|
|
||||||
|
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||||
{
|
{
|
||||||
var flipViewItem = task.Result;
|
ActiveVerticalScrollViewer = flipViewItem.FindDescendant<ScrollViewer>();
|
||||||
|
});
|
||||||
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
|
||||||
{
|
|
||||||
ActiveVerticalScrollViewer = flipViewItem.FindDescendant<ScrollViewer>();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateActiveCanvas()
|
|
||||||
{
|
|
||||||
if (SelectedIndex < 0)
|
|
||||||
ActiveCanvas = null;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GetCurrentFlipViewItem().ContinueWith(task =>
|
|
||||||
{
|
|
||||||
if (task.IsCompletedSuccessfully)
|
|
||||||
{
|
|
||||||
var flipViewItem = task.Result;
|
|
||||||
|
|
||||||
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
|
||||||
{
|
|
||||||
ActiveCanvas = flipViewItem.FindDescendant<WinoDayTimelineCanvas>();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
|
|
||||||
{
|
|
||||||
// Find the day range that contains the date.
|
|
||||||
var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date));
|
|
||||||
|
|
||||||
if (dayRange != null)
|
|
||||||
{
|
|
||||||
var navigationItemIndex = GetItemsSource().IndexOf(dayRange);
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource()
|
|
||||||
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateActiveCanvas()
|
||||||
|
{
|
||||||
|
if (SelectedIndex < 0)
|
||||||
|
ActiveCanvas = null;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GetCurrentFlipViewItem().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
var flipViewItem = task.Result;
|
||||||
|
|
||||||
|
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||||
|
{
|
||||||
|
ActiveCanvas = flipViewItem.FindDescendant<WinoDayTimelineCanvas>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
|
||||||
|
{
|
||||||
|
// Find the day range that contains the date.
|
||||||
|
var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date));
|
||||||
|
|
||||||
|
if (dayRange != null)
|
||||||
|
{
|
||||||
|
var navigationItemIndex = GetItemsSource().IndexOf(dayRange);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource()
|
||||||
|
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,284 +11,283 @@ using Wino.Calendar.Models;
|
|||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class WinoCalendarPanel : Panel
|
||||||
{
|
{
|
||||||
public class WinoCalendarPanel : Panel
|
private const double LastItemRightExtraMargin = 12d;
|
||||||
|
|
||||||
|
// Store each ICalendarItem measurements by their Id.
|
||||||
|
private readonly Dictionary<ICalendarItem, CalendarItemMeasurement> _measurements = new Dictionary<ICalendarItem, CalendarItemMeasurement>();
|
||||||
|
|
||||||
|
public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0)));
|
||||||
|
public static readonly DependencyProperty HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d));
|
||||||
|
public static readonly DependencyProperty PeriodProperty = DependencyProperty.Register(nameof(Period), typeof(ITimePeriod), typeof(WinoCalendarPanel), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public ITimePeriod Period
|
||||||
{
|
{
|
||||||
private const double LastItemRightExtraMargin = 12d;
|
get { return (ITimePeriod)GetValue(PeriodProperty); }
|
||||||
|
set { SetValue(PeriodProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
// Store each ICalendarItem measurements by their Id.
|
public double HourHeight
|
||||||
private readonly Dictionary<ICalendarItem, CalendarItemMeasurement> _measurements = new Dictionary<ICalendarItem, CalendarItemMeasurement>();
|
{
|
||||||
|
get { return (double)GetValue(HourHeightProperty); }
|
||||||
|
set { SetValue(HourHeightProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0)));
|
public Thickness EventItemMargin
|
||||||
public static readonly DependencyProperty HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d));
|
{
|
||||||
public static readonly DependencyProperty PeriodProperty = DependencyProperty.Register(nameof(Period), typeof(ITimePeriod), typeof(WinoCalendarPanel), new PropertyMetadata(null));
|
get { return (Thickness)GetValue(EventItemMarginProperty); }
|
||||||
|
set { SetValue(EventItemMarginProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public ITimePeriod Period
|
private void ResetMeasurements() => _measurements.Clear();
|
||||||
|
|
||||||
|
private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight)
|
||||||
|
{
|
||||||
|
var childStart = calendarItemViewModel.StartDate;
|
||||||
|
|
||||||
|
if (childStart <= Period.Start)
|
||||||
{
|
{
|
||||||
get { return (ITimePeriod)GetValue(PeriodProperty); }
|
// Event started before or exactly at the periods tart. This might be a multi-day event.
|
||||||
set { SetValue(PeriodProperty, value); }
|
// We can simply consider event must not have a top margin.
|
||||||
|
|
||||||
|
return 0d;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double HourHeight
|
double minutesFromStart = (childStart - Period.Start).TotalMinutes;
|
||||||
|
return (minutesFromStart / 1440) * availableHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
||||||
|
{
|
||||||
|
return (calendarItemMeasurement.Right - calendarItemMeasurement.Left) * availableWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
||||||
|
=> availableWidth * calendarItemMeasurement.Left;
|
||||||
|
|
||||||
|
private double GetChildHeight(ICalendarItem child)
|
||||||
|
{
|
||||||
|
// All day events are not measured.
|
||||||
|
if (child.IsAllDayEvent) return 0;
|
||||||
|
|
||||||
|
double childDurationInMinutes = 0d;
|
||||||
|
double availableHeight = HourHeight * 24;
|
||||||
|
|
||||||
|
var periodRelation = child.Period.GetRelation(Period);
|
||||||
|
|
||||||
|
// Debug.WriteLine($"Render relation of {child.Title} ({child.Period.Start} - {child.Period.End}) is {periodRelation} with {Period.Start.Day}");
|
||||||
|
|
||||||
|
if (!child.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
get { return (double)GetValue(HourHeightProperty); }
|
childDurationInMinutes = child.Period.Duration.TotalMinutes;
|
||||||
set { SetValue(HourHeightProperty, value); }
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Multi-day event.
|
||||||
|
// Check how many of the event falls into the current period.
|
||||||
|
childDurationInMinutes = (child.Period.End - Period.Start).TotalMinutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Thickness EventItemMargin
|
return (childDurationInMinutes / 1440) * availableHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
|
{
|
||||||
|
ResetMeasurements();
|
||||||
|
return base.MeasureOverride(availableSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
|
{
|
||||||
|
if (Period == null || HourHeight == 0d) return finalSize;
|
||||||
|
|
||||||
|
// Measure/arrange each child height and width.
|
||||||
|
// This is a vertical calendar. Therefore the height of each child is the duration of the event.
|
||||||
|
// Children weights for left and right will be saved if they don't exist.
|
||||||
|
// This is important because we don't want to measure the weights again.
|
||||||
|
// They don't change until new event is added or removed.
|
||||||
|
// Width of the each child may depend on the rectangle packing algorithm.
|
||||||
|
// Children are first categorized into columns. Then each column is shifted to the left until
|
||||||
|
// no overlap occurs. The width of each child is calculated based on the number of columns it spans.
|
||||||
|
|
||||||
|
double availableHeight = finalSize.Height;
|
||||||
|
double availableWidth = finalSize.Width;
|
||||||
|
|
||||||
|
var calendarControls = Children.Cast<ContentPresenter>();
|
||||||
|
|
||||||
|
if (!calendarControls.Any()) return base.ArrangeOverride(finalSize);
|
||||||
|
|
||||||
|
var events = calendarControls.Select(a => a.Content as CalendarItemViewModel);
|
||||||
|
|
||||||
|
LayoutEvents(events);
|
||||||
|
|
||||||
|
foreach (var control in calendarControls)
|
||||||
{
|
{
|
||||||
get { return (Thickness)GetValue(EventItemMarginProperty); }
|
// We can't arrange this child.
|
||||||
set { SetValue(EventItemMarginProperty, value); }
|
if (!(control.Content is ICalendarItem child)) continue;
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetMeasurements() => _measurements.Clear();
|
bool isHorizontallyLastItem = false;
|
||||||
|
|
||||||
private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight)
|
double childWidth = 0,
|
||||||
{
|
childHeight = Math.Max(0, GetChildHeight(child)),
|
||||||
var childStart = calendarItemViewModel.StartDate;
|
childTop = Math.Max(0, GetChildTopMargin(child, availableHeight)),
|
||||||
|
childLeft = 0;
|
||||||
|
|
||||||
if (childStart <= Period.Start)
|
// No need to measure anything here.
|
||||||
{
|
if (childHeight == 0) continue;
|
||||||
// Event started before or exactly at the periods tart. This might be a multi-day event.
|
|
||||||
// We can simply consider event must not have a top margin.
|
|
||||||
|
|
||||||
return 0d;
|
if (!_measurements.ContainsKey(child))
|
||||||
}
|
|
||||||
|
|
||||||
double minutesFromStart = (childStart - Period.Start).TotalMinutes;
|
|
||||||
return (minutesFromStart / 1440) * availableHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
|
||||||
{
|
|
||||||
return (calendarItemMeasurement.Right - calendarItemMeasurement.Left) * availableWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
|
||||||
=> availableWidth * calendarItemMeasurement.Left;
|
|
||||||
|
|
||||||
private double GetChildHeight(ICalendarItem child)
|
|
||||||
{
|
|
||||||
// All day events are not measured.
|
|
||||||
if (child.IsAllDayEvent) return 0;
|
|
||||||
|
|
||||||
double childDurationInMinutes = 0d;
|
|
||||||
double availableHeight = HourHeight * 24;
|
|
||||||
|
|
||||||
var periodRelation = child.Period.GetRelation(Period);
|
|
||||||
|
|
||||||
// Debug.WriteLine($"Render relation of {child.Title} ({child.Period.Start} - {child.Period.End}) is {periodRelation} with {Period.Start.Day}");
|
|
||||||
|
|
||||||
if (!child.IsMultiDayEvent)
|
|
||||||
{
|
|
||||||
childDurationInMinutes = child.Period.Duration.TotalMinutes;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// Multi-day event.
|
// Multi-day event.
|
||||||
// Check how many of the event falls into the current period.
|
|
||||||
childDurationInMinutes = (child.Period.End - Period.Start).TotalMinutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (childDurationInMinutes / 1440) * availableHeight;
|
childLeft = 0;
|
||||||
}
|
childWidth = availableWidth;
|
||||||
|
|
||||||
protected override Size MeasureOverride(Size availableSize)
|
|
||||||
{
|
|
||||||
ResetMeasurements();
|
|
||||||
return base.MeasureOverride(availableSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Size ArrangeOverride(Size finalSize)
|
|
||||||
{
|
|
||||||
if (Period == null || HourHeight == 0d) return finalSize;
|
|
||||||
|
|
||||||
// Measure/arrange each child height and width.
|
|
||||||
// This is a vertical calendar. Therefore the height of each child is the duration of the event.
|
|
||||||
// Children weights for left and right will be saved if they don't exist.
|
|
||||||
// This is important because we don't want to measure the weights again.
|
|
||||||
// They don't change until new event is added or removed.
|
|
||||||
// Width of the each child may depend on the rectangle packing algorithm.
|
|
||||||
// Children are first categorized into columns. Then each column is shifted to the left until
|
|
||||||
// no overlap occurs. The width of each child is calculated based on the number of columns it spans.
|
|
||||||
|
|
||||||
double availableHeight = finalSize.Height;
|
|
||||||
double availableWidth = finalSize.Width;
|
|
||||||
|
|
||||||
var calendarControls = Children.Cast<ContentPresenter>();
|
|
||||||
|
|
||||||
if (!calendarControls.Any()) return base.ArrangeOverride(finalSize);
|
|
||||||
|
|
||||||
var events = calendarControls.Select(a => a.Content as CalendarItemViewModel);
|
|
||||||
|
|
||||||
LayoutEvents(events);
|
|
||||||
|
|
||||||
foreach (var control in calendarControls)
|
|
||||||
{
|
|
||||||
// We can't arrange this child.
|
|
||||||
if (!(control.Content is ICalendarItem child)) continue;
|
|
||||||
|
|
||||||
bool isHorizontallyLastItem = false;
|
|
||||||
|
|
||||||
double childWidth = 0,
|
|
||||||
childHeight = Math.Max(0, GetChildHeight(child)),
|
|
||||||
childTop = Math.Max(0, GetChildTopMargin(child, availableHeight)),
|
|
||||||
childLeft = 0;
|
|
||||||
|
|
||||||
// No need to measure anything here.
|
|
||||||
if (childHeight == 0) continue;
|
|
||||||
|
|
||||||
if (!_measurements.ContainsKey(child))
|
|
||||||
{
|
|
||||||
// Multi-day event.
|
|
||||||
|
|
||||||
childLeft = 0;
|
|
||||||
childWidth = availableWidth;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var childMeasurement = _measurements[child];
|
|
||||||
|
|
||||||
childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
|
|
||||||
childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
|
|
||||||
|
|
||||||
isHorizontallyLastItem = childMeasurement.Right == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add additional right margin to items that falls on the right edge of the panel.
|
|
||||||
double extraRightMargin = 0;
|
|
||||||
|
|
||||||
// Multi-day events don't have any margin and their hit test is disabled.
|
|
||||||
if (!child.IsMultiDayEvent)
|
|
||||||
{
|
|
||||||
// Max of 5% of the width or 20px max.
|
|
||||||
extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (childWidth < 0) childWidth = 1;
|
|
||||||
|
|
||||||
// Regular events must have 2px margin
|
|
||||||
if (!child.IsMultiDayEvent && !child.IsAllDayEvent)
|
|
||||||
{
|
|
||||||
childLeft += 2;
|
|
||||||
childTop += 2;
|
|
||||||
childHeight -= 2;
|
|
||||||
childWidth -= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, Math.Max(childWidth - extraRightMargin, 1), childHeight);
|
|
||||||
|
|
||||||
// Make sure measured size will fit in the arranged box.
|
|
||||||
var measureSize = arrangementRect.ToSize();
|
|
||||||
control.Measure(measureSize);
|
|
||||||
control.Arrange(arrangementRect);
|
|
||||||
|
|
||||||
//Debug.WriteLine($"{child.Title}, Measured: {measureSize}, Arranged: {arrangementRect}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return finalSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region ColumSpanning and Packing Algorithm
|
|
||||||
|
|
||||||
private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement)
|
|
||||||
{
|
|
||||||
if (_measurements.ContainsKey(calendarItem))
|
|
||||||
{
|
|
||||||
_measurements[calendarItem] = measurement;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_measurements.Add(calendarItem, measurement);
|
var childMeasurement = _measurements[child];
|
||||||
|
|
||||||
|
childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
|
||||||
|
childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
|
||||||
|
|
||||||
|
isHorizontallyLastItem = childMeasurement.Right == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add additional right margin to items that falls on the right edge of the panel.
|
||||||
|
double extraRightMargin = 0;
|
||||||
|
|
||||||
|
// Multi-day events don't have any margin and their hit test is disabled.
|
||||||
|
if (!child.IsMultiDayEvent)
|
||||||
|
{
|
||||||
|
// Max of 5% of the width or 20px max.
|
||||||
|
extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childWidth < 0) childWidth = 1;
|
||||||
|
|
||||||
|
// Regular events must have 2px margin
|
||||||
|
if (!child.IsMultiDayEvent && !child.IsAllDayEvent)
|
||||||
|
{
|
||||||
|
childLeft += 2;
|
||||||
|
childTop += 2;
|
||||||
|
childHeight -= 2;
|
||||||
|
childWidth -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, Math.Max(childWidth - extraRightMargin, 1), childHeight);
|
||||||
|
|
||||||
|
// Make sure measured size will fit in the arranged box.
|
||||||
|
var measureSize = arrangementRect.ToSize();
|
||||||
|
control.Measure(measureSize);
|
||||||
|
control.Arrange(arrangementRect);
|
||||||
|
|
||||||
|
//Debug.WriteLine($"{child.Title}, Measured: {measureSize}, Arranged: {arrangementRect}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick the left and right positions of each event, such that there are no overlap.
|
|
||||||
private void LayoutEvents(IEnumerable<ICalendarItem> events)
|
return finalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ColumSpanning and Packing Algorithm
|
||||||
|
|
||||||
|
private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement)
|
||||||
|
{
|
||||||
|
if (_measurements.ContainsKey(calendarItem))
|
||||||
{
|
{
|
||||||
var columns = new List<List<ICalendarItem>>();
|
_measurements[calendarItem] = measurement;
|
||||||
DateTime? lastEventEnding = null;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_measurements.Add(calendarItem, measurement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var ev in events.OrderBy(ev => ev.StartDate).ThenBy(ev => ev.EndDate))
|
// Pick the left and right positions of each event, such that there are no overlap.
|
||||||
{
|
private void LayoutEvents(IEnumerable<ICalendarItem> events)
|
||||||
// Multi-day events are not measured.
|
{
|
||||||
if (ev.IsMultiDayEvent) continue;
|
var columns = new List<List<ICalendarItem>>();
|
||||||
|
DateTime? lastEventEnding = null;
|
||||||
|
|
||||||
if (ev.Period.Start >= lastEventEnding)
|
foreach (var ev in events.OrderBy(ev => ev.StartDate).ThenBy(ev => ev.EndDate))
|
||||||
{
|
{
|
||||||
PackEvents(columns);
|
// Multi-day events are not measured.
|
||||||
columns.Clear();
|
if (ev.IsMultiDayEvent) continue;
|
||||||
lastEventEnding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool placed = false;
|
if (ev.Period.Start >= lastEventEnding)
|
||||||
|
|
||||||
foreach (var col in columns)
|
|
||||||
{
|
|
||||||
if (!col.Last().Period.OverlapsWith(ev.Period))
|
|
||||||
{
|
|
||||||
col.Add(ev);
|
|
||||||
placed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!placed)
|
|
||||||
{
|
|
||||||
columns.Add(new List<ICalendarItem> { ev });
|
|
||||||
}
|
|
||||||
if (lastEventEnding == null || ev.Period.End > lastEventEnding.Value)
|
|
||||||
{
|
|
||||||
lastEventEnding = ev.Period.End;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (columns.Count > 0)
|
|
||||||
{
|
{
|
||||||
PackEvents(columns);
|
PackEvents(columns);
|
||||||
|
columns.Clear();
|
||||||
|
lastEventEnding = null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Set the left and right positions for each event in the connected group.
|
bool placed = false;
|
||||||
private void PackEvents(List<List<ICalendarItem>> columns)
|
|
||||||
{
|
|
||||||
float numColumns = columns.Count;
|
|
||||||
int iColumn = 0;
|
|
||||||
|
|
||||||
foreach (var col in columns)
|
foreach (var col in columns)
|
||||||
{
|
{
|
||||||
foreach (var ev in col)
|
if (!col.Last().Period.OverlapsWith(ev.Period))
|
||||||
{
|
{
|
||||||
int colSpan = ExpandEvent(ev, iColumn, columns);
|
col.Add(ev);
|
||||||
|
placed = true;
|
||||||
var leftWeight = iColumn / numColumns;
|
break;
|
||||||
var rightWeight = (iColumn + colSpan) / numColumns;
|
|
||||||
|
|
||||||
AddOrUpdateMeasurement(ev, new CalendarItemMeasurement(leftWeight, rightWeight));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iColumn++;
|
|
||||||
}
|
}
|
||||||
}
|
if (!placed)
|
||||||
|
|
||||||
// Checks how many columns the event can expand into, without colliding with other events.
|
|
||||||
private int ExpandEvent(ICalendarItem ev, int iColumn, List<List<ICalendarItem>> columns)
|
|
||||||
{
|
|
||||||
int colSpan = 1;
|
|
||||||
|
|
||||||
foreach (var col in columns.Skip(iColumn + 1))
|
|
||||||
{
|
{
|
||||||
foreach (var ev1 in col)
|
columns.Add(new List<ICalendarItem> { ev });
|
||||||
{
|
}
|
||||||
if (ev1.Period.OverlapsWith(ev.Period)) return colSpan;
|
if (lastEventEnding == null || ev.Period.End > lastEventEnding.Value)
|
||||||
}
|
{
|
||||||
|
lastEventEnding = ev.Period.End;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (columns.Count > 0)
|
||||||
|
{
|
||||||
|
PackEvents(columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
colSpan++;
|
// Set the left and right positions for each event in the connected group.
|
||||||
|
private void PackEvents(List<List<ICalendarItem>> columns)
|
||||||
|
{
|
||||||
|
float numColumns = columns.Count;
|
||||||
|
int iColumn = 0;
|
||||||
|
|
||||||
|
foreach (var col in columns)
|
||||||
|
{
|
||||||
|
foreach (var ev in col)
|
||||||
|
{
|
||||||
|
int colSpan = ExpandEvent(ev, iColumn, columns);
|
||||||
|
|
||||||
|
var leftWeight = iColumn / numColumns;
|
||||||
|
var rightWeight = (iColumn + colSpan) / numColumns;
|
||||||
|
|
||||||
|
AddOrUpdateMeasurement(ev, new CalendarItemMeasurement(leftWeight, rightWeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
return colSpan;
|
iColumn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks how many columns the event can expand into, without colliding with other events.
|
||||||
|
private int ExpandEvent(ICalendarItem ev, int iColumn, List<List<ICalendarItem>> columns)
|
||||||
|
{
|
||||||
|
int colSpan = 1;
|
||||||
|
|
||||||
|
foreach (var col in columns.Skip(iColumn + 1))
|
||||||
|
{
|
||||||
|
foreach (var ev1 in col)
|
||||||
|
{
|
||||||
|
if (ev1.Period.OverlapsWith(ev.Period)) return colSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
colSpan++;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
return colSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,89 +4,88 @@ using Windows.UI.Xaml;
|
|||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class WinoCalendarTypeSelectorControl : Control
|
||||||
{
|
{
|
||||||
public class WinoCalendarTypeSelectorControl : Control
|
private const string PART_TodayButton = nameof(PART_TodayButton);
|
||||||
|
private const string PART_DayToggle = nameof(PART_DayToggle);
|
||||||
|
private const string PART_WeekToggle = nameof(PART_WeekToggle);
|
||||||
|
private const string PART_MonthToggle = nameof(PART_MonthToggle);
|
||||||
|
private const string PART_YearToggle = nameof(PART_YearToggle);
|
||||||
|
|
||||||
|
public static readonly DependencyProperty SelectedTypeProperty = DependencyProperty.Register(nameof(SelectedType), typeof(CalendarDisplayType), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(CalendarDisplayType.Week));
|
||||||
|
public static readonly DependencyProperty DisplayDayCountProperty = DependencyProperty.Register(nameof(DisplayDayCount), typeof(int), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(0));
|
||||||
|
public static readonly DependencyProperty TodayClickedCommandProperty = DependencyProperty.Register(nameof(TodayClickedCommand), typeof(ICommand), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public ICommand TodayClickedCommand
|
||||||
{
|
{
|
||||||
private const string PART_TodayButton = nameof(PART_TodayButton);
|
get { return (ICommand)GetValue(TodayClickedCommandProperty); }
|
||||||
private const string PART_DayToggle = nameof(PART_DayToggle);
|
set { SetValue(TodayClickedCommandProperty, value); }
|
||||||
private const string PART_WeekToggle = nameof(PART_WeekToggle);
|
}
|
||||||
private const string PART_MonthToggle = nameof(PART_MonthToggle);
|
|
||||||
private const string PART_YearToggle = nameof(PART_YearToggle);
|
|
||||||
|
|
||||||
public static readonly DependencyProperty SelectedTypeProperty = DependencyProperty.Register(nameof(SelectedType), typeof(CalendarDisplayType), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(CalendarDisplayType.Week));
|
public CalendarDisplayType SelectedType
|
||||||
public static readonly DependencyProperty DisplayDayCountProperty = DependencyProperty.Register(nameof(DisplayDayCount), typeof(int), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(0));
|
{
|
||||||
public static readonly DependencyProperty TodayClickedCommandProperty = DependencyProperty.Register(nameof(TodayClickedCommand), typeof(ICommand), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(null));
|
get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); }
|
||||||
|
set { SetValue(SelectedTypeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public ICommand TodayClickedCommand
|
public int DisplayDayCount
|
||||||
{
|
{
|
||||||
get { return (ICommand)GetValue(TodayClickedCommandProperty); }
|
get { return (int)GetValue(DisplayDayCountProperty); }
|
||||||
set { SetValue(TodayClickedCommandProperty, value); }
|
set { SetValue(DisplayDayCountProperty, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public CalendarDisplayType SelectedType
|
private AppBarButton _todayButton;
|
||||||
{
|
private AppBarToggleButton _dayToggle;
|
||||||
get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); }
|
private AppBarToggleButton _weekToggle;
|
||||||
set { SetValue(SelectedTypeProperty, value); }
|
private AppBarToggleButton _monthToggle;
|
||||||
}
|
private AppBarToggleButton _yearToggle;
|
||||||
|
|
||||||
public int DisplayDayCount
|
public WinoCalendarTypeSelectorControl()
|
||||||
{
|
{
|
||||||
get { return (int)GetValue(DisplayDayCountProperty); }
|
DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl);
|
||||||
set { SetValue(DisplayDayCountProperty, value); }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private AppBarButton _todayButton;
|
protected override void OnApplyTemplate()
|
||||||
private AppBarToggleButton _dayToggle;
|
{
|
||||||
private AppBarToggleButton _weekToggle;
|
base.OnApplyTemplate();
|
||||||
private AppBarToggleButton _monthToggle;
|
|
||||||
private AppBarToggleButton _yearToggle;
|
|
||||||
|
|
||||||
public WinoCalendarTypeSelectorControl()
|
_todayButton = GetTemplateChild(PART_TodayButton) as AppBarButton;
|
||||||
{
|
_dayToggle = GetTemplateChild(PART_DayToggle) as AppBarToggleButton;
|
||||||
DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl);
|
_weekToggle = GetTemplateChild(PART_WeekToggle) as AppBarToggleButton;
|
||||||
}
|
_monthToggle = GetTemplateChild(PART_MonthToggle) as AppBarToggleButton;
|
||||||
|
_yearToggle = GetTemplateChild(PART_YearToggle) as AppBarToggleButton;
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
Guard.IsNotNull(_todayButton, nameof(_todayButton));
|
||||||
{
|
Guard.IsNotNull(_dayToggle, nameof(_dayToggle));
|
||||||
base.OnApplyTemplate();
|
Guard.IsNotNull(_weekToggle, nameof(_weekToggle));
|
||||||
|
Guard.IsNotNull(_monthToggle, nameof(_monthToggle));
|
||||||
|
Guard.IsNotNull(_yearToggle, nameof(_yearToggle));
|
||||||
|
|
||||||
_todayButton = GetTemplateChild(PART_TodayButton) as AppBarButton;
|
_todayButton.Click += TodayClicked;
|
||||||
_dayToggle = GetTemplateChild(PART_DayToggle) as AppBarToggleButton;
|
|
||||||
_weekToggle = GetTemplateChild(PART_WeekToggle) as AppBarToggleButton;
|
|
||||||
_monthToggle = GetTemplateChild(PART_MonthToggle) as AppBarToggleButton;
|
|
||||||
_yearToggle = GetTemplateChild(PART_YearToggle) as AppBarToggleButton;
|
|
||||||
|
|
||||||
Guard.IsNotNull(_todayButton, nameof(_todayButton));
|
_dayToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Day); };
|
||||||
Guard.IsNotNull(_dayToggle, nameof(_dayToggle));
|
_weekToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Week); };
|
||||||
Guard.IsNotNull(_weekToggle, nameof(_weekToggle));
|
_monthToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Month); };
|
||||||
Guard.IsNotNull(_monthToggle, nameof(_monthToggle));
|
_yearToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Year); };
|
||||||
Guard.IsNotNull(_yearToggle, nameof(_yearToggle));
|
|
||||||
|
|
||||||
_todayButton.Click += TodayClicked;
|
UpdateToggleButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
_dayToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Day); };
|
private void TodayClicked(object sender, RoutedEventArgs e) => TodayClickedCommand?.Execute(null);
|
||||||
_weekToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Week); };
|
|
||||||
_monthToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Month); };
|
|
||||||
_yearToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Year); };
|
|
||||||
|
|
||||||
UpdateToggleButtonStates();
|
private void SetSelectedType(CalendarDisplayType type)
|
||||||
}
|
{
|
||||||
|
SelectedType = type;
|
||||||
|
UpdateToggleButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
private void TodayClicked(object sender, RoutedEventArgs e) => TodayClickedCommand?.Execute(null);
|
private void UpdateToggleButtonStates()
|
||||||
|
{
|
||||||
private void SetSelectedType(CalendarDisplayType type)
|
_dayToggle.IsChecked = SelectedType == CalendarDisplayType.Day;
|
||||||
{
|
_weekToggle.IsChecked = SelectedType == CalendarDisplayType.Week;
|
||||||
SelectedType = type;
|
_monthToggle.IsChecked = SelectedType == CalendarDisplayType.Month;
|
||||||
UpdateToggleButtonStates();
|
_yearToggle.IsChecked = SelectedType == CalendarDisplayType.Year;
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateToggleButtonStates()
|
|
||||||
{
|
|
||||||
_dayToggle.IsChecked = SelectedType == CalendarDisplayType.Day;
|
|
||||||
_weekToggle.IsChecked = SelectedType == CalendarDisplayType.Week;
|
|
||||||
_monthToggle.IsChecked = SelectedType == CalendarDisplayType.Month;
|
|
||||||
_yearToggle.IsChecked = SelectedType == CalendarDisplayType.Year;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,140 +8,139 @@ using Windows.UI.Xaml.Media;
|
|||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Helpers;
|
using Wino.Helpers;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class WinoCalendarView : Control
|
||||||
{
|
{
|
||||||
public class WinoCalendarView : Control
|
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(null));
|
||||||
|
|
||||||
|
public Color TodayBackgroundColor
|
||||||
{
|
{
|
||||||
private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder);
|
get { return (Color)GetValue(TodayBackgroundColorProperty); }
|
||||||
private const string PART_CalendarView = nameof(PART_CalendarView);
|
set { SetValue(TodayBackgroundColorProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged)));
|
/// <summary>
|
||||||
public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged)));
|
/// Gets or sets the command to execute when a date is picked.
|
||||||
public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null));
|
/// Unused.
|
||||||
public static readonly DependencyProperty TodayBackgroundColorProperty = DependencyProperty.Register(nameof(TodayBackgroundColor), typeof(Color), typeof(WinoCalendarView), new PropertyMetadata(null));
|
/// </summary>
|
||||||
|
public ICommand DateClickedCommand
|
||||||
|
{
|
||||||
|
get { return (ICommand)GetValue(DateClickedCommandProperty); }
|
||||||
|
set { SetValue(DateClickedCommandProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public Color TodayBackgroundColor
|
/// <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;
|
||||||
|
|
||||||
|
public WinoCalendarView()
|
||||||
|
{
|
||||||
|
DefaultStyleKey = typeof(WinoCalendarView);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView;
|
||||||
|
|
||||||
|
Guard.IsNotNull(CalendarView, nameof(CalendarView));
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
CalendarView.RegisterPropertyChangedCallback(CalendarView.DisplayModeProperty, (s, e) => UpdateVisibleDateRangeBackgrounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InternalCalendarViewSelectionChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.AddedDates?.Count > 0)
|
||||||
{
|
{
|
||||||
get { return (Color)GetValue(TodayBackgroundColorProperty); }
|
var clickedDate = args.AddedDates[0].Date;
|
||||||
set { SetValue(TodayBackgroundColorProperty, value); }
|
SetInnerDisplayDate(clickedDate);
|
||||||
|
|
||||||
|
var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate);
|
||||||
|
DateClickedCommand?.Execute(clickArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Reset selection, we don't show selected dates but react to them.
|
||||||
/// Gets or sets the command to execute when a date is picked.
|
CalendarView.SelectedDates.Clear();
|
||||||
/// Unused.
|
}
|
||||||
/// </summary>
|
|
||||||
public ICommand DateClickedCommand
|
private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinoCalendarView control)
|
||||||
{
|
{
|
||||||
get { return (ICommand)GetValue(DateClickedCommandProperty); }
|
control.UpdateVisibleDateRangeBackgrounds();
|
||||||
set { SetValue(DateClickedCommandProperty, value); }
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void SetInnerDisplayDate(DateTime dateTime) => CalendarView?.SetDisplayDate(dateTime);
|
||||||
/// Gets or sets the highlighted range of dates.
|
|
||||||
/// </summary>
|
// Changing selected dates will trigger the selection changed event.
|
||||||
public DateRange HighlightedDateRange
|
// 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)
|
||||||
{
|
{
|
||||||
get { return (DateRange)GetValue(HighlightedDateRangeProperty); }
|
control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate);
|
||||||
set { SetValue(HighlightedDateRangeProperty, value); }
|
control.UpdateVisibleDateRangeBackgrounds();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Brush VisibleDateBackground
|
public void UpdateVisibleDateRangeBackgrounds()
|
||||||
|
{
|
||||||
|
if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundColor == null || CalendarView == null) return;
|
||||||
|
|
||||||
|
var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants<CalendarViewDayItem>(CalendarView);
|
||||||
|
|
||||||
|
foreach (var calendarDayItem in markDateCalendarDayItems)
|
||||||
{
|
{
|
||||||
get { return (Brush)GetValue(VisibleDateBackgroundProperty); }
|
var border = WinoVisualTreeHelper.GetChildObject<Border>(calendarDayItem, PART_DayViewItemBorder);
|
||||||
set { SetValue(VisibleDateBackgroundProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (border == null) return;
|
||||||
|
|
||||||
|
if (calendarDayItem.Date.Date == DateTime.Today.Date)
|
||||||
private CalendarView CalendarView;
|
|
||||||
|
|
||||||
public WinoCalendarView()
|
|
||||||
{
|
|
||||||
DefaultStyleKey = typeof(WinoCalendarView);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate()
|
|
||||||
{
|
|
||||||
base.OnApplyTemplate();
|
|
||||||
|
|
||||||
CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView;
|
|
||||||
|
|
||||||
Guard.IsNotNull(CalendarView, nameof(CalendarView));
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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;
|
border.Background = new SolidColorBrush(TodayBackgroundColor);
|
||||||
SetInnerDisplayDate(clickedDate);
|
|
||||||
|
|
||||||
var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate);
|
|
||||||
DateClickedCommand?.Execute(clickArgs);
|
|
||||||
}
|
}
|
||||||
|
else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date)
|
||||||
// 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();
|
border.Background = VisibleDateBackground;
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate);
|
border.Background = null;
|
||||||
control.UpdateVisibleDateRangeBackgrounds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateVisibleDateRangeBackgrounds()
|
|
||||||
{
|
|
||||||
if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundColor == 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,269 +10,268 @@ using Windows.UI.Xaml.Media;
|
|||||||
using Wino.Calendar.Args;
|
using Wino.Calendar.Args;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Controls
|
namespace Wino.Calendar.Controls;
|
||||||
|
|
||||||
|
public partial class WinoDayTimelineCanvas : Control, IDisposable
|
||||||
{
|
{
|
||||||
public class WinoDayTimelineCanvas : Control, IDisposable
|
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||||
|
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||||
|
|
||||||
|
private const string PART_InternalCanvas = nameof(PART_InternalCanvas);
|
||||||
|
private CanvasControl 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
|
||||||
{
|
{
|
||||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
get { return (UIElement)GetValue(PositionerUIElementProperty); }
|
||||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
set { SetValue(PositionerUIElementProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
private const string PART_InternalCanvas = nameof(PART_InternalCanvas);
|
public CalendarRenderOptions RenderOptions
|
||||||
private CanvasControl Canvas;
|
{
|
||||||
|
get { return (CalendarRenderOptions)GetValue(RenderOptionsProperty); }
|
||||||
|
set { SetValue(RenderOptionsProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty RenderOptionsProperty = DependencyProperty.Register(nameof(RenderOptions), typeof(CalendarRenderOptions), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
public SolidColorBrush HalfHourSeperatorColor
|
||||||
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)));
|
get { return (SolidColorBrush)GetValue(HalfHourSeperatorColorProperty); }
|
||||||
public static readonly DependencyProperty SelectedCellBackgroundBrushProperty = DependencyProperty.Register(nameof(SelectedCellBackgroundBrush), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
set { SetValue(HalfHourSeperatorColorProperty, value); }
|
||||||
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
|
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 CanvasControl;
|
||||||
|
|
||||||
|
// TODO: These will leak. Dispose them properly when needed.
|
||||||
|
Canvas.Draw += OnCanvasDraw;
|
||||||
|
Canvas.PointerPressed += OnCanvasPointerPressed;
|
||||||
|
|
||||||
|
ForceDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinoDayTimelineCanvas control)
|
||||||
{
|
{
|
||||||
get { return (UIElement)GetValue(PositionerUIElementProperty); }
|
if (e.OldValue != null && e.NewValue == null)
|
||||||
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 CanvasControl;
|
|
||||||
|
|
||||||
// TODO: These will leak. Dispose them properly when needed.
|
|
||||||
Canvas.Draw += OnCanvasDraw;
|
|
||||||
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.RaiseCellUnselected();
|
|
||||||
}
|
|
||||||
|
|
||||||
control.ForceDraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RaiseCellUnselected()
|
|
||||||
{
|
|
||||||
TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCanvasPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (RenderOptions == 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.
|
|
||||||
|
|
||||||
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}");
|
control.ForceDraw();
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
&& Canvas.ReadyToDraw
|
|
||||||
&& WorkingHourCellBackgroundColor != null
|
|
||||||
&& SeperatorColor != null
|
|
||||||
&& HalfHourSeperatorColor != null
|
|
||||||
&& SelectedCellBackgroundBrush != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCanvasDraw(CanvasControl sender, CanvasDrawEventArgs args)
|
|
||||||
{
|
|
||||||
if (!CanDrawTimeline()) return;
|
|
||||||
|
|
||||||
int hours = 24;
|
|
||||||
|
|
||||||
double canvasWidth = Canvas.ActualWidth;
|
|
||||||
double canvasHeight = Canvas.ActualHeight;
|
|
||||||
|
|
||||||
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 = SeperatorColor.Color;
|
|
||||||
float strokeThickness = 0.5f;
|
|
||||||
|
|
||||||
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
|
|
||||||
double x = day * rectWidth;
|
|
||||||
double y = hour * rectHeight;
|
|
||||||
|
|
||||||
var rectangle = new Rect(x, y, rectWidth, rectHeight);
|
|
||||||
|
|
||||||
// Draw the rectangle border.
|
|
||||||
// This is the main rectangle.
|
|
||||||
args.DrawingSession.DrawRectangle(rectangle, strokeColor, strokeThickness);
|
|
||||||
|
|
||||||
// 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 Rect(x + 1, y + 1, rectWidth - 1, rectHeight - 1);
|
|
||||||
|
|
||||||
args.DrawingSession.DrawRectangle(backgroundRectangle, strokeColor, strokeThickness);
|
|
||||||
args.DrawingSession.FillRectangle(backgroundRectangle, WorkingHourCellBackgroundColor.Color);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a line in the center of the rectangle for representing half hours.
|
|
||||||
double lineY = y + rectHeight / 2;
|
|
||||||
|
|
||||||
args.DrawingSession.DrawLine((float)x, (float)lineY, (float)(x + rectWidth), (float)lineY, HalfHourSeperatorColor.Color, strokeThickness, new CanvasStrokeStyle()
|
|
||||||
{
|
|
||||||
DashStyle = CanvasDashStyle.Dot
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 Rect(day * rectWidth, selectedY, rectWidth, selectionRectHeight);
|
|
||||||
args.DrawingSession.FillRectangle(selectedRectangle, SelectedCellBackgroundBrush.Color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (Canvas == null) return;
|
|
||||||
|
|
||||||
Canvas.Draw -= OnCanvasDraw;
|
|
||||||
Canvas.PointerPressed -= OnCanvasPointerPressed;
|
|
||||||
Canvas.RemoveFromVisualTree();
|
|
||||||
|
|
||||||
Canvas = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RaiseCellUnselected()
|
||||||
|
{
|
||||||
|
TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCanvasPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (RenderOptions == 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
&& Canvas.ReadyToDraw
|
||||||
|
&& WorkingHourCellBackgroundColor != null
|
||||||
|
&& SeperatorColor != null
|
||||||
|
&& HalfHourSeperatorColor != null
|
||||||
|
&& SelectedCellBackgroundBrush != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCanvasDraw(CanvasControl sender, CanvasDrawEventArgs args)
|
||||||
|
{
|
||||||
|
if (!CanDrawTimeline()) return;
|
||||||
|
|
||||||
|
int hours = 24;
|
||||||
|
|
||||||
|
double canvasWidth = Canvas.ActualWidth;
|
||||||
|
double canvasHeight = Canvas.ActualHeight;
|
||||||
|
|
||||||
|
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 = SeperatorColor.Color;
|
||||||
|
float strokeThickness = 0.5f;
|
||||||
|
|
||||||
|
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
|
||||||
|
double x = day * rectWidth;
|
||||||
|
double y = hour * rectHeight;
|
||||||
|
|
||||||
|
var rectangle = new Rect(x, y, rectWidth, rectHeight);
|
||||||
|
|
||||||
|
// Draw the rectangle border.
|
||||||
|
// This is the main rectangle.
|
||||||
|
args.DrawingSession.DrawRectangle(rectangle, strokeColor, strokeThickness);
|
||||||
|
|
||||||
|
// 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 Rect(x + 1, y + 1, rectWidth - 1, rectHeight - 1);
|
||||||
|
|
||||||
|
args.DrawingSession.DrawRectangle(backgroundRectangle, strokeColor, strokeThickness);
|
||||||
|
args.DrawingSession.FillRectangle(backgroundRectangle, WorkingHourCellBackgroundColor.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a line in the center of the rectangle for representing half hours.
|
||||||
|
double lineY = y + rectHeight / 2;
|
||||||
|
|
||||||
|
args.DrawingSession.DrawLine((float)x, (float)lineY, (float)(x + rectWidth), (float)lineY, HalfHourSeperatorColor.Color, strokeThickness, new CanvasStrokeStyle()
|
||||||
|
{
|
||||||
|
DashStyle = CanvasDashStyle.Dot
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Rect(day * rectWidth, selectedY, rectWidth, selectionRectHeight);
|
||||||
|
args.DrawingSession.FillRectangle(selectedRectangle, SelectedCellBackgroundBrush.Color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Canvas == null) return;
|
||||||
|
|
||||||
|
Canvas.Draw -= OnCanvasDraw;
|
||||||
|
Canvas.PointerPressed -= OnCanvasPointerPressed;
|
||||||
|
Canvas.RemoveFromVisualTree();
|
||||||
|
|
||||||
|
Canvas = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,98 +10,97 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Helpers;
|
using Wino.Helpers;
|
||||||
|
|
||||||
namespace Wino.Calendar.Helpers
|
namespace Wino.Calendar.Helpers;
|
||||||
|
|
||||||
|
public static class CalendarXamlHelpers
|
||||||
{
|
{
|
||||||
public static class CalendarXamlHelpers
|
public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection)
|
||||||
|
=> (CalendarItemViewModel)collection.AllDayEvents.FirstOrDefault();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns full date + duration info in Event Details page details title.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||||
{
|
{
|
||||||
public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection)
|
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||||
=> (CalendarItemViewModel)collection.AllDayEvents.FirstOrDefault();
|
|
||||||
|
|
||||||
/// <summary>
|
var start = calendarItemViewModel.Period.Start;
|
||||||
/// Returns full date + duration info in Event Details page details title.
|
var end = calendarItemViewModel.Period.End;
|
||||||
/// </summary>
|
|
||||||
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
string timeFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "h:mm tt" : "HH:mm";
|
||||||
|
string dateFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "dddd, dd MMMM h:mm tt" : "dddd, dd MMMM HH:mm";
|
||||||
|
|
||||||
|
if (calendarItemViewModel.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
||||||
|
|
||||||
var start = calendarItemViewModel.Period.Start;
|
|
||||||
var end = calendarItemViewModel.Period.End;
|
|
||||||
|
|
||||||
string timeFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "h:mm tt" : "HH:mm";
|
|
||||||
string dateFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "dddd, dd MMMM h:mm tt" : "dddd, dd MMMM HH:mm";
|
|
||||||
|
|
||||||
if (calendarItemViewModel.IsMultiDayEvent)
|
|
||||||
{
|
|
||||||
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
|
||||||
{
|
{
|
||||||
if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringChild) return string.Empty;
|
return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}";
|
||||||
|
|
||||||
// Parse recurrence rules
|
|
||||||
var calendarEvent = new CalendarEvent
|
|
||||||
{
|
|
||||||
Start = new CalDateTime(calendarItemViewModel.StartDate),
|
|
||||||
End = new CalDateTime(calendarItemViewModel.EndDate),
|
|
||||||
};
|
|
||||||
|
|
||||||
var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator);
|
|
||||||
|
|
||||||
foreach (var line in recurrenceLines)
|
|
||||||
{
|
|
||||||
calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (calendarEvent.RecurrenceRules == null || !calendarEvent.RecurrenceRules.Any())
|
|
||||||
{
|
|
||||||
return "No recurrence pattern.";
|
|
||||||
}
|
|
||||||
|
|
||||||
var recurrenceRule = calendarEvent.RecurrenceRules.First();
|
|
||||||
var daysOfWeek = string.Join(", ", recurrenceRule.ByDay.Select(day => day.DayOfWeek.ToString()));
|
|
||||||
string timeZone = calendarEvent.DtStart.TzId ?? "UTC";
|
|
||||||
|
|
||||||
return $"Every {daysOfWeek}, effective {calendarEvent.DtStart.Value.ToShortDateString()} " +
|
|
||||||
$"from {calendarEvent.DtStart.Value.ToShortTimeString()} to {calendarEvent.DtEnd.Value.ToShortTimeString()} " +
|
|
||||||
$"{timeZone}.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
|
||||||
{
|
|
||||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
|
||||||
|
|
||||||
// Single event in a day.
|
|
||||||
if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent)
|
|
||||||
{
|
|
||||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}";
|
|
||||||
}
|
|
||||||
else if (calendarItemViewModel.IsMultiDayEvent)
|
|
||||||
{
|
|
||||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// All day event.
|
|
||||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
|
||||||
CalendarItemViewModel calendarItemViewModel,
|
|
||||||
CalendarDisplayType calendarDisplayType)
|
|
||||||
{
|
|
||||||
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
|
||||||
|
|
||||||
// All and/or multi day events always go to the top of the screen.
|
|
||||||
if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom;
|
|
||||||
|
|
||||||
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
||||||
|
{
|
||||||
|
if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringChild) return string.Empty;
|
||||||
|
|
||||||
|
// Parse recurrence rules
|
||||||
|
var calendarEvent = new CalendarEvent
|
||||||
|
{
|
||||||
|
Start = new CalDateTime(calendarItemViewModel.StartDate),
|
||||||
|
End = new CalDateTime(calendarItemViewModel.EndDate),
|
||||||
|
};
|
||||||
|
|
||||||
|
var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator);
|
||||||
|
|
||||||
|
foreach (var line in recurrenceLines)
|
||||||
|
{
|
||||||
|
calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendarEvent.RecurrenceRules == null || !calendarEvent.RecurrenceRules.Any())
|
||||||
|
{
|
||||||
|
return "No recurrence pattern.";
|
||||||
|
}
|
||||||
|
|
||||||
|
var recurrenceRule = calendarEvent.RecurrenceRules.First();
|
||||||
|
var daysOfWeek = string.Join(", ", recurrenceRule.ByDay.Select(day => day.DayOfWeek.ToString()));
|
||||||
|
string timeZone = calendarEvent.DtStart.TzId ?? "UTC";
|
||||||
|
|
||||||
|
return $"Every {daysOfWeek}, effective {calendarEvent.DtStart.Value.ToShortDateString()} " +
|
||||||
|
$"from {calendarEvent.DtStart.Value.ToShortTimeString()} to {calendarEvent.DtEnd.Value.ToShortTimeString()} " +
|
||||||
|
$"{timeZone}.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||||
|
{
|
||||||
|
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||||
|
|
||||||
|
// Single event in a day.
|
||||||
|
if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent)
|
||||||
|
{
|
||||||
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}";
|
||||||
|
}
|
||||||
|
else if (calendarItemViewModel.IsMultiDayEvent)
|
||||||
|
{
|
||||||
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// All day event.
|
||||||
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
||||||
|
CalendarItemViewModel calendarItemViewModel,
|
||||||
|
CalendarDisplayType calendarDisplayType)
|
||||||
|
{
|
||||||
|
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
||||||
|
|
||||||
|
// All and/or multi day events always go to the top of the screen.
|
||||||
|
if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom;
|
||||||
|
|
||||||
|
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,15 @@ using Windows.UI.Xaml.Navigation;
|
|||||||
|
|
||||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
|
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
|
||||||
|
|
||||||
namespace Wino.Calendar
|
namespace Wino.Calendar;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class MainPage : Page
|
||||||
{
|
{
|
||||||
/// <summary>
|
public MainPage()
|
||||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class MainPage : Page
|
|
||||||
{
|
{
|
||||||
public MainPage()
|
this.InitializeComponent();
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
namespace Wino.Calendar.Models
|
namespace Wino.Calendar.Models;
|
||||||
|
|
||||||
|
public struct CalendarItemMeasurement
|
||||||
{
|
{
|
||||||
public struct CalendarItemMeasurement
|
// Where to start?
|
||||||
|
public double Left { get; set; }
|
||||||
|
|
||||||
|
// Extend until where?
|
||||||
|
public double Right { get; set; }
|
||||||
|
|
||||||
|
public CalendarItemMeasurement(double left, double right)
|
||||||
{
|
{
|
||||||
// Where to start?
|
Left = left;
|
||||||
public double Left { get; set; }
|
Right = right;
|
||||||
|
|
||||||
// Extend until where?
|
|
||||||
public double Right { get; set; }
|
|
||||||
|
|
||||||
public CalendarItemMeasurement(double left, double right)
|
|
||||||
{
|
|
||||||
Left = left;
|
|
||||||
Right = right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,20 @@
|
|||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Calendar.ViewModels.Data;
|
using Wino.Calendar.ViewModels.Data;
|
||||||
|
|
||||||
namespace Wino.Calendar.Selectors
|
namespace Wino.Calendar.Selectors;
|
||||||
|
|
||||||
|
public partial class CustomAreaCalendarItemSelector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
public class CustomAreaCalendarItemSelector : DataTemplateSelector
|
public DataTemplate AllDayTemplate { get; set; }
|
||||||
|
public DataTemplate MultiDayTemplate { get; set; }
|
||||||
|
|
||||||
|
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||||
{
|
{
|
||||||
public DataTemplate AllDayTemplate { get; set; }
|
if (item is CalendarItemViewModel calendarItemViewModel)
|
||||||
public DataTemplate MultiDayTemplate { get; set; }
|
|
||||||
|
|
||||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
|
||||||
{
|
{
|
||||||
if (item is CalendarItemViewModel calendarItemViewModel)
|
return calendarItemViewModel.IsMultiDayEvent ? MultiDayTemplate : AllDayTemplate;
|
||||||
{
|
|
||||||
return calendarItemViewModel.IsMultiDayEvent ? MultiDayTemplate : AllDayTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.SelectTemplateCore(item, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return base.SelectTemplateCore(item, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,33 +2,32 @@
|
|||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
namespace Wino.Calendar.Selectors
|
namespace Wino.Calendar.Selectors;
|
||||||
|
|
||||||
|
public partial class WinoCalendarItemTemplateSelector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
public class WinoCalendarItemTemplateSelector : DataTemplateSelector
|
public CalendarDisplayType DisplayType { get; set; }
|
||||||
|
|
||||||
|
public DataTemplate DayWeekWorkWeekTemplate { get; set; }
|
||||||
|
public DataTemplate MonthlyTemplate { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||||
{
|
{
|
||||||
public CalendarDisplayType DisplayType { get; set; }
|
switch (DisplayType)
|
||||||
|
|
||||||
public DataTemplate DayWeekWorkWeekTemplate { get; set; }
|
|
||||||
public DataTemplate MonthlyTemplate { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
|
||||||
{
|
{
|
||||||
switch (DisplayType)
|
case CalendarDisplayType.Day:
|
||||||
{
|
case CalendarDisplayType.Week:
|
||||||
case CalendarDisplayType.Day:
|
case CalendarDisplayType.WorkWeek:
|
||||||
case CalendarDisplayType.Week:
|
return DayWeekWorkWeekTemplate;
|
||||||
case CalendarDisplayType.WorkWeek:
|
case CalendarDisplayType.Month:
|
||||||
return DayWeekWorkWeekTemplate;
|
return MonthlyTemplate;
|
||||||
case CalendarDisplayType.Month:
|
case CalendarDisplayType.Year:
|
||||||
return MonthlyTemplate;
|
break;
|
||||||
case CalendarDisplayType.Year:
|
default:
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.SelectTemplateCore(item, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return base.SelectTemplateCore(item, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,108 +7,107 @@ using Wino.Calendar.ViewModels.Data;
|
|||||||
using Wino.Calendar.ViewModels.Interfaces;
|
using Wino.Calendar.ViewModels.Interfaces;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
|
||||||
namespace Wino.Calendar.Services
|
namespace Wino.Calendar.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulated state manager for collectively managing the state of account calendars.
|
||||||
|
/// Callers must react to the events to update their state only from this service.
|
||||||
|
/// </summary>
|
||||||
|
public partial class AccountCalendarStateService : ObservableObject, IAccountCalendarStateService
|
||||||
{
|
{
|
||||||
/// <summary>
|
public event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
||||||
/// Encapsulated state manager for collectively managing the state of account calendars.
|
public event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
||||||
/// Callers must react to the events to update their state only from this service.
|
|
||||||
/// </summary>
|
[ObservableProperty]
|
||||||
public partial class AccountCalendarStateService : ObservableObject, IAccountCalendarStateService
|
public partial ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; set; }
|
||||||
|
|
||||||
|
private ObservableCollection<GroupedAccountCalendarViewModel> _internalGroupedAccountCalendars = new ObservableCollection<GroupedAccountCalendarViewModel>();
|
||||||
|
|
||||||
|
public IEnumerable<AccountCalendarViewModel> ActiveCalendars
|
||||||
{
|
{
|
||||||
public event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
get
|
||||||
public event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> groupedAccountCalendars;
|
|
||||||
|
|
||||||
private ObservableCollection<GroupedAccountCalendarViewModel> _internalGroupedAccountCalendars = new ObservableCollection<GroupedAccountCalendarViewModel>();
|
|
||||||
|
|
||||||
public IEnumerable<AccountCalendarViewModel> ActiveCalendars
|
|
||||||
{
|
{
|
||||||
get
|
return GroupedAccountCalendars
|
||||||
{
|
.SelectMany(a => a.AccountCalendars)
|
||||||
return GroupedAccountCalendars
|
.Where(b => b.IsChecked);
|
||||||
.SelectMany(a => a.AccountCalendars)
|
|
||||||
.Where(b => b.IsChecked);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable
|
public IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
get
|
return GroupedAccountCalendars
|
||||||
{
|
.Select(a => a.AccountCalendars)
|
||||||
return GroupedAccountCalendars
|
.SelectMany(b => b)
|
||||||
.Select(a => a.AccountCalendars)
|
.GroupBy(c => c.Account);
|
||||||
.SelectMany(b => b)
|
|
||||||
.GroupBy(c => c.Account);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public AccountCalendarStateService()
|
public AccountCalendarStateService()
|
||||||
|
{
|
||||||
|
GroupedAccountCalendars = new ReadOnlyObservableCollection<GroupedAccountCalendarViewModel>(_internalGroupedAccountCalendars);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e)
|
||||||
|
=> CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel);
|
||||||
|
|
||||||
|
private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e)
|
||||||
|
=> AccountCalendarSelectionStateChanged?.Invoke(this, e);
|
||||||
|
|
||||||
|
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||||
|
{
|
||||||
|
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
||||||
|
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
||||||
|
|
||||||
|
_internalGroupedAccountCalendars.Add(groupedAccountCalendar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||||
|
{
|
||||||
|
groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged;
|
||||||
|
groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged;
|
||||||
|
|
||||||
|
_internalGroupedAccountCalendars.Remove(groupedAccountCalendar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearGroupedAccountCalendar()
|
||||||
|
{
|
||||||
|
foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars)
|
||||||
{
|
{
|
||||||
GroupedAccountCalendars = new ReadOnlyObservableCollection<GroupedAccountCalendarViewModel>(_internalGroupedAccountCalendars);
|
RemoveGroupedAccountCalendar(groupedAccountCalendar);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e)
|
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar)
|
||||||
=> CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel);
|
{
|
||||||
|
// Find the group that this calendar belongs to.
|
||||||
|
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
||||||
|
|
||||||
private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e)
|
if (group == null)
|
||||||
=> AccountCalendarSelectionStateChanged?.Invoke(this, e);
|
|
||||||
|
|
||||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
|
||||||
{
|
{
|
||||||
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
// If the group doesn't exist, create it.
|
||||||
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar });
|
||||||
|
AddGroupedAccountCalendar(group);
|
||||||
_internalGroupedAccountCalendars.Add(groupedAccountCalendar);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
|
||||||
{
|
{
|
||||||
groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged;
|
group.AccountCalendars.Add(accountCalendar);
|
||||||
groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged;
|
|
||||||
|
|
||||||
_internalGroupedAccountCalendars.Remove(groupedAccountCalendar);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ClearGroupedAccountCalendar()
|
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar)
|
||||||
|
{
|
||||||
|
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
||||||
|
|
||||||
|
// We don't expect but just in case.
|
||||||
|
if (group == null) return;
|
||||||
|
|
||||||
|
group.AccountCalendars.Remove(accountCalendar);
|
||||||
|
|
||||||
|
if (group.AccountCalendars.Count == 0)
|
||||||
{
|
{
|
||||||
foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars)
|
RemoveGroupedAccountCalendar(group);
|
||||||
{
|
|
||||||
RemoveGroupedAccountCalendar(groupedAccountCalendar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar)
|
|
||||||
{
|
|
||||||
// Find the group that this calendar belongs to.
|
|
||||||
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
|
||||||
|
|
||||||
if (group == null)
|
|
||||||
{
|
|
||||||
// If the group doesn't exist, create it.
|
|
||||||
group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar });
|
|
||||||
AddGroupedAccountCalendar(group);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
group.AccountCalendars.Add(accountCalendar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar)
|
|
||||||
{
|
|
||||||
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
|
||||||
|
|
||||||
// We don't expect but just in case.
|
|
||||||
if (group == null) return;
|
|
||||||
|
|
||||||
group.AccountCalendars.Remove(accountCalendar);
|
|
||||||
|
|
||||||
if (group.AccountCalendars.Count == 0)
|
|
||||||
{
|
|
||||||
RemoveGroupedAccountCalendar(group);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.UWP.Services;
|
using Wino.Core.UWP.Services;
|
||||||
|
|
||||||
namespace Wino.Calendar.Services
|
namespace Wino.Calendar.Services;
|
||||||
|
|
||||||
|
public class DialogService : DialogServiceBase, ICalendarDialogService
|
||||||
{
|
{
|
||||||
public class DialogService : DialogServiceBase, ICalendarDialogService
|
public DialogService(IThemeService themeService,
|
||||||
|
IConfigurationService configurationService,
|
||||||
|
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||||
{
|
{
|
||||||
public DialogService(IThemeService themeService,
|
|
||||||
IConfigurationService configurationService,
|
|
||||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,54 +10,53 @@ using Wino.Core.Domain.Models.Navigation;
|
|||||||
using Wino.Core.UWP.Services;
|
using Wino.Core.UWP.Services;
|
||||||
using Wino.Views;
|
using Wino.Views;
|
||||||
|
|
||||||
namespace Wino.Calendar.Services
|
namespace Wino.Calendar.Services;
|
||||||
|
|
||||||
|
public class NavigationService : NavigationServiceBase, INavigationService
|
||||||
{
|
{
|
||||||
public class NavigationService : NavigationServiceBase, INavigationService
|
public Type GetPageType(WinoPage winoPage)
|
||||||
{
|
{
|
||||||
public Type GetPageType(WinoPage winoPage)
|
return winoPage switch
|
||||||
{
|
{
|
||||||
return winoPage switch
|
WinoPage.CalendarPage => typeof(CalendarPage),
|
||||||
{
|
WinoPage.SettingsPage => typeof(SettingsPage),
|
||||||
WinoPage.CalendarPage => typeof(CalendarPage),
|
WinoPage.CalendarSettingsPage => typeof(CalendarSettingsPage),
|
||||||
WinoPage.SettingsPage => typeof(SettingsPage),
|
WinoPage.AccountManagementPage => typeof(AccountManagementPage),
|
||||||
WinoPage.CalendarSettingsPage => typeof(CalendarSettingsPage),
|
WinoPage.ManageAccountsPage => typeof(ManageAccountsPage),
|
||||||
WinoPage.AccountManagementPage => typeof(AccountManagementPage),
|
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
|
||||||
WinoPage.ManageAccountsPage => typeof(ManageAccountsPage),
|
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
|
||||||
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
|
WinoPage.EventDetailsPage => typeof(EventDetailsPage),
|
||||||
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
|
_ => throw new Exception("Page is not implemented yet."),
|
||||||
WinoPage.EventDetailsPage => typeof(EventDetailsPage),
|
};
|
||||||
_ => throw new Exception("Page is not implemented yet."),
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GoBack()
|
public void GoBack()
|
||||||
|
{
|
||||||
|
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
||||||
{
|
{
|
||||||
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
var shellFrame = shellPage.GetShellFrame();
|
||||||
{
|
|
||||||
var shellFrame = shellPage.GetShellFrame();
|
|
||||||
|
|
||||||
if (shellFrame.CanGoBack)
|
if (shellFrame.CanGoBack)
|
||||||
{
|
{
|
||||||
shellFrame.GoBack();
|
shellFrame.GoBack();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public bool Navigate(WinoPage page, object parameter = null, NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame, NavigationTransitionType transition = NavigationTransitionType.None)
|
|
||||||
{
|
public bool Navigate(WinoPage page, object parameter = null, NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame, NavigationTransitionType transition = NavigationTransitionType.None)
|
||||||
// All navigations are performed on shell frame for calendar.
|
{
|
||||||
|
// All navigations are performed on shell frame for calendar.
|
||||||
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
|
||||||
{
|
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
||||||
var shellFrame = shellPage.GetShellFrame();
|
{
|
||||||
|
var shellFrame = shellPage.GetShellFrame();
|
||||||
var pageType = GetPageType(page);
|
|
||||||
|
var pageType = GetPageType(page);
|
||||||
shellFrame.Navigate(pageType, parameter);
|
|
||||||
return true;
|
shellFrame.Navigate(pageType, parameter);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
return false;
|
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,33 +4,32 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Accounts;
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
|
|
||||||
namespace Wino.Calendar.Services
|
namespace Wino.Calendar.Services;
|
||||||
|
|
||||||
|
public class ProviderService : IProviderService
|
||||||
{
|
{
|
||||||
public class ProviderService : IProviderService
|
public IProviderDetail GetProviderDetail(MailProviderType type)
|
||||||
{
|
{
|
||||||
public IProviderDetail GetProviderDetail(MailProviderType type)
|
var details = GetAvailableProviders();
|
||||||
{
|
|
||||||
var details = GetAvailableProviders();
|
|
||||||
|
|
||||||
return details.FirstOrDefault(a => a.Type == type);
|
return details.FirstOrDefault(a => a.Type == type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IProviderDetail> GetAvailableProviders()
|
||||||
|
{
|
||||||
|
var providerList = new List<IProviderDetail>();
|
||||||
|
|
||||||
|
var providers = new MailProviderType[]
|
||||||
|
{
|
||||||
|
MailProviderType.Outlook,
|
||||||
|
MailProviderType.Gmail
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var type in providers)
|
||||||
|
{
|
||||||
|
providerList.Add(new ProviderDetail(type, SpecialImapProvider.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IProviderDetail> GetAvailableProviders()
|
return providerList;
|
||||||
{
|
|
||||||
var providerList = new List<IProviderDetail>();
|
|
||||||
|
|
||||||
var providers = new MailProviderType[]
|
|
||||||
{
|
|
||||||
MailProviderType.Outlook,
|
|
||||||
MailProviderType.Gmail
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var type in providers)
|
|
||||||
{
|
|
||||||
providerList.Add(new ProviderDetail(type, SpecialImapProvider.None));
|
|
||||||
}
|
|
||||||
|
|
||||||
return providerList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,11 @@
|
|||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
|
|
||||||
namespace Wino.Calendar.Styles
|
namespace Wino.Calendar.Styles;
|
||||||
|
|
||||||
|
public sealed partial class WinoCalendarResources : ResourceDictionary
|
||||||
{
|
{
|
||||||
public sealed partial class WinoCalendarResources : ResourceDictionary
|
public WinoCalendarResources()
|
||||||
{
|
{
|
||||||
public WinoCalendarResources()
|
this.InitializeComponent();
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Abstract
|
namespace Wino.Calendar.Views.Abstract;
|
||||||
{
|
|
||||||
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel> { }
|
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel> { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Abstract
|
namespace Wino.Calendar.Views.Abstract;
|
||||||
{
|
|
||||||
public class AccountManagementPageAbstract : BasePage<AccountManagementViewModel> { }
|
public partial class AccountManagementPageAbstract : BasePage<AccountManagementViewModel> { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Abstract
|
namespace Wino.Calendar.Views.Abstract;
|
||||||
{
|
|
||||||
public abstract class AppShellAbstract : BasePage<AppShellViewModel> { }
|
public abstract class AppShellAbstract : BasePage<AppShellViewModel> { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Abstract
|
namespace Wino.Calendar.Views.Abstract;
|
||||||
{
|
|
||||||
public abstract class CalendarPageAbstract : BasePage<CalendarPageViewModel> { }
|
public abstract class CalendarPageAbstract : BasePage<CalendarPageViewModel> { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Abstract
|
namespace Wino.Calendar.Views.Abstract;
|
||||||
{
|
|
||||||
public abstract class CalendarSettingsPageAbstract : BasePage<CalendarSettingsPageViewModel> { }
|
public abstract class CalendarSettingsPageAbstract : BasePage<CalendarSettingsPageViewModel> { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Abstract
|
namespace Wino.Calendar.Views.Abstract;
|
||||||
{
|
|
||||||
public abstract class EventDetailsPageAbstract : BasePage<EventDetailsPageViewModel> { }
|
public abstract class EventDetailsPageAbstract : BasePage<EventDetailsPageViewModel> { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
using Wino.Core.ViewModels;
|
using Wino.Core.ViewModels;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Abstract
|
namespace Wino.Calendar.Views.Abstract;
|
||||||
{
|
|
||||||
public class PersonalizationPageAbstract : BasePage<PersonalizationPageViewModel> { }
|
public partial class PersonalizationPageAbstract : BasePage<PersonalizationPageViewModel> { }
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using Wino.Calendar.Views.Abstract;
|
using Wino.Calendar.Views.Abstract;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Account
|
namespace Wino.Calendar.Views.Account;
|
||||||
|
|
||||||
|
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
|
||||||
{
|
{
|
||||||
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
|
public AccountManagementPage()
|
||||||
{
|
{
|
||||||
public AccountManagementPage()
|
InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,50 +5,49 @@ using Wino.Calendar.Views.Abstract;
|
|||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
using Wino.Messaging.Client.Calendar;
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views
|
namespace Wino.Calendar.Views;
|
||||||
|
|
||||||
|
public sealed partial class AppShell : AppShellAbstract,
|
||||||
|
IRecipient<CalendarDisplayTypeChangedMessage>
|
||||||
{
|
{
|
||||||
public sealed partial class AppShell : AppShellAbstract,
|
private const string STATE_HorizontalCalendar = "HorizontalCalendar";
|
||||||
IRecipient<CalendarDisplayTypeChangedMessage>
|
private const string STATE_VerticalCalendar = "VerticalCalendar";
|
||||||
|
|
||||||
|
public Frame GetShellFrame() => ShellFrame;
|
||||||
|
|
||||||
|
public AppShell()
|
||||||
{
|
{
|
||||||
private const string STATE_HorizontalCalendar = "HorizontalCalendar";
|
InitializeComponent();
|
||||||
private const string STATE_VerticalCalendar = "VerticalCalendar";
|
|
||||||
|
|
||||||
public Frame GetShellFrame() => ShellFrame;
|
Window.Current.SetTitleBar(DragArea);
|
||||||
|
ManageCalendarDisplayType();
|
||||||
public AppShell()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
Window.Current.SetTitleBar(DragArea);
|
|
||||||
ManageCalendarDisplayType();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ManageCalendarDisplayType()
|
|
||||||
{
|
|
||||||
// Go to different states based on the display type.
|
|
||||||
if (ViewModel.IsVerticalCalendar)
|
|
||||||
{
|
|
||||||
VisualStateManager.GoToState(this, STATE_VerticalCalendar, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
|
|
||||||
|
|
||||||
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
|
|
||||||
|
|
||||||
public void Receive(CalendarDisplayTypeChangedMessage message)
|
|
||||||
{
|
|
||||||
ManageCalendarDisplayType();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
|
|
||||||
=> RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
|
|
||||||
|
|
||||||
private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args)
|
|
||||||
=> ViewModel.NavigationService.GoBack();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ManageCalendarDisplayType()
|
||||||
|
{
|
||||||
|
// Go to different states based on the display type.
|
||||||
|
if (ViewModel.IsVerticalCalendar)
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, STATE_VerticalCalendar, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
|
||||||
|
|
||||||
|
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
|
||||||
|
|
||||||
|
public void Receive(CalendarDisplayTypeChangedMessage message)
|
||||||
|
{
|
||||||
|
ManageCalendarDisplayType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||||
|
=> RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
|
||||||
|
|
||||||
|
private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args)
|
||||||
|
=> ViewModel.NavigationService.GoBack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,153 +9,152 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Messaging.Client.Calendar;
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views
|
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;
|
||||||
IRecipient<ScrollToDateMessage>,
|
|
||||||
IRecipient<ScrollToHourMessage>,
|
public CalendarPage()
|
||||||
IRecipient<GoNextDateRequestedMessage>,
|
|
||||||
IRecipient<GoPreviousDateRequestedMessage>
|
|
||||||
{
|
{
|
||||||
private const int PopupDialogOffset = 12;
|
InitializeComponent();
|
||||||
|
NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||||
|
|
||||||
public CalendarPage()
|
ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalendarItemDetailContextChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel.DisplayDetailsCalendarItemViewModel != null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel);
|
||||||
NavigationCacheMode = NavigationCacheMode.Enabled;
|
|
||||||
|
|
||||||
ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged;
|
if (control != null)
|
||||||
}
|
|
||||||
|
|
||||||
private void CalendarItemDetailContextChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (ViewModel.DisplayDetailsCalendarItemViewModel != null)
|
|
||||||
{
|
{
|
||||||
var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel);
|
EventDetailsPopup.PlacementTarget = control;
|
||||||
|
|
||||||
if (control != null)
|
|
||||||
{
|
|
||||||
EventDetailsPopup.PlacementTarget = control;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public void Receive(ScrollToHourMessage message) => CalendarControl.NavigateToHour(message.TimeSpan);
|
|
||||||
public void Receive(ScrollToDateMessage message) => CalendarControl.NavigateToDay(message.Date);
|
public void Receive(ScrollToHourMessage message) => CalendarControl.NavigateToHour(message.TimeSpan);
|
||||||
public void Receive(GoNextDateRequestedMessage message) => CalendarControl.GoNextRange();
|
public void Receive(ScrollToDateMessage message) => CalendarControl.NavigateToDay(message.Date);
|
||||||
public void Receive(GoPreviousDateRequestedMessage message) => CalendarControl.GoPreviousRange();
|
public void Receive(GoNextDateRequestedMessage message) => CalendarControl.GoNextRange();
|
||||||
|
public void Receive(GoPreviousDateRequestedMessage message) => CalendarControl.GoPreviousRange();
|
||||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
|
||||||
{
|
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||||
base.OnNavigatedTo(e);
|
{
|
||||||
|
base.OnNavigatedTo(e);
|
||||||
if (e.NavigationMode == NavigationMode.Back) return;
|
|
||||||
|
if (e.NavigationMode == NavigationMode.Back) return;
|
||||||
if (e.Parameter is CalendarPageNavigationArgs args)
|
|
||||||
{
|
if (e.Parameter is CalendarPageNavigationArgs args)
|
||||||
if (args.RequestDefaultNavigation)
|
{
|
||||||
{
|
if (args.RequestDefaultNavigation)
|
||||||
// Go today.
|
{
|
||||||
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(DateTime.Now.Date, CalendarInitInitiative.App));
|
// Go today.
|
||||||
}
|
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(DateTime.Now.Date, CalendarInitInitiative.App));
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
// Go specified date.
|
{
|
||||||
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(args.NavigationDate, CalendarInitInitiative.User));
|
// Go specified date.
|
||||||
}
|
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(args.NavigationDate, CalendarInitInitiative.User));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void CellSelected(object sender, TimelineCellSelectedArgs e)
|
|
||||||
{
|
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.
|
// 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)
|
|
||||||
{
|
if (EventDetailsPopup.IsOpen)
|
||||||
CalendarControl.UnselectActiveTimelineCell();
|
{
|
||||||
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
CalendarControl.UnselectActiveTimelineCell();
|
||||||
|
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
||||||
return;
|
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
ViewModel.SelectedQuickEventDate = e.ClickedDate;
|
|
||||||
|
ViewModel.SelectedQuickEventDate = e.ClickedDate;
|
||||||
TeachingTipPositionerGrid.Width = e.CellSize.Width;
|
|
||||||
TeachingTipPositionerGrid.Height = e.CellSize.Height;
|
TeachingTipPositionerGrid.Width = e.CellSize.Width;
|
||||||
|
TeachingTipPositionerGrid.Height = e.CellSize.Height;
|
||||||
Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X);
|
|
||||||
Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y);
|
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;
|
// Adjust the start and end time in the flyout.
|
||||||
var endTime = startTime.Add(TimeSpan.FromMinutes(30));
|
var startTime = ViewModel.SelectedQuickEventDate.Value.TimeOfDay;
|
||||||
|
var endTime = startTime.Add(TimeSpan.FromMinutes(30));
|
||||||
ViewModel.SelectQuickEventTimeRange(startTime, endTime);
|
|
||||||
|
ViewModel.SelectQuickEventTimeRange(startTime, endTime);
|
||||||
QuickEventPopupDialog.IsOpen = true;
|
|
||||||
}
|
QuickEventPopupDialog.IsOpen = true;
|
||||||
|
}
|
||||||
private void CellUnselected(object sender, TimelineCellUnselectedArgs e)
|
|
||||||
{
|
private void CellUnselected(object sender, TimelineCellUnselectedArgs e)
|
||||||
QuickEventPopupDialog.IsOpen = false;
|
{
|
||||||
}
|
QuickEventPopupDialog.IsOpen = false;
|
||||||
|
}
|
||||||
private void QuickEventAccountSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
private void QuickEventAccountSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
QuickEventAccountSelectorFlyout.Hide();
|
{
|
||||||
}
|
QuickEventAccountSelectorFlyout.Hide();
|
||||||
|
}
|
||||||
private void QuickEventPopupClosed(object sender, object e)
|
|
||||||
{
|
private void QuickEventPopupClosed(object sender, object e)
|
||||||
// Reset the timeline selection when the tip is closed.
|
{
|
||||||
CalendarControl.ResetTimelineSelection();
|
// Reset the timeline selection when the tip is closed.
|
||||||
}
|
CalendarControl.ResetTimelineSelection();
|
||||||
|
}
|
||||||
private void PopupPlacementChanged(object sender, object e)
|
|
||||||
{
|
private void PopupPlacementChanged(object sender, object e)
|
||||||
if (sender is Popup senderPopup)
|
{
|
||||||
{
|
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
|
// When the quick event Popup is positioned for different calendar types,
|
||||||
// spacing from the cell.
|
// we must adjust the offset to make sure the tip is not hidden and has nice
|
||||||
|
// spacing from the cell.
|
||||||
switch (senderPopup.ActualPlacement)
|
|
||||||
{
|
switch (senderPopup.ActualPlacement)
|
||||||
case PopupPlacementMode.Top:
|
{
|
||||||
senderPopup.VerticalOffset = PopupDialogOffset * -1;
|
case PopupPlacementMode.Top:
|
||||||
break;
|
senderPopup.VerticalOffset = PopupDialogOffset * -1;
|
||||||
case PopupPlacementMode.Bottom:
|
break;
|
||||||
senderPopup.VerticalOffset = PopupDialogOffset;
|
case PopupPlacementMode.Bottom:
|
||||||
break;
|
senderPopup.VerticalOffset = PopupDialogOffset;
|
||||||
case PopupPlacementMode.Left:
|
break;
|
||||||
senderPopup.HorizontalOffset = PopupDialogOffset * -1;
|
case PopupPlacementMode.Left:
|
||||||
break;
|
senderPopup.HorizontalOffset = PopupDialogOffset * -1;
|
||||||
case PopupPlacementMode.Right:
|
break;
|
||||||
senderPopup.HorizontalOffset = PopupDialogOffset;
|
case PopupPlacementMode.Right:
|
||||||
break;
|
senderPopup.HorizontalOffset = PopupDialogOffset;
|
||||||
default:
|
break;
|
||||||
break;
|
default:
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
private void StartTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
|
|
||||||
=> ViewModel.SelectedStartTimeString = args.Text;
|
private void StartTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
|
||||||
|
=> ViewModel.SelectedStartTimeString = args.Text;
|
||||||
private void EndTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
|
|
||||||
=> ViewModel.SelectedEndTimeString = args.Text;
|
private void EndTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
|
||||||
|
=> ViewModel.SelectedEndTimeString = args.Text;
|
||||||
private void EventDetailsPopupClosed(object sender, object e)
|
|
||||||
{
|
private void EventDetailsPopupClosed(object sender, object e)
|
||||||
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
{
|
||||||
}
|
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
||||||
|
}
|
||||||
private void CalendarScrolling(object sender, EventArgs e)
|
|
||||||
{
|
private void CalendarScrolling(object sender, EventArgs e)
|
||||||
// In case of scrolling, we must dismiss the event details dialog.
|
{
|
||||||
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
// In case of scrolling, we must dismiss the event details dialog.
|
||||||
}
|
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
|
|
||||||
using Wino.Calendar.Views.Abstract;
|
using Wino.Calendar.Views.Abstract;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views
|
namespace Wino.Calendar.Views;
|
||||||
|
|
||||||
|
public sealed partial class EventDetailsPage : EventDetailsPageAbstract
|
||||||
{
|
{
|
||||||
public sealed partial class EventDetailsPage : EventDetailsPageAbstract
|
public EventDetailsPage()
|
||||||
{
|
{
|
||||||
public EventDetailsPage()
|
this.InitializeComponent();
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using Wino.Calendar.Views.Abstract;
|
using Wino.Calendar.Views.Abstract;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Settings
|
namespace Wino.Calendar.Views.Settings;
|
||||||
|
|
||||||
|
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
|
||||||
{
|
{
|
||||||
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
|
public AccountDetailsPage()
|
||||||
{
|
{
|
||||||
public AccountDetailsPage()
|
this.InitializeComponent();
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using Wino.Calendar.Views.Abstract;
|
using Wino.Calendar.Views.Abstract;
|
||||||
|
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Settings
|
namespace Wino.Calendar.Views.Settings;
|
||||||
|
|
||||||
|
public sealed partial class CalendarSettingsPage : CalendarSettingsPageAbstract
|
||||||
{
|
{
|
||||||
public sealed partial class CalendarSettingsPage : CalendarSettingsPageAbstract
|
public CalendarSettingsPage()
|
||||||
{
|
{
|
||||||
public CalendarSettingsPage()
|
InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using Wino.Calendar.Views.Abstract;
|
using Wino.Calendar.Views.Abstract;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views.Settings
|
namespace Wino.Calendar.Views.Settings;
|
||||||
|
|
||||||
|
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
|
||||||
{
|
{
|
||||||
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
|
public PersonalizationPage()
|
||||||
{
|
{
|
||||||
public PersonalizationPage()
|
this.InitializeComponent();
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ namespace Wino.Core.Domain.Models;
|
|||||||
[JsonSerializable(typeof(CustomThemeMetadata))]
|
[JsonSerializable(typeof(CustomThemeMetadata))]
|
||||||
[JsonSerializable(typeof(WebViewMessage))]
|
[JsonSerializable(typeof(WebViewMessage))]
|
||||||
[JsonSerializable(typeof(List<ImageInfo>))]
|
[JsonSerializable(typeof(List<ImageInfo>))]
|
||||||
public partial class DomainModelsJsonContext: JsonSerializerContext;
|
public partial class DomainModelsJsonContext : JsonSerializerContext;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
@@ -53,6 +54,8 @@ public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
|
|||||||
Debug.WriteLine($"Disposed {GetType().Name}");
|
Debug.WriteLine($"Disposed {GetType().Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RequiresDynamicCode("AOT")]
|
||||||
|
[RequiresUnreferencedCode("AOT")]
|
||||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnNavigatedTo(e);
|
base.OnNavigatedTo(e);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -288,6 +289,8 @@ public class WinoServerConnectionManager :
|
|||||||
public Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType message, CancellationToken cancellationToken = default) where TRequestType : IClientMessage
|
public Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType message, CancellationToken cancellationToken = default) where TRequestType : IClientMessage
|
||||||
=> GetResponseInternalAsync<TResponse, TRequestType>(message, cancellationToken: cancellationToken);
|
=> GetResponseInternalAsync<TResponse, TRequestType>(message, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
[RequiresDynamicCode("Calls System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)")]
|
||||||
|
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)")]
|
||||||
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message,
|
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message,
|
||||||
Dictionary<string, object> parameters = null,
|
Dictionary<string, object> parameters = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -46,6 +47,8 @@ public partial class AboutPageViewModel : CoreBaseViewModel
|
|||||||
PreferencesService = preferencesService;
|
PreferencesService = preferencesService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RequiresDynamicCode("AOT")]
|
||||||
|
[RequiresUnreferencedCode("AOT")]
|
||||||
protected override void OnActivated()
|
protected override void OnActivated()
|
||||||
{
|
{
|
||||||
base.OnActivated();
|
base.OnActivated();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
@@ -200,6 +201,8 @@ public partial class PersonalizationPageViewModel : CoreBaseViewModel
|
|||||||
SelectedAppTheme = AppThemes.Find(a => a.Id == _themeService.CurrentApplicationThemeId);
|
SelectedAppTheme = AppThemes.Find(a => a.Id == _themeService.CurrentApplicationThemeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RequiresDynamicCode("AOT")]
|
||||||
|
[RequiresUnreferencedCode("AOT")]
|
||||||
protected override async void OnActivated()
|
protected override async void OnActivated()
|
||||||
{
|
{
|
||||||
base.OnActivated();
|
base.OnActivated();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -20,6 +21,8 @@ public record HttpRequestBundle<TRequest>(TRequest NativeRequest, IUIChangeReque
|
|||||||
/// <param name="BatchRequest">Batch request that is generated by base synchronizer.</param>
|
/// <param name="BatchRequest">Batch request that is generated by base synchronizer.</param>
|
||||||
public record HttpRequestBundle<TRequest, TResponse>(TRequest NativeRequest, IRequestBase Request) : HttpRequestBundle<TRequest>(NativeRequest, Request)
|
public record HttpRequestBundle<TRequest, TResponse>(TRequest NativeRequest, IRequestBase Request) : HttpRequestBundle<TRequest>(NativeRequest, Request)
|
||||||
{
|
{
|
||||||
|
[RequiresDynamicCode("AOT")]
|
||||||
|
[RequiresUnreferencedCode("AOT")]
|
||||||
public async Task<TResponse> DeserializeBundleAsync(HttpResponseMessage httpResponse, CancellationToken cancellationToken = default)
|
public async Task<TResponse> DeserializeBundleAsync(HttpResponseMessage httpResponse, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var content = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
var content = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -464,6 +465,7 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
await UpdateDeltaSynchronizationIdentifierAsync(iterator.Deltalink).ConfigureAwait(false);
|
await UpdateDeltaSynchronizationIdentifierAsync(iterator.Deltalink).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RequiresUnreferencedCode("Calls Microsoft.Kiota.Abstractions.Serialization.KiotaJsonSerializer.DeserializeAsync<T>(String, CancellationToken)")]
|
||||||
private async Task<T> DeserializeGraphBatchResponseAsync<T>(BatchResponseContentCollection collection, string requestId, CancellationToken cancellationToken = default) where T : IParsable, new()
|
private async Task<T> DeserializeGraphBatchResponseAsync<T>(BatchResponseContentCollection collection, string requestId, CancellationToken cancellationToken = default) where T : IParsable, new()
|
||||||
{
|
{
|
||||||
// This deserialization may throw generalException in case of failure.
|
// This deserialization may throw generalException in case of failure.
|
||||||
|
|||||||
Reference in New Issue
Block a user