37
Wino.Calendar.ViewModels/AccountManagementViewModel.cs
Normal file
37
Wino.Calendar.ViewModels/AccountManagementViewModel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Store;
|
||||
using Wino.Core.ViewModels;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
||||
{
|
||||
public AccountManagementViewModel(ICalendarDialogService dialogService,
|
||||
IWinoServerConnectionManager winoServerConnectionManager,
|
||||
INavigationService navigationService,
|
||||
IAccountService accountService,
|
||||
IProviderService providerService,
|
||||
IStoreManagementService storeManagementService,
|
||||
IAuthenticationProvider authenticationProvider,
|
||||
IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
|
||||
{
|
||||
CalendarDialogService = dialogService;
|
||||
}
|
||||
|
||||
public ICalendarDialogService CalendarDialogService { get; }
|
||||
|
||||
public override Task InitializeAccountsAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
var t = await StoreManagementService.HasProductAsync(StoreProductType.UnlimitedAccounts);
|
||||
}
|
||||
}
|
||||
}
|
||||
237
Wino.Calendar.ViewModels/AppShellViewModel.cs
Normal file
237
Wino.Calendar.ViewModels/AppShellViewModel.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Extensions;
|
||||
using Wino.Core.MenuItems;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||
IRecipient<VisibleDateRangeChangedMessage>,
|
||||
IRecipient<CalendarEnableStatusChangedMessage>,
|
||||
IRecipient<CalendarInitializedMessage>,
|
||||
IRecipient<NavigateManageAccountsRequested>
|
||||
{
|
||||
public event EventHandler<CalendarDisplayType> DisplayTypeChanged;
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
public IStatePersistanceService StatePersistenceService { get; }
|
||||
public INavigationService NavigationService { get; }
|
||||
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
||||
public MenuItemCollection FooterItems { get; set; }
|
||||
public MenuItemCollection MenuItems { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private IMenuItem _selectedMenuItem;
|
||||
|
||||
[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;
|
||||
|
||||
public AppShellViewModel(IPreferencesService preferencesService,
|
||||
IStatePersistanceService statePersistanceService,
|
||||
INavigationService navigationService,
|
||||
IWinoServerConnectionManager serverConnectionManager)
|
||||
{
|
||||
NavigationService = navigationService;
|
||||
ServerConnectionManager = serverConnectionManager;
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
StatePersistenceService = statePersistanceService;
|
||||
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
||||
}
|
||||
|
||||
private void PrefefencesChanged(object sender, string e)
|
||||
{
|
||||
if (e == nameof(StatePersistenceService.CalendarDisplayType))
|
||||
{
|
||||
DisplayTypeChanged?.Invoke(this, StatePersistenceService.CalendarDisplayType);
|
||||
OnPropertyChanged(nameof(IsVerticalCalendar));
|
||||
|
||||
// Change the calendar.
|
||||
DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
CreateFooterItems();
|
||||
UpdateDateNavigationHeaderItems();
|
||||
}
|
||||
|
||||
partial void OnSelectedMenuItemChanged(IMenuItem oldValue, IMenuItem newValue)
|
||||
{
|
||||
if (newValue is SettingsItem)
|
||||
{
|
||||
NavigationService.Navigate(WinoPage.SettingsPage);
|
||||
}
|
||||
else if (newValue is ManageAccountsMenuItem)
|
||||
{
|
||||
NavigationService.Navigate(WinoPage.AccountManagementPage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When calendar type switches, we need to navigate to the most ideal date.
|
||||
/// This method returns that date.
|
||||
/// </summary>
|
||||
private DateTime GetDisplayTypeSwitchDate()
|
||||
{
|
||||
switch (StatePersistenceService.CalendarDisplayType)
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;
|
||||
|
||||
return HighlightedDateRange.StartDate;
|
||||
case CalendarDisplayType.Week:
|
||||
// TODO: From settings
|
||||
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date.GetWeekStartDateForDate(DayOfWeek.Monday);
|
||||
|
||||
return HighlightedDateRange.StartDate.GetWeekStartDateForDate(DayOfWeek.Monday);
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
break;
|
||||
case CalendarDisplayType.Month:
|
||||
break;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DateTime.Today.Date;
|
||||
}
|
||||
|
||||
protected override void OnDispatcherAssigned()
|
||||
{
|
||||
base.OnDispatcherAssigned();
|
||||
|
||||
MenuItems = new MenuItemCollection(Dispatcher);
|
||||
FooterItems = new MenuItemCollection(Dispatcher);
|
||||
}
|
||||
|
||||
public override void OnPageLoaded()
|
||||
{
|
||||
base.OnPageLoaded();
|
||||
|
||||
NavigationService.Navigate(WinoPage.CalendarPage, new CalendarPageNavigationArgs()
|
||||
{
|
||||
RequestDefaultNavigation = true
|
||||
});
|
||||
}
|
||||
|
||||
private void CreateFooterItems()
|
||||
{
|
||||
FooterItems.Clear();
|
||||
FooterItems.Add(new ManageAccountsMenuItem());
|
||||
FooterItems.Add(new SettingsItem());
|
||||
}
|
||||
|
||||
#region Commands
|
||||
|
||||
[RelayCommand]
|
||||
private void TodayClicked() => Messenger.Send(new GoToCalendarDayMessage(DateTime.Now.Date));
|
||||
|
||||
[RelayCommand]
|
||||
public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);
|
||||
|
||||
[RelayCommand]
|
||||
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
|
||||
|
||||
[RelayCommand]
|
||||
private void DateClicked(CalendarViewDayClickedEventArgs clickedDate)
|
||||
=> Messenger.Send(new CalendarInitializeMessage(clickedDate.ClickedDate, CalendarInitInitiative.User));
|
||||
|
||||
#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);
|
||||
|
||||
// Calendar page is loaded and calendar is ready to recieve render requests.
|
||||
public void Receive(CalendarInitializedMessage message) => Messenger.Send(new GoToCalendarDayMessage(DateTime.Now.Date));
|
||||
|
||||
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItem = FooterItems.FirstOrDefault(a => a is ManageAccountsMenuItem);
|
||||
}
|
||||
}
|
||||
436
Wino.Calendar.ViewModels/CalendarPageViewModel.cs
Normal file
436
Wino.Calendar.ViewModels/CalendarPageViewModel.cs
Normal file
@@ -0,0 +1,436 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Calendar.Models.CalendarTypeStrategies;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.MenuItems;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
IRecipient<CalendarInitializeMessage>
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ObservableRangeCollection<DayRangeRenderModel> _dayRanges = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedDateRangeIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private DayRangeRenderModel _selectedDayRange;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isCalendarEnabled = true;
|
||||
|
||||
// Get rid of some of the items if we have too many.
|
||||
private const int maxDayRangeSize = 10;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
|
||||
// Store latest rendered options.
|
||||
private CalendarDisplayType _currentDisplayType;
|
||||
private int _displayDayCount;
|
||||
|
||||
private SemaphoreSlim _calendarLoadingSemaphore = new(1);
|
||||
private bool isLoadMoreBlocked = false;
|
||||
private CalendarSettings _currentSettings = null;
|
||||
|
||||
public IStatePersistanceService StatePersistanceService { get; }
|
||||
|
||||
public CalendarPageViewModel(IStatePersistanceService statePersistanceService,
|
||||
IPreferencesService preferencesService)
|
||||
{
|
||||
StatePersistanceService = statePersistanceService;
|
||||
_preferencesService = preferencesService;
|
||||
|
||||
_currentSettings = _preferencesService.GetCurrentCalendarSettings();
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
Messenger.Send(new CalendarInitializedMessage());
|
||||
}
|
||||
|
||||
// TODO: Replace when calendar settings are updated.
|
||||
// Should be a field ideally.
|
||||
private BaseCalendarTypeDrawingStrategy GetDrawingStrategy(CalendarDisplayType displayType)
|
||||
{
|
||||
return displayType switch
|
||||
{
|
||||
CalendarDisplayType.Day => new DayCalendarDrawingStrategy(_currentSettings),
|
||||
CalendarDisplayType.Week => new WeekCalendarDrawingStrategy(_currentSettings),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
partial void OnIsCalendarEnabledChanging(bool oldValue, bool newValue) => Messenger.Send(new CalendarEnableStatusChangedMessage(newValue));
|
||||
|
||||
private bool ShouldResetDayRanges(CalendarInitializeMessage message)
|
||||
{
|
||||
// Never reset if the initiative is from the app.
|
||||
if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false;
|
||||
|
||||
// 1. Display type is different.
|
||||
// 2. Day display count is different.
|
||||
// 3. Display date is not in the visible range.
|
||||
|
||||
return
|
||||
(_currentDisplayType != StatePersistanceService.CalendarDisplayType ||
|
||||
_displayDayCount != StatePersistanceService.DayDisplayCount ||
|
||||
(DayRanges != null && !DayRanges.Select(a => a.CalendarRenderOptions).Any(b => b.DateRange.IsInRange(message.DisplayDate))));
|
||||
}
|
||||
|
||||
public async void Receive(CalendarInitializeMessage message)
|
||||
{
|
||||
await _calendarLoadingSemaphore.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteUIThread(() => IsCalendarEnabled = false);
|
||||
|
||||
if (ShouldResetDayRanges(message))
|
||||
{
|
||||
DayRanges.Clear();
|
||||
|
||||
Debug.WriteLine("Will reset day ranges.");
|
||||
}
|
||||
else if (ShouldScrollToItem(message))
|
||||
{
|
||||
// Scroll to the selected date.
|
||||
|
||||
Messenger.Send(new ScrollToDateMessage(message.DisplayDate));
|
||||
Debug.WriteLine("Scrolling to selected date.");
|
||||
return;
|
||||
}
|
||||
|
||||
// This will replace the whole collection because the user initiated a new render.
|
||||
await RenderDatesAsync(message.CalendarInitInitiative,
|
||||
message.DisplayDate,
|
||||
CalendarLoadDirection.Replace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debugger.Break();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_calendarLoadingSemaphore.Release();
|
||||
|
||||
await ExecuteUIThread(() => IsCalendarEnabled = true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RenderDatesAsync(CalendarInitInitiative calendarInitInitiative,
|
||||
DateTime? loadingDisplayDate = null,
|
||||
CalendarLoadDirection calendarLoadDirection = CalendarLoadDirection.Replace)
|
||||
{
|
||||
// This is the part we arrange the flip view calendar logic.
|
||||
|
||||
/* Loading for a month of the selected date is fine.
|
||||
* If the selected date is in the loaded range, we'll just change the selected flip index to scroll.
|
||||
* If the selected date is not in the loaded range:
|
||||
* 1. Detect the direction of the scroll.
|
||||
* 2. Load the next month.
|
||||
* 3. Replace existing month with the new month.
|
||||
*/
|
||||
|
||||
// 2 things are important: How many items should 1 flip have, and, where we should start loading?
|
||||
|
||||
// User initiated renders must always have a date to start with.
|
||||
if (calendarInitInitiative == CalendarInitInitiative.User) Guard.IsNotNull(loadingDisplayDate, nameof(loadingDisplayDate));
|
||||
|
||||
var strategy = GetDrawingStrategy(StatePersistanceService.CalendarDisplayType);
|
||||
var displayDate = loadingDisplayDate.GetValueOrDefault();
|
||||
|
||||
// How many days should be placed in 1 flip view item?
|
||||
int eachFlipItemCount = strategy.GetRenderDayCount(displayDate, StatePersistanceService.DayDisplayCount);
|
||||
|
||||
DateRange flipLoadRange = null;
|
||||
|
||||
if (calendarInitInitiative == CalendarInitInitiative.User)
|
||||
{
|
||||
flipLoadRange = strategy.GetRenderDateRange(displayDate, StatePersistanceService.DayDisplayCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
var minimumLoadedDate = DayRanges[0].CalendarRenderOptions.DateRange.StartDate;
|
||||
var maximumLoadedDate = DayRanges[DayRanges.Count - 1].CalendarRenderOptions.DateRange.EndDate;
|
||||
|
||||
var currentInitializedDateRange = new DateRange(minimumLoadedDate, maximumLoadedDate);
|
||||
|
||||
// App is trying to load.
|
||||
// This should be based on direction. We'll load the next or previous range.
|
||||
// DisplayDate is either the start or end date of the current visible range.
|
||||
|
||||
if (calendarLoadDirection == CalendarLoadDirection.Previous)
|
||||
{
|
||||
flipLoadRange = strategy.GetPreviousDateRange(currentInitializedDateRange, StatePersistanceService.DayDisplayCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
flipLoadRange = strategy.GetNextDateRange(currentInitializedDateRange, StatePersistanceService.DayDisplayCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Create day ranges for each flip item until we reach the total days to load.
|
||||
int totalFlipItemCount = (int)Math.Ceiling((double)flipLoadRange.TotalDays / eachFlipItemCount);
|
||||
|
||||
List<DayRangeRenderModel> renderModels = new();
|
||||
|
||||
for (int i = 0; i < totalFlipItemCount; i++)
|
||||
{
|
||||
var startDate = flipLoadRange.StartDate.AddDays(i * eachFlipItemCount);
|
||||
var endDate = startDate.AddDays(eachFlipItemCount);
|
||||
|
||||
var range = new DateRange(startDate, endDate);
|
||||
var renderOptions = new CalendarRenderOptions(range, _currentSettings);
|
||||
|
||||
renderModels.Add(new DayRangeRenderModel(renderOptions));
|
||||
}
|
||||
|
||||
CalendarLoadDirection animationDirection = calendarLoadDirection;
|
||||
|
||||
bool removeCurrent = calendarLoadDirection == CalendarLoadDirection.Replace;
|
||||
|
||||
if (calendarLoadDirection == CalendarLoadDirection.Replace)
|
||||
{
|
||||
// New date ranges are being replaced.
|
||||
// We must preserve existing selection if any, add the items before/after the current one, remove the current one.
|
||||
// This will make sure the new dates are animated in the correct direction.
|
||||
|
||||
isLoadMoreBlocked = true;
|
||||
|
||||
// Remove all other dates except this one.
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
DayRanges.RemoveRange(DayRanges.Where(a => a != SelectedDayRange).ToList());
|
||||
});
|
||||
|
||||
animationDirection = displayDate <= SelectedDayRange?.CalendarRenderOptions.DateRange.StartDate ?
|
||||
CalendarLoadDirection.Previous : CalendarLoadDirection.Next;
|
||||
}
|
||||
|
||||
if (animationDirection == CalendarLoadDirection.Next)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
foreach (var item in renderModels)
|
||||
{
|
||||
DayRanges.Add(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (animationDirection == CalendarLoadDirection.Previous)
|
||||
{
|
||||
// Wait for the animation to finish.
|
||||
// Otherwise it somehow shutters a little, which is annoying.
|
||||
|
||||
if (!removeCurrent) await Task.Delay(500);
|
||||
|
||||
// Insert each render model in reverse order.
|
||||
for (int i = renderModels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
DayRanges.Insert(0, renderModels[i]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Flip count: ({DayRanges.Count})");
|
||||
|
||||
foreach (var item in DayRanges)
|
||||
{
|
||||
Debug.WriteLine($"- {item.CalendarRenderOptions.DateRange.ToString()}");
|
||||
}
|
||||
|
||||
if (removeCurrent)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
DayRanges.Remove(SelectedDayRange);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO...
|
||||
// await TryConsolidateItemsAsync();
|
||||
|
||||
isLoadMoreBlocked = false;
|
||||
|
||||
// Only scroll if the render is initiated by user.
|
||||
// Otherwise we'll scroll to the app rendered invisible date range.
|
||||
if (calendarInitInitiative == CalendarInitInitiative.User)
|
||||
{
|
||||
// Save the current settings for the page for later comparison.
|
||||
_currentDisplayType = StatePersistanceService.CalendarDisplayType;
|
||||
_displayDayCount = StatePersistanceService.DayDisplayCount;
|
||||
|
||||
Messenger.Send(new ScrollToDateMessage(displayDate));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryConsolidateItemsAsync()
|
||||
{
|
||||
// Check if trimming is necessary
|
||||
if (DayRanges.Count > maxDayRangeSize)
|
||||
{
|
||||
Debug.WriteLine("Trimming items.");
|
||||
|
||||
isLoadMoreBlocked = true;
|
||||
|
||||
var removeCount = DayRanges.Count - maxDayRangeSize;
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
// Right shifted, remove from the start.
|
||||
if (SelectedDateRangeIndex > DayRanges.Count / 2)
|
||||
{
|
||||
DayRanges.RemoveRange(DayRanges.Take(removeCount).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Left shifted, remove from the end.
|
||||
DayRanges.RemoveRange(DayRanges.Skip(DayRanges.Count - removeCount).Take(removeCount));
|
||||
}
|
||||
|
||||
SelectedDateRangeIndex = DayRanges.IndexOf(SelectedDayRange);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldScrollToItem(CalendarInitializeMessage message)
|
||||
{
|
||||
// Never scroll if the initiative is from the app.
|
||||
if (message.CalendarInitInitiative == CalendarInitInitiative.App) return false;
|
||||
|
||||
// Nothing to scroll.
|
||||
if (DayRanges.Count == 0) return false;
|
||||
|
||||
var minimumLoadedDate = DayRanges[0].CalendarRenderOptions.DateRange.StartDate;
|
||||
var maximumLoadedDate = DayRanges[DayRanges.Count - 1].CalendarRenderOptions.DateRange.EndDate;
|
||||
|
||||
var selectedDate = message.DisplayDate;
|
||||
|
||||
return selectedDate >= minimumLoadedDate && selectedDate <= maximumLoadedDate;
|
||||
}
|
||||
|
||||
partial void OnSelectedDayRangeChanged(DayRangeRenderModel value)
|
||||
{
|
||||
if (DayRanges.Count == 0 || SelectedDateRangeIndex < 0) return;
|
||||
|
||||
if (isLoadMoreBlocked) return;
|
||||
|
||||
var selectedRange = DayRanges[SelectedDateRangeIndex];
|
||||
|
||||
if (selectedRange != null)
|
||||
{
|
||||
// Send the loading message initiated by the app.
|
||||
if (SelectedDateRangeIndex == DayRanges.Count - 1)
|
||||
{
|
||||
// Load next, starting from the end date.
|
||||
_ = LoadMoreAsync(CalendarLoadDirection.Next);
|
||||
}
|
||||
else if (SelectedDateRangeIndex == 0)
|
||||
{
|
||||
// Load previous, starting from the start date.
|
||||
|
||||
_ = LoadMoreAsync(CalendarLoadDirection.Previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadMoreAsync(CalendarLoadDirection direction)
|
||||
{
|
||||
Debug.WriteLine($"Loading {direction} items.");
|
||||
|
||||
try
|
||||
{
|
||||
await _calendarLoadingSemaphore.WaitAsync();
|
||||
await RenderDatesAsync(CalendarInitInitiative.App, calendarLoadDirection: direction);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debugger.Break();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_calendarLoadingSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnCalendarEventAdded(ICalendarItem calendarItem)
|
||||
{
|
||||
base.OnCalendarEventAdded(calendarItem);
|
||||
|
||||
// Test
|
||||
var eventDays = DayRanges.SelectMany(a => a.CalendarDays).Where(b => b.Period.Start.Date == calendarItem.StartTime.Date);
|
||||
|
||||
var beforeAllDay = new CalendarItem(calendarItem.StartTime.Date.AddHours(0), calendarItem.StartTime.Date.AddMinutes(30))
|
||||
{
|
||||
Name = "kj"
|
||||
};
|
||||
|
||||
var allday = new CalendarItem(calendarItem.StartTime.Date.AddHours(1), calendarItem.StartTime.Date.AddHours(10).AddMinutes(59))
|
||||
{
|
||||
Name = "All day"
|
||||
};
|
||||
|
||||
var test = new CalendarItem(calendarItem.StartTime.Date.AddHours(4), calendarItem.StartTime.Date.AddHours(4).AddMinutes(30))
|
||||
{
|
||||
Name = "test"
|
||||
};
|
||||
|
||||
var hour = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8))
|
||||
{
|
||||
Name = "1 h"
|
||||
};
|
||||
|
||||
var hourandhalf = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30))
|
||||
{
|
||||
Name = "1.5 h"
|
||||
};
|
||||
var halfhour1 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(7).AddMinutes(30))
|
||||
{
|
||||
Name = "30 min"
|
||||
};
|
||||
|
||||
var halfhour2 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7).AddMinutes(30), calendarItem.StartTime.Date.AddHours(8))
|
||||
{
|
||||
Name = "30 min"
|
||||
};
|
||||
var halfhour3 = new CalendarItem(calendarItem.StartTime.Date.AddHours(8), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30))
|
||||
{
|
||||
Name = "30 min"
|
||||
};
|
||||
|
||||
foreach (var day in eventDays)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
day.Events.Add(beforeAllDay);
|
||||
day.Events.Add(allday);
|
||||
day.Events.Add(hourandhalf);
|
||||
day.Events.Add(hour);
|
||||
day.Events.Add(halfhour1);
|
||||
day.Events.Add(halfhour2);
|
||||
day.Events.Add(halfhour3);
|
||||
day.Events.Add(test);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs
Normal file
127
Wino.Calendar.ViewModels/CalendarSettingsPageViewModel.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Translations;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
{
|
||||
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)
|
||||
{
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
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();
|
||||
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
|
||||
{
|
||||
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.WorkingDayStart = WorkingDayStartIndex switch
|
||||
{
|
||||
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.WorkingDayEnd = WorkingDayEndIndex switch
|
||||
{
|
||||
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.Prefer24HourTimeFormat = Is24HourHeaders;
|
||||
PreferencesService.WorkingHourStart = WorkingHourStart;
|
||||
PreferencesService.WorkingHourEnd = WorkingHourEnd;
|
||||
PreferencesService.HourHeight = CellHourHeight;
|
||||
|
||||
Messenger.Send(new CalendarSettingsUpdatedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Wino.Calendar.ViewModels/Wino.Calendar.ViewModels.csproj
Normal file
20
Wino.Calendar.ViewModels/Wino.Calendar.ViewModels.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TimePeriodLibrary.NET" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core.ViewModels\Wino.Core.ViewModels.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core\Wino.Core.csproj" />
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user