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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||||
24
Wino.Calendar/Activation/DefaultActivationHandler.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.ApplicationModel.Activation;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Media.Animation;
|
||||||
|
using Wino.Activation;
|
||||||
|
using Wino.Calendar.Views;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Activation
|
||||||
|
{
|
||||||
|
public class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||||
|
{
|
||||||
|
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only navigate if Frame content doesn't exist.
|
||||||
|
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||||
|
=> (Window.Current?.Content as Frame)?.Content == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,34 @@
|
|||||||
<Application
|
<core:WinoApplication
|
||||||
x:Class="Wino.Calendar.App"
|
x:Class="Wino.Calendar.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:Wino.Calendar">
|
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
|
||||||
|
xmlns:core="using:Wino.Core.UWP"
|
||||||
|
xmlns:coreStyles="using:Wino.Core.UWP.Styles"
|
||||||
|
xmlns:local="using:Wino.Calendar"
|
||||||
|
xmlns:styles="using:Wino.Calendar.Styles">
|
||||||
|
<Application.Resources>
|
||||||
|
<controls:XamlControlsResources>
|
||||||
|
<controls:XamlControlsResources.MergedDictionaries>
|
||||||
|
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||||
|
|
||||||
|
<core:CoreGeneric />
|
||||||
|
|
||||||
|
<!-- Calendar Specific -->
|
||||||
|
<styles:WinoCalendarResources />
|
||||||
|
|
||||||
|
<ResourceDictionary Source="Styles/CalendarRenderStyles.xaml" />
|
||||||
|
|
||||||
|
<ResourceDictionary Source="Styles/CalendarDayItemsControl.xaml" />
|
||||||
|
<ResourceDictionary Source="Styles/DayHeaderControl.xaml" />
|
||||||
|
<ResourceDictionary Source="Styles/WinoDayTimelineCanvas.xaml" />
|
||||||
|
<ResourceDictionary Source="Styles/DayColumnControl.xaml" />
|
||||||
|
<ResourceDictionary Source="Styles/WinoCalendarView.xaml" />
|
||||||
|
<ResourceDictionary Source="Styles/WinoCalendarTypeSelectorControl.xaml" />
|
||||||
|
|
||||||
|
<styles:CalendarItemControlResources />
|
||||||
|
</controls:XamlControlsResources.MergedDictionaries>
|
||||||
|
</controls:XamlControlsResources>
|
||||||
|
</Application.Resources>
|
||||||
|
</core:WinoApplication>
|
||||||
|
|
||||||
</Application>
|
|
||||||
|
|||||||
@@ -1,100 +1,89 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
|
||||||
using Windows.ApplicationModel;
|
|
||||||
using Windows.ApplicationModel.Activation;
|
using Windows.ApplicationModel.Activation;
|
||||||
using Windows.Foundation;
|
using Windows.UI.Core.Preview;
|
||||||
using Windows.Foundation.Collections;
|
using Wino.Activation;
|
||||||
using Windows.UI.Xaml;
|
using Wino.Calendar.Activation;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Wino.Calendar.Services;
|
||||||
using Windows.UI.Xaml.Controls.Primitives;
|
using Wino.Calendar.ViewModels;
|
||||||
using Windows.UI.Xaml.Data;
|
using Wino.Core;
|
||||||
using Windows.UI.Xaml.Input;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Windows.UI.Xaml.Media;
|
using Wino.Core.UWP;
|
||||||
using Windows.UI.Xaml.Navigation;
|
|
||||||
|
|
||||||
namespace Wino.Calendar
|
namespace Wino.Calendar
|
||||||
{
|
{
|
||||||
/// <summary>
|
public sealed partial class App : WinoApplication
|
||||||
/// Provides application-specific behavior to supplement the default Application class.
|
|
||||||
/// </summary>
|
|
||||||
sealed partial class App : Application
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
public override string AppCenterKey => "dfdad6ab-95f9-44cc-9112-45ec6730c49e";
|
||||||
/// Initializes the singleton application object. This is the first line of authored code
|
|
||||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
|
||||||
/// </summary>
|
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
InitializeComponent();
|
||||||
this.Suspending += OnSuspending;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public override IServiceProvider ConfigureServices()
|
||||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
|
||||||
/// will be used such as when the application is launched to open a specific file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="e">Details about the launch request and process.</param>
|
|
||||||
protected override void OnLaunched(LaunchActivatedEventArgs e)
|
|
||||||
{
|
{
|
||||||
Frame rootFrame = Window.Current.Content as Frame;
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
// Do not repeat app initialization when the Window already has content,
|
services.RegisterCoreServices();
|
||||||
// just ensure that the window is active
|
services.RegisterCoreUWPServices();
|
||||||
if (rootFrame == null)
|
services.RegisterCoreViewModels();
|
||||||
|
|
||||||
|
RegisterUWPServices(services);
|
||||||
|
RegisterViewModels(services);
|
||||||
|
RegisterActivationHandlers(services);
|
||||||
|
|
||||||
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Dependency Injection
|
||||||
|
|
||||||
|
private void RegisterActivationHandlers(IServiceCollection services)
|
||||||
|
{
|
||||||
|
//services.AddTransient<ProtocolActivationHandler>();
|
||||||
|
//services.AddTransient<ToastNotificationActivationHandler>();
|
||||||
|
//services.AddTransient<FileActivationHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterUWPServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<INavigationService, NavigationService>();
|
||||||
|
services.AddSingleton<ICalendarDialogService, DialogService>();
|
||||||
|
services.AddTransient<ISettingsBuilderService, SettingsBuilderService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterViewModels(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton(typeof(AppShellViewModel));
|
||||||
|
services.AddTransient(typeof(CalendarPageViewModel));
|
||||||
|
services.AddTransient(typeof(CalendarSettingsPageViewModel));
|
||||||
|
services.AddTransient(typeof(AccountManagementViewModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected override void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||||
|
{
|
||||||
|
// TODO: Check server running.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
|
||||||
|
|
||||||
|
if (!args.PrelaunchActivated)
|
||||||
{
|
{
|
||||||
// Create a Frame to act as the navigation context and navigate to the first page
|
await ActivateWinoAsync(args);
|
||||||
rootFrame = new Frame();
|
|
||||||
|
|
||||||
rootFrame.NavigationFailed += OnNavigationFailed;
|
|
||||||
|
|
||||||
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
|
|
||||||
{
|
|
||||||
//TODO: Load state from previously suspended application
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place the frame in the current Window
|
|
||||||
Window.Current.Content = rootFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.PrelaunchActivated == false)
|
|
||||||
{
|
|
||||||
if (rootFrame.Content == null)
|
|
||||||
{
|
|
||||||
// When the navigation stack isn't restored navigate to the first page,
|
|
||||||
// configuring the new page by passing required information as a navigation
|
|
||||||
// parameter
|
|
||||||
rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
|
||||||
}
|
|
||||||
// Ensure the current window is active
|
|
||||||
Window.Current.Activate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override IEnumerable<ActivationHandler> GetActivationHandlers()
|
||||||
/// Invoked when Navigation to a certain page fails
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">The Frame which failed navigation</param>
|
|
||||||
/// <param name="e">Details about the navigation failure</param>
|
|
||||||
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
|
|
||||||
{
|
{
|
||||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
|
||||||
/// Invoked when application execution is being suspended. Application state is saved
|
=> new DefaultActivationHandler();
|
||||||
/// without knowing whether the application will be terminated or resumed with the contents
|
|
||||||
/// of memory still intact.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">The source of the suspend request.</param>
|
|
||||||
/// <param name="e">Details about the suspend request.</param>
|
|
||||||
private void OnSuspending(object sender, SuspendingEventArgs e)
|
|
||||||
{
|
|
||||||
var deferral = e.SuspendingOperation.GetDeferral();
|
|
||||||
//TODO: Save application state and stop any background activity
|
|
||||||
deferral.Complete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
Wino.Calendar/Args/TimelineCellSelectedArgs.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Windows.Foundation;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Args
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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;
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Wino.Calendar/Args/TimelineCellUnselectedArgs.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Args
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When selected timeline cell is unselected.
|
||||||
|
/// </summary>
|
||||||
|
public class TimelineCellUnselectedArgs : EventArgs { }
|
||||||
|
}
|
||||||
BIN
Wino.Calendar/Assets/LargeTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Wino.Calendar/Assets/LargeTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
Wino.Calendar/Assets/SmallTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-100.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-125.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-150.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 17 KiB |
BIN
Wino.Calendar/Assets/SplashScreen.scale-400.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 6.0 KiB |
BIN
Wino.Calendar/Assets/Square150x150Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 809 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 809 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-16.png
Normal file
|
After Width: | Height: | Size: 596 B |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-24.png
Normal file
|
After Width: | Height: | Size: 920 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-256.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Wino.Calendar/Assets/Square44x44Logo.targetsize-48.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-100.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-125.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-150.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-200.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
Wino.Calendar/Assets/StoreLogo.scale-400.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 6.6 KiB |
BIN
Wino.Calendar/Assets/Wide310x150Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
132
Wino.Calendar/Controls/CalendarDayItemsControl.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
public class CalendarDayItemsControl : Control
|
||||||
|
{
|
||||||
|
private const string PART_CalendarPanel = nameof(PART_CalendarPanel);
|
||||||
|
|
||||||
|
private WinoCalendarPanel CalendarPanel;
|
||||||
|
|
||||||
|
public CalendarDayModel DayModel
|
||||||
|
{
|
||||||
|
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
||||||
|
set { SetValue(DayModelProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(CalendarDayItemsControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRepresentingDateChanged)));
|
||||||
|
|
||||||
|
private static void OnRepresentingDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is CalendarDayItemsControl control)
|
||||||
|
{
|
||||||
|
if (e.OldValue != null && e.OldValue is CalendarDayModel oldCalendarDayModel)
|
||||||
|
{
|
||||||
|
control.DetachCollection(oldCalendarDayModel.Events);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewValue != null && e.NewValue is CalendarDayModel newCalendarDayModel)
|
||||||
|
{
|
||||||
|
control.AttachCollection(newCalendarDayModel.Events);
|
||||||
|
}
|
||||||
|
|
||||||
|
control.ResetItems();
|
||||||
|
control.RenderEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarDayItemsControl()
|
||||||
|
{
|
||||||
|
DefaultStyleKey = typeof(CalendarDayItemsControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
CalendarPanel = GetTemplateChild(PART_CalendarPanel) as WinoCalendarPanel;
|
||||||
|
|
||||||
|
RenderEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetItems()
|
||||||
|
{
|
||||||
|
if (CalendarPanel == null) return;
|
||||||
|
|
||||||
|
CalendarPanel.Children.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderEvents()
|
||||||
|
{
|
||||||
|
if (CalendarPanel == null || CalendarPanel.DayModel == null) return;
|
||||||
|
|
||||||
|
RenderCalendarItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AttachCollection(ObservableCollection<ICalendarItem> newCollection)
|
||||||
|
=> newCollection.CollectionChanged += CalendarItemsChanged;
|
||||||
|
|
||||||
|
private void DetachCollection(ObservableCollection<ICalendarItem> oldCollection)
|
||||||
|
=> oldCollection.CollectionChanged -= CalendarItemsChanged;
|
||||||
|
|
||||||
|
private void CalendarItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
foreach (ICalendarItem item in e.NewItems)
|
||||||
|
{
|
||||||
|
AddItem(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
foreach (ICalendarItem item in e.OldItems)
|
||||||
|
{
|
||||||
|
var control = GetCalendarItemControl(item);
|
||||||
|
if (control != null)
|
||||||
|
{
|
||||||
|
CalendarPanel.Children.Remove(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
|
ResetItems();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CalendarItemControl GetCalendarItemControl(ICalendarItem item)
|
||||||
|
=> CalendarPanel.Children.Where(c => c is CalendarItemControl calendarItemControl && calendarItemControl.Item == item).FirstOrDefault() as CalendarItemControl;
|
||||||
|
|
||||||
|
private void RenderCalendarItems()
|
||||||
|
{
|
||||||
|
if (DayModel == null || DayModel.Events == null || DayModel.Events.Count == 0)
|
||||||
|
{
|
||||||
|
ResetItems();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in DayModel.Events)
|
||||||
|
{
|
||||||
|
AddItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddItem(ICalendarItem item)
|
||||||
|
{
|
||||||
|
CalendarPanel.Children.Add(new CalendarItemControl()
|
||||||
|
{
|
||||||
|
Item = item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Wino.Calendar/Controls/CalendarItemControl.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
public class CalendarItemControl : Control
|
||||||
|
{
|
||||||
|
public ICalendarItem Item
|
||||||
|
{
|
||||||
|
get { return (ICalendarItem)GetValue(ItemProperty); }
|
||||||
|
set { SetValue(ItemProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(ICalendarItem), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
|
||||||
|
|
||||||
|
public CalendarItemControl()
|
||||||
|
{
|
||||||
|
DefaultStyleKey = typeof(CalendarItemControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is CalendarItemControl control)
|
||||||
|
{
|
||||||
|
control.UpdateDateRendering();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDateRendering()
|
||||||
|
{
|
||||||
|
if (Item == null) return;
|
||||||
|
|
||||||
|
UpdateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Item?.Name ?? "NA";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Wino.Calendar/Controls/CustomCalendarFlipView.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Windows.UI.Xaml.Automation.Peers;
|
||||||
|
using Windows.UI.Xaml.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 class CustomCalendarFlipView : FlipView
|
||||||
|
{
|
||||||
|
private const string PART_PreviousButton = "PreviousButtonHorizontal";
|
||||||
|
private const string PART_NextButton = "NextButtonHorizontal";
|
||||||
|
|
||||||
|
private Button PreviousButton;
|
||||||
|
private Button NextButton;
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
PreviousButton = GetTemplateChild(PART_PreviousButton) as Button;
|
||||||
|
NextButton = GetTemplateChild(PART_NextButton) as Button;
|
||||||
|
|
||||||
|
// Hide navigation buttons
|
||||||
|
PreviousButton.Opacity = NextButton.Opacity = 0;
|
||||||
|
PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoPreviousFlip()
|
||||||
|
{
|
||||||
|
var backPeer = new ButtonAutomationPeer(PreviousButton);
|
||||||
|
backPeer.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoNextFlip()
|
||||||
|
{
|
||||||
|
var nextPeer = new ButtonAutomationPeer(NextButton);
|
||||||
|
nextPeer.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
65
Wino.Calendar/Controls/DayColumnControl.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
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 TodayState = nameof(TodayState);
|
||||||
|
private const string NotTodayState = nameof(NotTodayState);
|
||||||
|
|
||||||
|
private TextBlock HeaderDateDayText;
|
||||||
|
private TextBlock ColumnHeaderText;
|
||||||
|
private Border IsTodayBorder;
|
||||||
|
|
||||||
|
public CalendarDayModel DayModel
|
||||||
|
{
|
||||||
|
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
||||||
|
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;
|
||||||
|
|
||||||
|
UpdateValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (control is DayColumnControl columnControl)
|
||||||
|
{
|
||||||
|
columnControl.UpdateValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateValues()
|
||||||
|
{
|
||||||
|
if (HeaderDateDayText == null || ColumnHeaderText == null || IsTodayBorder == null || DayModel == null) return;
|
||||||
|
|
||||||
|
HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString();
|
||||||
|
ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo);
|
||||||
|
|
||||||
|
bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date;
|
||||||
|
|
||||||
|
VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Wino.Calendar/Controls/DayHeaderControl.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
public class DayHeaderControl : Control
|
||||||
|
{
|
||||||
|
private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock);
|
||||||
|
private TextBlock HeaderTextblock;
|
||||||
|
|
||||||
|
public DayHeaderDisplayType DisplayType
|
||||||
|
{
|
||||||
|
get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); }
|
||||||
|
set { SetValue(DisplayTypeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
headerControl.UpdateHeaderText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHeaderText()
|
||||||
|
{
|
||||||
|
if (HeaderTextblock != null)
|
||||||
|
{
|
||||||
|
HeaderTextblock.Text = DisplayType == DayHeaderDisplayType.TwelveHour ? Date.ToString("h tt") : Date.ToString("HH:mm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
Wino.Calendar/Controls/WinoCalendarControl.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Calendar.Args;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
public class WinoCalendarControl : Control
|
||||||
|
{
|
||||||
|
private const string PART_WinoFlipView = nameof(PART_WinoFlipView);
|
||||||
|
|
||||||
|
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||||
|
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||||
|
|
||||||
|
#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 DayRangeRenderModel SelectedFlipViewDayRange
|
||||||
|
{
|
||||||
|
get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); }
|
||||||
|
set { SetValue(SelectedFlipViewDayRangeProperty, 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 WinoDayTimelineCanvas _activeCanvas;
|
||||||
|
|
||||||
|
public WinoDayTimelineCanvas ActiveCanvas
|
||||||
|
{
|
||||||
|
get { return _activeCanvas; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// FlipView's timeline is changing.
|
||||||
|
// Make sure to unregister from the old one.
|
||||||
|
|
||||||
|
if (_activeCanvas != null)
|
||||||
|
{
|
||||||
|
// Dismiss any selection on the old canvas.
|
||||||
|
|
||||||
|
_activeCanvas.SelectedDateTime = null;
|
||||||
|
_activeCanvas.TimelineCellSelected -= ActiveTimelineCellSelected;
|
||||||
|
_activeCanvas.TimelineCellUnselected -= ActiveTimelineCellUnselected;
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeCanvas = value;
|
||||||
|
|
||||||
|
if (_activeCanvas != null)
|
||||||
|
{
|
||||||
|
_activeCanvas.TimelineCellSelected += ActiveTimelineCellSelected;
|
||||||
|
_activeCanvas.TimelineCellUnselected += ActiveTimelineCellUnselected;
|
||||||
|
|
||||||
|
// Raise visible date range change to shell.
|
||||||
|
WeakReferenceMessenger.Default.Send(new VisibleDateRangeChangedMessage(_activeCanvas.RenderOptions.DateRange));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WinoCalendarFlipView InternalFlipView;
|
||||||
|
|
||||||
|
public WinoCalendarControl()
|
||||||
|
{
|
||||||
|
DefaultStyleKey = typeof(WinoCalendarControl);
|
||||||
|
SizeChanged += CalendarSizeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Each FlipViewItem will have 1 timeline canvas to draw hour cells in the background that supports selection of them.
|
||||||
|
// When the selection changes, we need to stop listening to the old canvas and start listening to the new one to catch events.
|
||||||
|
|
||||||
|
InternalFlipView.ActiveTimelineCanvasChanged += FlipViewsActiveTimelineCanvasChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlipViewsActiveTimelineCanvasChanged(object sender, WinoDayTimelineCanvas e)
|
||||||
|
{
|
||||||
|
ActiveCanvas = e;
|
||||||
|
|
||||||
|
SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
Wino.Calendar/Controls/WinoCalendarFlipView.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.WinUI;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
using Wino.Core.MenuItems;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
public class WinoCalendarFlipView : CustomCalendarFlipView
|
||||||
|
{
|
||||||
|
public event EventHandler<WinoDayTimelineCanvas> ActiveTimelineCanvasChanged;
|
||||||
|
|
||||||
|
public WinoCalendarFlipView()
|
||||||
|
{
|
||||||
|
SelectionChanged += CalendarDisplayRangeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void CalendarDisplayRangeChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectedIndex < 0)
|
||||||
|
ActiveTimelineCanvasChanged?.Invoke(this, null);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Refactor this mechanism by listening to PrepareContainerForItemOverride and Loaded events together.
|
||||||
|
while (ContainerFromIndex(SelectedIndex) == null)
|
||||||
|
{
|
||||||
|
await Task.Delay(250);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContainerFromIndex(SelectedIndex) is FlipViewItem flipViewItem)
|
||||||
|
{
|
||||||
|
var canvas = flipViewItem.FindDescendant<WinoDayTimelineCanvas>();
|
||||||
|
ActiveTimelineCanvasChanged?.Invoke(this, canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigates to the specified date in the calendar.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateTime">Date to navigate.</param>
|
||||||
|
public async void NavigateToDay(DateTime dateTime)
|
||||||
|
{
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void NavigateHour(TimeSpan hourTimeSpan)
|
||||||
|
{
|
||||||
|
// Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers.
|
||||||
|
// Find the day range that contains the hour.
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource()
|
||||||
|
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
|
||||||
|
}
|
||||||
|
}
|
||||||
254
Wino.Calendar/Controls/WinoCalendarPanel.cs
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Calendar.Models;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
public class WinoCalendarPanel : Panel
|
||||||
|
{
|
||||||
|
private const double LastItemRightExtraMargin = 12d;
|
||||||
|
|
||||||
|
// Store each ICalendarItem measurements by their Id.
|
||||||
|
private readonly Dictionary<Guid, CalendarItemMeasurement> _measurements = new Dictionary<Guid, 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 DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(WinoCalendarPanel), new PropertyMetadata(null, new PropertyChangedCallback(OnDayChanged)));
|
||||||
|
|
||||||
|
public CalendarDayModel DayModel
|
||||||
|
{
|
||||||
|
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
||||||
|
set { SetValue(DayModelProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thickness EventItemMargin
|
||||||
|
{
|
||||||
|
get { return (Thickness)GetValue(EventItemMarginProperty); }
|
||||||
|
set { SetValue(EventItemMarginProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinoCalendarPanel control)
|
||||||
|
{
|
||||||
|
// We need to listen for new events being added or removed from the collection to reset measurements.
|
||||||
|
if (e.OldValue is CalendarDayModel oldDayModel)
|
||||||
|
{
|
||||||
|
control.DetachCollection(oldDayModel.Events);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewValue is CalendarDayModel newDayModel)
|
||||||
|
{
|
||||||
|
control.AttachCollection(newDayModel.Events);
|
||||||
|
}
|
||||||
|
|
||||||
|
control.ResetMeasurements();
|
||||||
|
control.UpdateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AttachCollection(IEnumerable<ICalendarItem> events)
|
||||||
|
{
|
||||||
|
if (events is INotifyCollectionChanged collection)
|
||||||
|
{
|
||||||
|
collection.CollectionChanged += EventCollectionChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachCollection(IEnumerable<ICalendarItem> events)
|
||||||
|
{
|
||||||
|
if (events is INotifyCollectionChanged collection)
|
||||||
|
{
|
||||||
|
collection.CollectionChanged -= EventCollectionChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetMeasurements() => _measurements.Clear();
|
||||||
|
|
||||||
|
// No need to handle actions. Each action requires a full measurement update.
|
||||||
|
private void EventCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => ResetMeasurements();
|
||||||
|
|
||||||
|
private double GetChildTopMargin(DateTime childStart, double availableHeight)
|
||||||
|
{
|
||||||
|
double totalMinutes = 1440;
|
||||||
|
double minutesFromStart = (childStart - DayModel.RepresentingDate).TotalMinutes;
|
||||||
|
return (minutesFromStart / totalMinutes) * 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(DateTime childStart, DateTime childEnd)
|
||||||
|
{
|
||||||
|
double totalMinutes = 1440;
|
||||||
|
double availableHeight = DayModel.CalendarRenderOptions.CalendarSettings.HourHeight * 24;
|
||||||
|
double childDuration = (childEnd - childStart).TotalMinutes;
|
||||||
|
return (childDuration / totalMinutes) * availableHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size ArrangeOverride(Size 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<CalendarItemControl>();
|
||||||
|
|
||||||
|
if (_measurements.Count == 0 && DayModel.Events.Count > 0)
|
||||||
|
{
|
||||||
|
// We keep track of this collection when event is added/removed/reset etc.
|
||||||
|
// So if the collection is empty, we must fill it up again for proper calculations.
|
||||||
|
|
||||||
|
LayoutEvents(DayModel.Events);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in calendarControls)
|
||||||
|
{
|
||||||
|
// We can't arrange this child. It doesn't have a valid ICalendarItem or measurement.
|
||||||
|
if (child.Item == null || !_measurements.ContainsKey(child.Item.Id)) continue;
|
||||||
|
|
||||||
|
var childMeasurement = _measurements[child.Item.Id];
|
||||||
|
|
||||||
|
double childHeight = Math.Max(0, GetChildHeight(child.Item.StartTime, child.Item.EndTime));
|
||||||
|
double childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
|
||||||
|
double childTop = Math.Max(0, GetChildTopMargin(child.Item.StartTime, availableHeight));
|
||||||
|
double childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
|
||||||
|
|
||||||
|
bool isHorizontallyLastItem = childMeasurement.Right == 1;
|
||||||
|
|
||||||
|
// Add additional right margin to items that falls on the right edge of the panel.
|
||||||
|
// Max of 5% of the width or 20px.
|
||||||
|
var extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0;
|
||||||
|
|
||||||
|
var finalChildWidth = childWidth - extraRightMargin;
|
||||||
|
|
||||||
|
if (finalChildWidth < 0) finalChildWidth = 1;
|
||||||
|
|
||||||
|
child.Measure(new Size(childWidth, childHeight));
|
||||||
|
|
||||||
|
var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, childWidth - extraRightMargin, childHeight);
|
||||||
|
|
||||||
|
child.Arrange(arrangementRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ColumSpanning and Packing Algorithm
|
||||||
|
|
||||||
|
private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement)
|
||||||
|
{
|
||||||
|
if (_measurements.ContainsKey(calendarItem.Id))
|
||||||
|
{
|
||||||
|
_measurements[calendarItem.Id] = measurement;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_measurements.Add(calendarItem.Id, measurement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick the left and right positions of each event, such that there are no overlap.
|
||||||
|
private void LayoutEvents(IEnumerable<ICalendarItem> events)
|
||||||
|
{
|
||||||
|
var columns = new List<List<ICalendarItem>>();
|
||||||
|
DateTime? lastEventEnding = null;
|
||||||
|
|
||||||
|
foreach (var ev in events.OrderBy(ev => ev.Period.Start).ThenBy(ev => ev.Period.End))
|
||||||
|
{
|
||||||
|
if (ev.Period.Start >= lastEventEnding)
|
||||||
|
{
|
||||||
|
PackEvents(columns);
|
||||||
|
columns.Clear();
|
||||||
|
lastEventEnding = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool placed = false;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return colSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
92
Wino.Calendar/Controls/WinoCalendarTypeSelectorControl.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
using CommunityToolkit.Diagnostics;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
get { return (ICommand)GetValue(TodayClickedCommandProperty); }
|
||||||
|
set { SetValue(TodayClickedCommandProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarDisplayType SelectedType
|
||||||
|
{
|
||||||
|
get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); }
|
||||||
|
set { SetValue(SelectedTypeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DisplayDayCount
|
||||||
|
{
|
||||||
|
get { return (int)GetValue(DisplayDayCountProperty); }
|
||||||
|
set { SetValue(DisplayDayCountProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppBarButton _todayButton;
|
||||||
|
private AppBarToggleButton _dayToggle;
|
||||||
|
private AppBarToggleButton _weekToggle;
|
||||||
|
private AppBarToggleButton _monthToggle;
|
||||||
|
private AppBarToggleButton _yearToggle;
|
||||||
|
|
||||||
|
public WinoCalendarTypeSelectorControl()
|
||||||
|
{
|
||||||
|
DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
_todayButton = GetTemplateChild(PART_TodayButton) as AppBarButton;
|
||||||
|
_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));
|
||||||
|
Guard.IsNotNull(_dayToggle, nameof(_dayToggle));
|
||||||
|
Guard.IsNotNull(_weekToggle, nameof(_weekToggle));
|
||||||
|
Guard.IsNotNull(_monthToggle, nameof(_monthToggle));
|
||||||
|
Guard.IsNotNull(_yearToggle, nameof(_yearToggle));
|
||||||
|
|
||||||
|
_todayButton.Click += TodayClicked;
|
||||||
|
|
||||||
|
_dayToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Day); };
|
||||||
|
_weekToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Week); };
|
||||||
|
_monthToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Month); };
|
||||||
|
_yearToggle.Click += (s, e) => { SetSelectedType(CalendarDisplayType.Year); };
|
||||||
|
|
||||||
|
UpdateToggleButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TodayClicked(object sender, RoutedEventArgs e) => TodayClickedCommand?.Execute(null);
|
||||||
|
|
||||||
|
private void SetSelectedType(CalendarDisplayType type)
|
||||||
|
{
|
||||||
|
SelectedType = type;
|
||||||
|
UpdateToggleButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateToggleButtonStates()
|
||||||
|
{
|
||||||
|
_dayToggle.IsChecked = SelectedType == CalendarDisplayType.Day;
|
||||||
|
_weekToggle.IsChecked = SelectedType == CalendarDisplayType.Week;
|
||||||
|
_monthToggle.IsChecked = SelectedType == CalendarDisplayType.Month;
|
||||||
|
_yearToggle.IsChecked = SelectedType == CalendarDisplayType.Year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
Wino.Calendar/Controls/WinoCalendarView.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using CommunityToolkit.Diagnostics;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Media;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
using Wino.Helpers;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
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 TodayBackgroundBrushProperty = DependencyProperty.Register(nameof(TodayBackgroundBrush), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null));
|
||||||
|
public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the command to execute when a date is picked.
|
||||||
|
/// Unused.
|
||||||
|
/// </summary>
|
||||||
|
public ICommand DateClickedCommand
|
||||||
|
{
|
||||||
|
get { return (ICommand)GetValue(DateClickedCommandProperty); }
|
||||||
|
set { SetValue(DateClickedCommandProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the highlighted range of dates.
|
||||||
|
/// </summary>
|
||||||
|
public DateRange HighlightedDateRange
|
||||||
|
{
|
||||||
|
get { return (DateRange)GetValue(HighlightedDateRangeProperty); }
|
||||||
|
set { SetValue(HighlightedDateRangeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush VisibleDateBackground
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(VisibleDateBackgroundProperty); }
|
||||||
|
set { SetValue(VisibleDateBackgroundProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush TodayBackgroundBrush
|
||||||
|
{
|
||||||
|
get { return (Brush)GetValue(TodayBackgroundBrushProperty); }
|
||||||
|
set { SetValue(TodayBackgroundBrushProperty, 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)
|
||||||
|
{
|
||||||
|
var clickedDate = args.AddedDates[0].Date;
|
||||||
|
SetInnerDisplayDate(clickedDate);
|
||||||
|
|
||||||
|
var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate);
|
||||||
|
DateClickedCommand?.Execute(clickArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset selection, we don't show selected dates but react to them.
|
||||||
|
CalendarView.SelectedDates.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinoCalendarView control)
|
||||||
|
{
|
||||||
|
control.UpdateVisibleDateRangeBackgrounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetInnerDisplayDate(DateTime dateTime) => CalendarView?.SetDisplayDate(dateTime);
|
||||||
|
|
||||||
|
// Changing selected dates will trigger the selection changed event.
|
||||||
|
// It will behave like user clicked the date.
|
||||||
|
public void GoToDay(DateTime dateTime) => CalendarView.SelectedDates.Add(dateTime);
|
||||||
|
|
||||||
|
private static void OnHighlightedDateRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinoCalendarView control)
|
||||||
|
{
|
||||||
|
control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate);
|
||||||
|
control.UpdateVisibleDateRangeBackgrounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateVisibleDateRangeBackgrounds()
|
||||||
|
{
|
||||||
|
if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundBrush == 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 = TodayBackgroundBrush;
|
||||||
|
}
|
||||||
|
else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date)
|
||||||
|
{
|
||||||
|
border.Background = VisibleDateBackground;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
border.Background = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
277
Wino.Calendar/Controls/WinoDayTimelineCanvas.cs
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.Graphics.Canvas.Geometry;
|
||||||
|
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.UI.Input;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Media;
|
||||||
|
using Wino.Calendar.Args;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Controls
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
get { return (UIElement)GetValue(PositionerUIElementProperty); }
|
||||||
|
set { SetValue(PositionerUIElementProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CalendarRenderOptions RenderOptions
|
||||||
|
{
|
||||||
|
get { return (CalendarRenderOptions)GetValue(RenderOptionsProperty); }
|
||||||
|
set { SetValue(RenderOptionsProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolidColorBrush HalfHourSeperatorColor
|
||||||
|
{
|
||||||
|
get { return (SolidColorBrush)GetValue(HalfHourSeperatorColorProperty); }
|
||||||
|
set { SetValue(HalfHourSeperatorColorProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolidColorBrush SeperatorColor
|
||||||
|
{
|
||||||
|
get { return (SolidColorBrush)GetValue(SeperatorColorProperty); }
|
||||||
|
set { SetValue(SeperatorColorProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolidColorBrush WorkingHourCellBackgroundColor
|
||||||
|
{
|
||||||
|
get { return (SolidColorBrush)GetValue(WorkingHourCellBackgroundColorProperty); }
|
||||||
|
set { SetValue(WorkingHourCellBackgroundColorProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolidColorBrush SelectedCellBackgroundBrush
|
||||||
|
{
|
||||||
|
get { return (SolidColorBrush)GetValue(SelectedCellBackgroundBrushProperty); }
|
||||||
|
set { SetValue(SelectedCellBackgroundBrushProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? SelectedDateTime
|
||||||
|
{
|
||||||
|
get { return (DateTime?)GetValue(SelectedDateTimeProperty); }
|
||||||
|
set { SetValue(SelectedDateTimeProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
Canvas = GetTemplateChild(PART_InternalCanvas) as CanvasControl;
|
||||||
|
|
||||||
|
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.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
|
||||||
|
{
|
||||||
|
TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize));
|
||||||
|
SelectedDateTime = clickedDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,11 @@
|
|||||||
x:Class="Wino.Calendar.MainPage"
|
x:Class="Wino.Calendar.MainPage"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:Wino.Calendar"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:Wino.Calendar"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d"
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Grid>
|
<Grid />
|
||||||
|
|
||||||
</Grid>
|
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
17
Wino.Calendar/Models/CalendarItemMeasurement.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Wino.Calendar.Models
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
Right = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="58272BurakKSE.WinoCalendar"
|
Name="58272BurakKSE.WinoCalendar"
|
||||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||||
Version="1.0.0.0" />
|
Version="1.0.7.0" />
|
||||||
|
|
||||||
<mp:PhoneIdentity PhoneProductId="f047b7dd-96ec-4d54-a862-9321e271e449" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
<mp:PhoneIdentity PhoneProductId="f047b7dd-96ec-4d54-a862-9321e271e449" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
Square44x44Logo="Assets\Square44x44Logo.png"
|
Square44x44Logo="Assets\Square44x44Logo.png"
|
||||||
Description="Wino.Calendar"
|
Description="Wino.Calendar"
|
||||||
BackgroundColor="transparent">
|
BackgroundColor="transparent">
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
|
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png"/>
|
||||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -21,11 +21,24 @@
|
|||||||
An Assembly element with Name="*Application*" applies to all assemblies in
|
An Assembly element with Name="*Application*" applies to all assemblies in
|
||||||
the application package. The asterisks are not wildcards.
|
the application package. The asterisks are not wildcards.
|
||||||
-->
|
-->
|
||||||
<Assembly Name="*Application*" Dynamic="Required All" />
|
<Assembly Name="*Application*" Dynamic="Required All" />
|
||||||
|
|
||||||
|
<!-- Reduce memory footprint when building with Microsoft.Graph -->
|
||||||
|
|
||||||
<!-- Add your application specific runtime directives here. -->
|
<Assembly Name="Microsoft.Graph" Serialize="Excluded" />
|
||||||
|
|
||||||
|
<Assembly Name="Microsoft.Kiota.Abstractions" Dynamic="Public" />
|
||||||
|
<Assembly Name="Microsoft.Kiota.Authentication.Azure" Dynamic="Public" />
|
||||||
|
<Assembly Name="Microsoft.Kiota.Http.HttpClientLibrary" Dynamic="Public" />
|
||||||
|
<Assembly Name="Microsoft.Kiota.Serialization.Form" Dynamic="Public" />
|
||||||
|
<Assembly Name="Microsoft.Kiota.Serialization.Json" Dynamic="Public" />
|
||||||
|
<Assembly Name="Microsoft.Kiota.Serialization.Multipart" Dynamic="Public" />
|
||||||
|
|
||||||
|
<!-- Add your application specific runtime directives here. -->
|
||||||
|
<Type Name="Windows.Foundation.TypedEventHandler{Microsoft.UI.Xaml.Controls.NavigationView,Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs}" MarshalObject="Public" />
|
||||||
|
|
||||||
|
<Type Name="Microsoft.UI.Xaml.Controls.NavigationView">
|
||||||
|
<Event Name="ItemInvoked" Dynamic="Required"/>
|
||||||
|
</Type>
|
||||||
</Application>
|
</Application>
|
||||||
</Directives>
|
</Directives>
|
||||||
12
Wino.Calendar/Services/ApplicationResourceManager.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Services
|
||||||
|
{
|
||||||
|
internal class ApplicationResourceManager
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Wino.Calendar/Services/DialogService.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.UWP.Services;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Services
|
||||||
|
{
|
||||||
|
public class DialogService : DialogServiceBase, ICalendarDialogService
|
||||||
|
{
|
||||||
|
public DialogService(IThemeService themeService,
|
||||||
|
IConfigurationService configurationService,
|
||||||
|
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Wino.Calendar/Services/NavigationService.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Calendar.Views;
|
||||||
|
using Wino.Calendar.Views.Account;
|
||||||
|
using Wino.Calendar.Views.Settings;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
|
using Wino.Core.UWP.Services;
|
||||||
|
using Wino.Views;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Services
|
||||||
|
{
|
||||||
|
public class NavigationService : NavigationServiceBase, INavigationService
|
||||||
|
{
|
||||||
|
public Type GetPageType(WinoPage winoPage)
|
||||||
|
{
|
||||||
|
switch (winoPage)
|
||||||
|
{
|
||||||
|
case WinoPage.CalendarPage:
|
||||||
|
return typeof(CalendarPage);
|
||||||
|
case WinoPage.SettingsPage:
|
||||||
|
return typeof(SettingsPage);
|
||||||
|
case WinoPage.CalendarSettingsPage:
|
||||||
|
return typeof(CalendarSettingsPage);
|
||||||
|
case WinoPage.AccountManagementPage:
|
||||||
|
return typeof(AccountManagementPage);
|
||||||
|
default:
|
||||||
|
throw new Exception("Page is not implemented yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
||||||
|
{
|
||||||
|
var shellFrame = shellPage.GetShellFrame();
|
||||||
|
|
||||||
|
var pageType = GetPageType(page);
|
||||||
|
|
||||||
|
shellFrame.Navigate(pageType, parameter);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Wino.Calendar/Services/SettingsBuilderService.cs
Normal file
21
Wino.Calendar/Styles/CalendarDayItemsControl.xaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Calendar.Controls">
|
||||||
|
|
||||||
|
<!-- Calendar day items. -->
|
||||||
|
<Style TargetType="controls:CalendarDayItemsControl">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="controls:CalendarDayItemsControl">
|
||||||
|
<controls:WinoCalendarPanel
|
||||||
|
x:Name="PART_CalendarPanel"
|
||||||
|
DayModel="{TemplateBinding DayModel}"
|
||||||
|
EventItemMargin="0" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
29
Wino.Calendar/Styles/CalendarItemControlResources.xaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
x:Class="Wino.Calendar.Styles.CalendarItemControlResources"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Calendar.Controls">
|
||||||
|
|
||||||
|
<!-- CalendarItemControl -->
|
||||||
|
<Style TargetType="controls:CalendarItemControl">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="controls:CalendarItemControl">
|
||||||
|
<Grid
|
||||||
|
Margin="1,1,1,1"
|
||||||
|
Background="#cf2b36"
|
||||||
|
BorderBrush="Gray"
|
||||||
|
CornerRadius="3">
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{x:Bind Item.Name, Mode=OneWay}" />
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
12
Wino.Calendar/Styles/CalendarItemControlResources.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Windows.UI.Xaml;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Styles
|
||||||
|
{
|
||||||
|
public sealed partial class CalendarItemControlResources : ResourceDictionary
|
||||||
|
{
|
||||||
|
public CalendarItemControlResources()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Wino.Calendar/Styles/CalendarRenderStyles.xaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<StaticResource x:Key="WinoCalendarViewTodayBackgroundBrush" ResourceKey="SystemControlBackgroundAccentBrush" />
|
||||||
|
|
||||||
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
|
<ResourceDictionary x:Key="Light">
|
||||||
|
<SolidColorBrush x:Key="CalendarSeperatorBrush">#D9D9D9</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="CalendarFieldWorkingHoursBackgroundBrush">#ffffff</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="CalendarFieldWorkingNonHoursBackgroundBrush">#f9f9f9</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="CalendarFieldSelectedBackgroundBrush">#ebebeb</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="WinoCalendarViewBorderBrush">Yellow</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="WinoCalendarViewVisibleDayBackgroundBrush">#dfe4ea</SolidColorBrush>
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
||||||
|
<ResourceDictionary x:Key="Dark">
|
||||||
|
<SolidColorBrush x:Key="CalendarSeperatorBrush">#000000</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="CalendarFieldWorkingHoursBackgroundBrush">#262626</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="CalendarFieldWorkingNonHoursBackgroundBrush">#1a1a1a</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="CalendarFieldSelectedBackgroundBrush">#121212</SolidColorBrush>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="WinoCalendarViewBorderBrush">#3d3d3d</SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="WinoCalendarViewVisibleDayBackgroundBrush">#2f3542</SolidColorBrush>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
93
Wino.Calendar/Styles/DayColumnControl.xaml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Calendar.Controls">
|
||||||
|
|
||||||
|
<!-- Top column header DayColumnControl -->
|
||||||
|
<Style TargetType="controls:DayColumnControl">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="controls:DayColumnControl">
|
||||||
|
<Grid MinHeight="100" MaxHeight="150">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="25" />
|
||||||
|
<RowDefinition Height="7" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Name of the day. Monday, Tuesday etc. at the top. -->
|
||||||
|
<TextBlock
|
||||||
|
x:Name="PART_ColumnHeaderText"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
FontSize="16"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
BorderBrush="{ThemeResource CalendarSeperatorBrush}"
|
||||||
|
BorderThickness="1,1,0,1" />
|
||||||
|
|
||||||
|
<!-- Border for today indication. -->
|
||||||
|
<Border
|
||||||
|
x:Name="PART_IsTodayBorder"
|
||||||
|
Grid.Row="1"
|
||||||
|
Height="5"
|
||||||
|
Margin="2,0,2,0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{ThemeResource SystemAccentColor}"
|
||||||
|
CornerRadius="2"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
|
||||||
|
<!-- Place where full day events go. -->
|
||||||
|
<Grid
|
||||||
|
x:Name="PART_DayDataAreaGrid"
|
||||||
|
Grid.Row="2"
|
||||||
|
Padding="6"
|
||||||
|
BorderBrush="{ThemeResource CalendarSeperatorBrush}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Day number -->
|
||||||
|
<TextBlock x:Name="PART_HeaderDateDayText" FontSize="17" />
|
||||||
|
|
||||||
|
<!-- Extras -->
|
||||||
|
<StackPanel Grid.Column="1" HorizontalAlignment="Right" />
|
||||||
|
|
||||||
|
<!-- Events -->
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
VerticalScrollBarVisibility="Hidden" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup x:Name="TodayOrNotStates">
|
||||||
|
<VisualState x:Name="NotTodayState" />
|
||||||
|
<VisualState x:Name="TodayState">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="PART_IsTodayBorder.Visibility" Value="Visible" />
|
||||||
|
<Setter Target="PART_HeaderDateDayText.Foreground" Value="{ThemeResource SystemAccentColor}" />
|
||||||
|
<Setter Target="PART_HeaderDateDayText.FontWeight" Value="Semibold" />
|
||||||
|
<Setter Target="PART_ColumnHeaderText.FontWeight" Value="Semibold" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
23
Wino.Calendar/Styles/DayHeaderControl.xaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Calendar.Controls">
|
||||||
|
|
||||||
|
<!-- Left day header DayHeaderControl -->
|
||||||
|
<Style TargetType="controls:DayHeaderControl">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="controls:DayHeaderControl">
|
||||||
|
<Grid>
|
||||||
|
<TextBlock
|
||||||
|
x:Name="PART_DayHeaderTextBlock"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
FontSize="12" />
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
128
Wino.Calendar/Styles/WinoCalendarResources.xaml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
x:Class="Wino.Calendar.Styles.WinoCalendarResources"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Calendar.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:WinoCalendarControl.Styles"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:models="using:Wino.Core.Domain.Models.Calendar"
|
||||||
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
|
xmlns:toolkitControls="using:CommunityToolkit.WinUI.Controls">
|
||||||
|
|
||||||
|
<!-- 08:00 or 8 AM/PM on the left etc. -->
|
||||||
|
<DataTemplate x:Key="DayCalendarHourHeaderTemplate" x:DataType="models:DayHeaderRenderModel">
|
||||||
|
<Grid Height="{x:Bind HourHeight}">
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Text="{x:Bind DayHeader}" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- Vertical panel that renders items on canvas. -->
|
||||||
|
<DataTemplate x:Key="DayCalendarItemVerticalRenderTemplate" x:DataType="models:CalendarDayModel">
|
||||||
|
<controls:CalendarDayItemsControl DayModel="{x:Bind}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- Equally distributed days of week representation in FlipView. -->
|
||||||
|
<DataTemplate x:Key="FlipTemplate" x:DataType="models:DayRangeRenderModel">
|
||||||
|
<Grid
|
||||||
|
x:Name="RootGrid"
|
||||||
|
ColumnSpacing="0"
|
||||||
|
RowSpacing="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<ItemsControl
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="50,0,16,0"
|
||||||
|
ItemsSource="{x:Bind CalendarDays}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="models:CalendarDayModel">
|
||||||
|
<controls:DayColumnControl DayModel="{x:Bind}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<toolkitControls:UniformGrid
|
||||||
|
Columns="{Binding CalendarRenderOptions.TotalDayCount}"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Rows="1" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0"
|
||||||
|
Padding="0,0,16,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="50" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Rendering left hour headers. -->
|
||||||
|
<ItemsControl ItemTemplate="{StaticResource DayCalendarHourHeaderTemplate}" ItemsSource="{x:Bind DayHeaders}" />
|
||||||
|
|
||||||
|
<!-- Drawing canvas for timeline. -->
|
||||||
|
<controls:WinoDayTimelineCanvas
|
||||||
|
Grid.Column="1"
|
||||||
|
HalfHourSeperatorColor="{ThemeResource CalendarSeperatorBrush}"
|
||||||
|
PositionerUIElement="{Binding ElementName=RootGrid}"
|
||||||
|
RenderOptions="{x:Bind CalendarRenderOptions}"
|
||||||
|
SelectedCellBackgroundBrush="{ThemeResource CalendarFieldSelectedBackgroundBrush}"
|
||||||
|
SeperatorColor="{ThemeResource CalendarSeperatorBrush}"
|
||||||
|
WorkingHourCellBackgroundColor="{ThemeResource CalendarFieldWorkingHoursBackgroundBrush}" />
|
||||||
|
|
||||||
|
<!-- Each vertical day grids that renders events. -->
|
||||||
|
<ItemsControl
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
ItemTemplate="{StaticResource DayCalendarItemVerticalRenderTemplate}"
|
||||||
|
ItemsSource="{x:Bind CalendarDays}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<toolkitControls:UniformGrid
|
||||||
|
Columns="{Binding CalendarRenderOptions.TotalDayCount}"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Rows="1" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<!-- Default style for WinoCalendarControl -->
|
||||||
|
<Style TargetType="controls:WinoCalendarControl">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="controls:WinoCalendarControl">
|
||||||
|
<controls:WinoCalendarFlipView
|
||||||
|
x:Name="PART_WinoFlipView"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
IsTabStop="False"
|
||||||
|
ItemTemplate="{StaticResource FlipTemplate}"
|
||||||
|
ItemsSource="{TemplateBinding DayRanges}"
|
||||||
|
SelectedIndex="{Binding SelectedFlipViewIndex, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Teaching tip dialog sizing. -->
|
||||||
|
|
||||||
|
<x:Double x:Key="TeachingTipMinWidth">1000</x:Double>
|
||||||
|
<x:Double x:Key="TeachingTipMaxWidth">1000</x:Double>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
12
Wino.Calendar/Styles/WinoCalendarResources.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Windows.UI.Xaml;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Styles
|
||||||
|
{
|
||||||
|
public sealed partial class WinoCalendarResources : ResourceDictionary
|
||||||
|
{
|
||||||
|
public WinoCalendarResources()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Wino.Calendar/Styles/WinoCalendarTypeSelectorControl.xaml
Normal file
46
Wino.Calendar/Styles/WinoCalendarView.xaml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Calendar.Controls">
|
||||||
|
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="CalendarRenderStyles.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
<Style TargetType="controls:WinoCalendarView">
|
||||||
|
<Setter Property="VisibleDateBackground" Value="{ThemeResource WinoCalendarViewVisibleDayBackgroundBrush}" />
|
||||||
|
<Setter Property="TodayBackgroundBrush" Value="{ThemeResource SystemColorControlAccentBrush}" />
|
||||||
|
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="controls:WinoCalendarView">
|
||||||
|
<CalendarView
|
||||||
|
x:Name="PART_CalendarView"
|
||||||
|
BorderBrush="{ThemeResource WinoCalendarViewBorderBrush}"
|
||||||
|
CalendarItemCornerRadius="5"
|
||||||
|
CornerRadius="4"
|
||||||
|
DayItemMargin="0"
|
||||||
|
IsTodayHighlighted="False"
|
||||||
|
SelectionMode="Single">
|
||||||
|
<CalendarView.CalendarViewDayItemStyle>
|
||||||
|
<Style TargetType="CalendarViewDayItem">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="CalendarViewDayItem">
|
||||||
|
<Grid>
|
||||||
|
<Border x:Name="PART_DayViewItemBorder" CornerRadius="5">
|
||||||
|
<ContentPresenter />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</CalendarView.CalendarViewDayItemStyle>
|
||||||
|
</CalendarView>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
19
Wino.Calendar/Styles/WinoDayTimelineCanvas.xaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||||
|
xmlns:controls="using:Wino.Calendar.Controls">
|
||||||
|
|
||||||
|
<!-- Background Timeline Canvas -->
|
||||||
|
<Style TargetType="controls:WinoDayTimelineCanvas">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="controls:WinoDayTimelineCanvas">
|
||||||
|
<canvas:CanvasControl x:Name="PART_InternalCanvas" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using Wino.Calendar.ViewModels;
|
||||||
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Views.Abstract
|
||||||
|
{
|
||||||
|
public class AccountManagementPageAbstract : BasePage<AccountManagementViewModel> { }
|
||||||
|
}
|
||||||
7
Wino.Calendar/Views/Abstract/AppShellAbstract.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using Wino.Calendar.ViewModels;
|
||||||
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Views.Abstract
|
||||||
|
{
|
||||||
|
public abstract class AppShellAbstract : BasePage<AppShellViewModel> { }
|
||||||
|
}
|
||||||
7
Wino.Calendar/Views/Abstract/CalendarPageAbstract.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using Wino.Calendar.ViewModels;
|
||||||
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Views.Abstract
|
||||||
|
{
|
||||||
|
public abstract class CalendarPageAbstract : BasePage<CalendarPageViewModel> { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using Wino.Calendar.ViewModels;
|
||||||
|
using Wino.Core.UWP;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Views.Abstract
|
||||||
|
{
|
||||||
|
public abstract class CalendarSettingsPageAbstract : BasePage<CalendarSettingsPageViewModel> { }
|
||||||
|
}
|
||||||
12
Wino.Calendar/Views/Account/AccountManagementPage.xaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<abstract:AccountManagementPageAbstract
|
||||||
|
x:Class="Wino.Calendar.Views.Account.AccountManagementPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:abstract="using:Wino.Calendar.Views.Abstract"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:Wino.Calendar.Views.Account"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid />
|
||||||
|
</abstract:AccountManagementPageAbstract>
|
||||||
12
Wino.Calendar/Views/Account/AccountManagementPage.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Wino.Calendar.Views.Abstract;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Views.Account
|
||||||
|
{
|
||||||
|
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
|
||||||
|
{
|
||||||
|
public AccountManagementPage()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
251
Wino.Calendar/Views/AppShell.xaml
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<abstract:AppShellAbstract
|
||||||
|
x:Class="Wino.Calendar.Views.AppShell"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:abstract="using:Wino.Calendar.Views.Abstract"
|
||||||
|
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
|
||||||
|
xmlns:calendarControls="using:Wino.Calendar.Controls"
|
||||||
|
xmlns:communityControls="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:coreControls="using:Wino.Core.UWP.Controls"
|
||||||
|
xmlns:coreSelectors="using:Wino.Core.UWP.Selectors"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:Wino.Calendar.Views"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
|
muxc:BackdropMaterial.ApplyToRootOrPageBackground="{ThemeResource UseMica}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Page.Resources>
|
||||||
|
<coreSelectors:NavigationMenuTemplateSelector
|
||||||
|
x:Key="NavigationMenuTemplateSelector"
|
||||||
|
AccountManagementTemplate="{StaticResource ManageAccountsTemplate}"
|
||||||
|
RatingItemTemplate="{StaticResource RatingItemTemplate}"
|
||||||
|
SeperatorTemplate="{StaticResource SeperatorTemplate}"
|
||||||
|
SettingsItemTemplate="{StaticResource SettingsItemTemplate}" />
|
||||||
|
|
||||||
|
<Style
|
||||||
|
x:Key="CalendarNavigationButtonStyle"
|
||||||
|
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||||
|
TargetType="Button">
|
||||||
|
<Setter Property="Margin" Value="0,4,0,0" />
|
||||||
|
<Setter Property="Padding" Value="8,4,8,6" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Width" Value="40" />
|
||||||
|
</Style>
|
||||||
|
</Page.Resources>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
x:Name="RootGrid"
|
||||||
|
Padding="0"
|
||||||
|
ColumnSpacing="0"
|
||||||
|
RowSpacing="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="48" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="48" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- CoreWindowText="{x:Bind ViewModel.StatePersistenceService.CoreWindowTitle, Mode=OneWay}" -->
|
||||||
|
<coreControls:WinoAppTitleBar
|
||||||
|
x:Name="RealAppBar"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
BackButtonClicked="BackButtonClicked"
|
||||||
|
Canvas.ZIndex="150"
|
||||||
|
ConnectionStatus="{x:Bind ViewModel.ActiveConnectionStatus, Mode=OneWay}"
|
||||||
|
CoreWindowText="Wino Calendar"
|
||||||
|
IsBackButtonVisible="{x:Bind ViewModel.StatePersistenceService.IsBackButtonVisible, Mode=OneWay}"
|
||||||
|
IsNavigationPaneOpen="{x:Bind MainNavigationView.IsPaneOpen, Mode=TwoWay}"
|
||||||
|
NavigationViewDisplayMode="{x:Bind MainNavigationView.DisplayMode, Mode=OneWay}"
|
||||||
|
OpenPaneLength="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=OneWay}"
|
||||||
|
ReconnectCommand="{x:Bind ViewModel.ReconnectServerCommand}"
|
||||||
|
ShrinkShellContentOnExpansion="False"
|
||||||
|
SystemReserved="180">
|
||||||
|
<coreControls:WinoAppTitleBar.ShellFrameContent>
|
||||||
|
<Grid Margin="4,0,0,0" ColumnSpacing="12">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
x:Name="DragArea"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
Background="Transparent" />
|
||||||
|
|
||||||
|
<Grid ColumnSpacing="12">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="3*" />
|
||||||
|
<ColumnDefinition Width="7*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<AutoSuggestBox
|
||||||
|
x:Name="SearchBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
PlaceholderText="Search" />
|
||||||
|
|
||||||
|
<StackPanel
|
||||||
|
x:Name="NavigationTitleStack"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,0,12,4"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="6">
|
||||||
|
<Button
|
||||||
|
x:Name="PreviousDateButton"
|
||||||
|
Click="PreviousDateClicked"
|
||||||
|
Style="{StaticResource CalendarNavigationButtonStyle}">
|
||||||
|
<PathIcon x:Name="PreviousDateButtonPathIcon" Data="F1 M 8.72 18.599998 C 8.879999 18.733334 9.059999 18.799999 9.26 18.799999 C 9.459999 18.799999 9.633332 18.719999 9.78 18.559999 C 9.926666 18.4 10 18.219999 10 18.019999 C 10 17.82 9.92 17.653332 9.76 17.52 L 4.52 12.559999 L 17.24 12.559999 C 17.453333 12.559999 17.633331 12.486667 17.779999 12.339999 C 17.926666 12.193334 18 12.013333 18 11.799999 C 18 11.586666 17.926666 11.406667 17.779999 11.259999 C 17.633331 11.113333 17.453333 11.039999 17.24 11.039999 L 4.52 11.039999 L 9.76 6.08 C 9.973333 5.893333 10.046666 5.653332 9.98 5.359999 C 9.913333 5.066666 9.74 4.880001 9.46 4.799999 C 9.179999 4.720001 8.933332 4.786667 8.72 5 L 2.32 11.08 C 2.16 11.24 2.053333 11.426666 2 11.639999 C 1.973333 11.746666 1.973333 11.853333 2 11.959999 C 2.053333 12.173333 2.16 12.360001 2.32 12.52 Z " />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="NextDateButton"
|
||||||
|
Click="NextDateClicked"
|
||||||
|
Style="{StaticResource CalendarNavigationButtonStyle}">
|
||||||
|
<PathIcon x:Name="NextDateButtonPathIcon" Data="F1 M 11.28 5 C 11.12 4.866667 10.94 4.806667 10.74 4.82 C 10.539999 4.833334 10.366666 4.913334 10.219999 5.059999 C 10.073333 5.206665 10 5.379999 10 5.58 C 10 5.779999 10.08 5.946667 10.24 6.08 L 15.48 11.039999 L 2.76 11.039999 C 2.546667 11.039999 2.366667 11.113333 2.22 11.259999 C 2.073333 11.406667 2 11.586666 2 11.799999 C 2 12.013333 2.073333 12.193334 2.22 12.339999 C 2.366667 12.486667 2.546667 12.559999 2.76 12.559999 L 15.48 12.559999 L 10.24 17.52 C 10.026667 17.706665 9.953333 17.946667 10.02 18.24 C 10.086666 18.533333 10.259999 18.719999 10.54 18.799999 C 10.82 18.879999 11.066667 18.813334 11.28 18.599998 L 17.68 12.52 C 17.84 12.360001 17.946667 12.173333 18 11.959999 C 18 11.853333 18 11.746666 18 11.639999 C 17.946667 11.426666 17.84 11.24 17.68 11.08 Z " />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<calendarControls:CustomCalendarFlipView
|
||||||
|
x:Name="DayHeaderNavigationItemsFlipView"
|
||||||
|
MaxHeight="30"
|
||||||
|
Margin="8,4,0,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
Background="Transparent"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="Normal"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
ItemsSource="{x:Bind ViewModel.DateNavigationHeaderItems}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.SelectedDateNavigationHeaderIndex, Mode=OneWay}">
|
||||||
|
<FlipView.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="18"
|
||||||
|
Style="{StaticResource BodyTextBlockStyle}"
|
||||||
|
Text="{Binding}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</FlipView.ItemTemplate>
|
||||||
|
</calendarControls:CustomCalendarFlipView>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<calendarControls:WinoCalendarTypeSelectorControl
|
||||||
|
Grid.Column="2"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
DisplayDayCount="{x:Bind ViewModel.StatePersistenceService.DayDisplayCount, Mode=OneWay}"
|
||||||
|
SelectedType="{x:Bind ViewModel.StatePersistenceService.CalendarDisplayType, Mode=TwoWay}"
|
||||||
|
TodayClickedCommand="{x:Bind ViewModel.TodayClickedCommand}" />
|
||||||
|
</Grid>
|
||||||
|
</coreControls:WinoAppTitleBar.ShellFrameContent>
|
||||||
|
</coreControls:WinoAppTitleBar>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Background="{ThemeResource WinoApplicationBackgroundColor}"
|
||||||
|
IsHitTestVisible="False" />
|
||||||
|
|
||||||
|
<muxc:NavigationView
|
||||||
|
x:Name="MainNavigationView"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
AlwaysShowHeader="True"
|
||||||
|
FooterMenuItemsSource="{x:Bind ViewModel.FooterItems, Mode=OneWay}"
|
||||||
|
IsBackButtonVisible="Collapsed"
|
||||||
|
IsPaneOpen="{x:Bind ViewModel.PreferencesService.IsNavigationPaneOpened, Mode=TwoWay}"
|
||||||
|
IsPaneToggleButtonVisible="False"
|
||||||
|
IsSettingsVisible="False"
|
||||||
|
IsTabStop="True"
|
||||||
|
IsTitleBarAutoPaddingEnabled="False"
|
||||||
|
MenuItemTemplateSelector="{StaticResource NavigationMenuTemplateSelector}"
|
||||||
|
MenuItemsSource="{x:Bind ViewModel.MenuItems, Mode=OneWay}"
|
||||||
|
OpenPaneLength="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=TwoWay}"
|
||||||
|
SelectedItem="{x:Bind ViewModel.SelectedMenuItem, Mode=TwoWay}">
|
||||||
|
<muxc:NavigationView.ContentTransitions>
|
||||||
|
<TransitionCollection>
|
||||||
|
<AddDeleteThemeTransition />
|
||||||
|
</TransitionCollection>
|
||||||
|
</muxc:NavigationView.ContentTransitions>
|
||||||
|
|
||||||
|
<muxc:NavigationView.PaneHeader>
|
||||||
|
<calendarControls:WinoCalendarView
|
||||||
|
x:Name="CalendarView"
|
||||||
|
Margin="2,0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
DateClickedCommand="{x:Bind ViewModel.DateClickedCommand}"
|
||||||
|
HighlightedDateRange="{x:Bind ViewModel.HighlightedDateRange, Mode=OneWay}" />
|
||||||
|
</muxc:NavigationView.PaneHeader>
|
||||||
|
<muxc:NavigationView.Content>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<Frame
|
||||||
|
x:Name="ShellFrame"
|
||||||
|
Padding="0,0,7,7"
|
||||||
|
IsNavigationStackEnabled="False"
|
||||||
|
Navigated="ShellFrameContentNavigated">
|
||||||
|
<Frame.ContentTransitions>
|
||||||
|
<TransitionCollection>
|
||||||
|
<PopupThemeTransition />
|
||||||
|
</TransitionCollection>
|
||||||
|
</Frame.ContentTransitions>
|
||||||
|
</Frame>
|
||||||
|
|
||||||
|
<!-- InfoBar -->
|
||||||
|
<coreControls:WinoInfoBar
|
||||||
|
x:Name="ShellInfoBar"
|
||||||
|
MaxWidth="700"
|
||||||
|
Margin="0,60,25,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="False" />
|
||||||
|
</Grid>
|
||||||
|
</muxc:NavigationView.Content>
|
||||||
|
</muxc:NavigationView>
|
||||||
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup x:Name="LowResolutionStates">
|
||||||
|
<VisualState x:Name="BigScreen">
|
||||||
|
<VisualState.StateTriggers>
|
||||||
|
<AdaptiveTrigger MinWindowWidth="1200" />
|
||||||
|
</VisualState.StateTriggers>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="SmallScreen">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="NavigationTitleStack.Visibility" Value="Collapsed" />
|
||||||
|
<Setter Target="SearchBox.(Grid.ColumnSpan)" Value="2" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
<VisualState.StateTriggers>
|
||||||
|
<AdaptiveTrigger MinWindowWidth="0" />
|
||||||
|
</VisualState.StateTriggers>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
|
||||||
|
<VisualStateGroup x:Name="CalendarOrientationStates">
|
||||||
|
<VisualState x:Name="HorizontalCalendar" />
|
||||||
|
<VisualState x:Name="VerticalCalendar">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="DayHeaderNavigationItemsFlipView.ItemsPanel">
|
||||||
|
<Setter.Value>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel Orientation="Vertical" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Target="PreviousDateButtonPathIcon.Data" Value="F1 M 16.799999 13.079999 C 16.933332 12.92 16.993332 12.74 16.98 12.539999 C 16.966665 12.34 16.886665 12.166667 16.74 12.02 C 16.593334 11.873333 16.42 11.799999 16.219999 11.799999 C 16.02 11.799999 15.853333 11.879999 15.719999 12.039999 L 10.76 17.279999 L 10.76 4.559999 C 10.76 4.346666 10.686666 4.166668 10.54 4.02 C 10.393332 3.873333 10.213333 3.799999 10 3.799999 C 9.786666 3.799999 9.606666 3.873333 9.46 4.02 C 9.313333 4.166668 9.24 4.346666 9.24 4.559999 L 9.24 17.279999 L 4.28 12.039999 C 4.146667 11.879999 3.98 11.799999 3.78 11.799999 C 3.58 11.799999 3.4 11.873333 3.24 12.02 C 3.08 12.166667 3 12.34 3 12.539999 C 3 12.74 3.066667 12.92 3.2 13.079999 L 9.28 19.48 C 9.439999 19.639999 9.626666 19.746666 9.84 19.799999 C 9.946667 19.799999 10.053333 19.799999 10.16 19.799999 C 10.373333 19.746666 10.559999 19.639999 10.719999 19.48 Z " />
|
||||||
|
<Setter Target="NextDateButtonPathIcon.Data" Value="F1 M 3.2 10.52 C 2.986666 10.733333 2.92 10.98 3 11.259999 C 3.08 11.54 3.266666 11.713333 3.56 11.78 C 3.853333 11.846666 4.093333 11.773333 4.28 11.559999 L 9.24 6.32 L 9.24 19.039999 C 9.24 19.253332 9.313333 19.433332 9.46 19.58 C 9.606666 19.726665 9.786666 19.799999 10 19.799999 C 10.213333 19.799999 10.393332 19.726665 10.54 19.58 C 10.686666 19.433332 10.76 19.253332 10.76 19.039999 L 10.76 6.32 L 15.719999 11.559999 C 15.906666 11.773333 16.139999 11.846666 16.42 11.78 C 16.700001 11.713333 16.886665 11.54 16.98 11.259999 C 17.073332 10.98 17.013332 10.733333 16.799999 10.52 L 10.719999 4.119999 C 10.559999 3.959999 10.373333 3.853333 10.16 3.799999 C 10.053333 3.799999 9.946667 3.799999 9.84 3.799999 C 9.626666 3.853333 9.439999 3.959999 9.28 4.119999 Z " />
|
||||||
|
</VisualState.Setters>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
</Grid>
|
||||||
|
</abstract:AppShellAbstract>
|
||||||
57
Wino.Calendar/Views/AppShell.xaml.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Wino.Calendar.Views.Abstract;
|
||||||
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Calendar.Views
|
||||||
|
{
|
||||||
|
public sealed partial class AppShell : AppShellAbstract,
|
||||||
|
IRecipient<GoToCalendarDayMessage>
|
||||||
|
{
|
||||||
|
private const string STATE_HorizontalCalendar = "HorizontalCalendar";
|
||||||
|
private const string STATE_VerticalCalendar = "VerticalCalendar";
|
||||||
|
|
||||||
|
public Frame GetShellFrame() => ShellFrame;
|
||||||
|
public AppShell()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Window.Current.SetTitleBar(DragArea);
|
||||||
|
|
||||||
|
ViewModel.DisplayTypeChanged += CalendarDisplayTypeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalendarDisplayTypeChanged(object sender, Core.Domain.Enums.CalendarDisplayType e)
|
||||||
|
{
|
||||||
|
// 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 ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, Windows.UI.Xaml.RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(GoToCalendarDayMessage message)
|
||||||
|
{
|
||||||
|
CalendarView.GoToDay(message.DateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
|
||||||
|
|
||||||
|
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
75
Wino.Calendar/Views/CalendarPage.xaml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<abstract:CalendarPageAbstract
|
||||||
|
x:Class="Wino.Calendar.Views.CalendarPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:abstract="using:Wino.Calendar.Views.Abstract"
|
||||||
|
xmlns:calendarControls="using:Wino.Calendar.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:Wino.Calendar.Views"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<!--<Page.Resources>
|
||||||
|
<x:Double x:Key="TeachingTipMinWidth">500</x:Double>
|
||||||
|
<x:Double x:Key="TeachingTipMaxWidth">500</x:Double>
|
||||||
|
</Page.Resources>-->
|
||||||
|
<Border
|
||||||
|
Margin="0,0,7,7"
|
||||||
|
Background="{ThemeResource WinoContentZoneBackgroud}"
|
||||||
|
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="7">
|
||||||
|
<Grid>
|
||||||
|
<calendarControls:WinoCalendarControl
|
||||||
|
x:Name="CalendarControl"
|
||||||
|
DayRanges="{x:Bind ViewModel.DayRanges}"
|
||||||
|
IsHitTestVisible="{x:Bind ViewModel.IsCalendarEnabled, Mode=OneWay}"
|
||||||
|
SelectedFlipViewDayRange="{x:Bind ViewModel.SelectedDayRange, Mode=TwoWay}"
|
||||||
|
SelectedFlipViewIndex="{x:Bind ViewModel.SelectedDateRangeIndex, Mode=TwoWay}"
|
||||||
|
TimelineCellSelected="CellSelected"
|
||||||
|
TimelineCellUnselected="CellUnselected" />
|
||||||
|
|
||||||
|
<Canvas x:Name="CalendarOverlayCanvas" IsHitTestVisible="False">
|
||||||
|
<!-- Invisible target UI element for teaching tip display. -->
|
||||||
|
<Grid
|
||||||
|
x:Name="TeachingTipPositionerGrid"
|
||||||
|
Background="Transparent"
|
||||||
|
Visibility="Visible" />
|
||||||
|
|
||||||
|
<!-- Single teaching tip to display create event dialog. -->
|
||||||
|
<muxc:TeachingTip
|
||||||
|
x:Name="NewEventTip"
|
||||||
|
Width="500"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
Closed="CreateEventTipClosed"
|
||||||
|
IsOpen="False"
|
||||||
|
PreferredPlacement="Right"
|
||||||
|
Target="{x:Bind TeachingTipPositionerGrid}">
|
||||||
|
<muxc:TeachingTip.Content>
|
||||||
|
<Grid Margin="0,24" RowSpacing="12">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TimePicker x:Name="EventTimePicker" ClockIdentifier="24HourClock" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Create events dialog -->
|
||||||
|
<Button
|
||||||
|
x:Name="AddEvent"
|
||||||
|
Grid.Row="1"
|
||||||
|
Click="AddEventClicked"
|
||||||
|
Content="Add Event" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</muxc:TeachingTip.Content>
|
||||||
|
</muxc:TeachingTip>
|
||||||
|
</Canvas>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</abstract:CalendarPageAbstract>
|
||||||
|
|
||||||