Initial integration.
This commit is contained in:
+26
-26
@@ -7,13 +7,13 @@
|
||||
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Diagnostics" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.TabbedCommandBar" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.2.251219" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.251219" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.251219" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.251219" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.TabbedCommandBar" Version="8.2.251219" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.2.251219" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.251219" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250926-build.2293" />
|
||||
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.WinUI.DependencyPropertyGenerator" Version="0.1.250926-build.2293" />
|
||||
@@ -23,22 +23,22 @@
|
||||
<PackageVersion Include="Ical.Net" Version="4.3.1" />
|
||||
<PackageVersion Include="IsExternalInit" Version="1.0.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Graph" Version="5.96.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Graph" Version="5.99.0" />
|
||||
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client" Version="4.79.1" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.79.1" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.79.1" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client" Version="4.79.2" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.79.2" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.79.2" />
|
||||
<PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
|
||||
<PackageVersion Include="MimeKit" Version="4.14.0" />
|
||||
<PackageVersion Include="morelinq" Version="4.4.0" />
|
||||
<PackageVersion Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageVersion Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageVersion Include="NodaTime" Version="3.2.2" />
|
||||
<PackageVersion Include="Sentry.Serilog" Version="5.16.2" />
|
||||
<PackageVersion Include="NodaTime" Version="3.2.3" />
|
||||
<PackageVersion Include="Sentry.Serilog" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog" Version="4.3.0" />
|
||||
<PackageVersion Include="Serilog.Exceptions" Version="8.4.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||
@@ -46,24 +46,24 @@
|
||||
<PackageVersion Include="Serilog.Sinks.ApplicationInsights" Version="4.0.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="3.119.1" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.10.196-beta" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.1" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||
<PackageVersion Include="H.NotifyIcon.WinUI" Version="2.3.2" />
|
||||
<PackageVersion Include="H.NotifyIcon.WinUI" Version="2.4.1" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Google.Apis.Auth" Version="1.72.0" />
|
||||
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.72.0.3953" />
|
||||
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.70.0.3833" />
|
||||
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.69.0.3785" />
|
||||
<PackageVersion Include="Google.Apis.Auth" Version="1.73.0" />
|
||||
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.73.0.3993" />
|
||||
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.73.0.3987" />
|
||||
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.72.0.3973" />
|
||||
<PackageVersion Include="HtmlKit" Version="1.2.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.14.1" />
|
||||
<PackageVersion Include="TimePeriodLibrary.NET" Version="2.1.6" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.1.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.1" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.250930001-experimental1" />
|
||||
<PackageVersion Include="WinUIEx" Version="2.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class AccountDetailsPageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public AccountProviderDetailViewModel Account { get; private set; }
|
||||
public ICalendarDialogService CalendarDialogService { get; }
|
||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||
|
||||
public AccountDetailsPageViewModel(ICalendarDialogService calendarDialogService, IAccountService accountService, IAccountCalendarStateService accountCalendarStateService)
|
||||
{
|
||||
CalendarDialogService = calendarDialogService;
|
||||
_accountService = accountService;
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void EditAccountDetails()
|
||||
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsEditAccountDetails_Title, WinoPage.EditAccountDetailsPage, Account));
|
||||
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
||||
{
|
||||
private readonly IProviderService _providerService;
|
||||
|
||||
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;
|
||||
_providerService = providerService;
|
||||
}
|
||||
|
||||
public ICalendarDialogService CalendarDialogService { get; }
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
await InitializeAccountsAsync();
|
||||
}
|
||||
|
||||
public override async Task InitializeAccountsAsync()
|
||||
{
|
||||
Accounts.Clear();
|
||||
|
||||
var accounts = await AccountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var accountDetails = GetAccountProviderDetails(account);
|
||||
|
||||
Accounts.Add(accountDetails);
|
||||
}
|
||||
});
|
||||
|
||||
await ManageStorePurchasesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddNewAccountAsync()
|
||||
{
|
||||
if (IsAccountCreationBlocked)
|
||||
{
|
||||
var isPurchaseClicked = await DialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_AccountLimitMessage, Translator.DialogMessage_AccountLimitTitle, Translator.Buttons_Purchase);
|
||||
|
||||
if (!isPurchaseClicked) return;
|
||||
|
||||
await PurchaseUnlimitedAccountAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var availableProviders = _providerService.GetAvailableProviders();
|
||||
|
||||
var accountCreationDialogResult = await DialogService.ShowAccountProviderSelectionDialogAsync(availableProviders);
|
||||
|
||||
if (accountCreationDialogResult == null) return;
|
||||
|
||||
var accountCreationCancellationTokenSource = new CancellationTokenSource();
|
||||
var accountCreationDialog = CalendarDialogService.GetAccountCreationDialog(accountCreationDialogResult);
|
||||
|
||||
await accountCreationDialog.ShowDialogAsync(accountCreationCancellationTokenSource);
|
||||
await Task.Delay(500);
|
||||
|
||||
// For OAuth authentications, we just generate token and assign it to the MailAccount.
|
||||
|
||||
var createdAccount = new MailAccount()
|
||||
{
|
||||
ProviderType = accountCreationDialogResult.ProviderType,
|
||||
Name = accountCreationDialogResult.AccountName,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var tokenInformationResponse = await WinoServerConnectionManager
|
||||
.GetResponseAsync<TokenInformationEx, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
|
||||
createdAccount,
|
||||
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
|
||||
|
||||
if (accountCreationDialog.State == AccountCreationDialogState.Canceled)
|
||||
throw new AccountSetupCanceledException();
|
||||
|
||||
tokenInformationResponse.ThrowIfFailed();
|
||||
|
||||
await AccountService.CreateAccountAsync(createdAccount, null);
|
||||
|
||||
// Sync profile information if supported.
|
||||
if (createdAccount.IsProfileInfoSyncSupported)
|
||||
{
|
||||
// Start profile information synchronization.
|
||||
// It's only available for Outlook and Gmail synchronizers.
|
||||
|
||||
var profileSyncOptions = new MailSynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = MailSynchronizationType.UpdateProfile
|
||||
};
|
||||
|
||||
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
||||
|
||||
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
||||
|
||||
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
||||
|
||||
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
|
||||
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
|
||||
|
||||
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
|
||||
}
|
||||
|
||||
accountCreationDialog.State = AccountCreationDialogState.FetchingEvents;
|
||||
|
||||
// Start synchronizing events.
|
||||
var synchronizationOptions = new CalendarSynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
};
|
||||
|
||||
var synchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(new NewCalendarSynchronizationRequested(synchronizationOptions, SynchronizationSource.Client));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -23,7 +23,7 @@ using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||
public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
||||
IRecipient<VisibleDateRangeChangedMessage>,
|
||||
IRecipient<CalendarEnableStatusChangedMessage>,
|
||||
IRecipient<NavigateManageAccountsRequested>,
|
||||
@@ -69,7 +69,7 @@ public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||
// For updating account calendars asynchronously.
|
||||
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
|
||||
|
||||
public AppShellViewModel(IPreferencesService preferencesService,
|
||||
public CalendarAppShellViewModel(IPreferencesService preferencesService,
|
||||
IStatePersistanceService statePersistanceService,
|
||||
IAccountService accountService,
|
||||
ICalendarService calendarService,
|
||||
@@ -223,7 +223,7 @@ public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
}, SynchronizationSource.Client);
|
||||
});
|
||||
|
||||
Messenger.Send(t);
|
||||
}
|
||||
@@ -711,7 +711,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedStartTimeStringChanged(string newValue)
|
||||
partial void OnSelectedStartTimeStringChanged(string oldValue, string newValue)
|
||||
{
|
||||
var parsedTime = CurrentSettings.GetTimeSpan(newValue);
|
||||
|
||||
@@ -725,7 +725,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedEndTimeStringChanged(string newValue)
|
||||
partial void OnSelectedEndTimeStringChanged(string oldValue, string newValue)
|
||||
{
|
||||
var parsedTime = CurrentSettings.GetTimeSpan(newValue);
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Wino.Core;
|
||||
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public static class CalendarViewModelContainerSetup
|
||||
{
|
||||
public static void RegisterCalendarViewModelServices(this IServiceCollection services)
|
||||
{
|
||||
services.RegisterCoreServices();
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
_isExternalPropChangeBlocked = false;
|
||||
}
|
||||
|
||||
partial void OnIsCheckedStateChanged(bool? newValue)
|
||||
partial void OnIsCheckedStateChanged(bool? oldValue, bool? newValue)
|
||||
{
|
||||
if (_isExternalPropChangeBlocked) return;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Platforms>x86;x64;arm64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
|
||||
|
||||
@@ -30,6 +30,18 @@ public class CalendarItem : ICalendarItem
|
||||
public TimeSpan StartDateOffset { get; set; }
|
||||
public TimeSpan EndDateOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IANA timezone identifier for the start time (e.g., "America/New_York", "Europe/London").
|
||||
/// If null or empty, UTC is assumed.
|
||||
/// </summary>
|
||||
public string StartTimeZone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IANA timezone identifier for the end time (e.g., "America/New_York", "Europe/London").
|
||||
/// If null or empty, UTC is assumed.
|
||||
/// </summary>
|
||||
public string EndTimeZone { get; set; }
|
||||
|
||||
private ITimePeriod _period;
|
||||
public ITimePeriod Period
|
||||
{
|
||||
@@ -170,10 +182,70 @@ public class CalendarItem : ICalendarItem
|
||||
HtmlLink = HtmlLink,
|
||||
StartDateOffset = StartDateOffset,
|
||||
EndDateOffset = EndDateOffset,
|
||||
StartTimeZone = StartTimeZone,
|
||||
EndTimeZone = EndTimeZone,
|
||||
RemoteEventId = RemoteEventId,
|
||||
IsHidden = IsHidden,
|
||||
IsLocked = IsLocked,
|
||||
IsOccurrence = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start date as a DateTimeOffset with the correct timezone.
|
||||
/// If StartTimeZone is available, uses it to calculate the offset.
|
||||
/// Otherwise, uses the stored StartDateOffset or assumes UTC.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public DateTimeOffset StartDateTimeOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(StartTimeZone))
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(StartTimeZone);
|
||||
var offset = timeZoneInfo.GetUtcOffset(StartDate);
|
||||
return new DateTimeOffset(StartDate, offset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If timezone lookup fails, fall back to stored offset
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to stored offset, or UTC if offset is zero
|
||||
return new DateTimeOffset(StartDate, StartDateOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end date as a DateTimeOffset with the correct timezone.
|
||||
/// If EndTimeZone is available, uses it to calculate the offset.
|
||||
/// Otherwise, uses the stored EndDateOffset or assumes UTC.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public DateTimeOffset EndDateTimeOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(EndTimeZone))
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(EndTimeZone);
|
||||
var offset = timeZoneInfo.GetUtcOffset(EndDate);
|
||||
return new DateTimeOffset(EndDate, offset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If timezone lookup fails, fall back to stored offset
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to stored offset, or UTC if offset is zero
|
||||
return new DateTimeOffset(EndDate, EndDateOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
public enum NavigationReferenceFrame
|
||||
{
|
||||
ShellFrame,
|
||||
InnerShellFrame,
|
||||
RenderingFrame
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum WinoApplicationMode
|
||||
{
|
||||
Mail,
|
||||
Calendar
|
||||
}
|
||||
@@ -64,6 +64,12 @@ public interface ISynchronizationManager
|
||||
Task<MailSynchronizationResult> SynchronizeProfileAsync(Guid accountId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Handles calendar synchronization for the given account.
|
||||
/// </summary>
|
||||
Task<CalendarSynchronizationResult> SynchronizeCalendarAsync(CalendarSynchronizationOptions options,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a MIME message for the given mail item.
|
||||
/// </summary>
|
||||
|
||||
@@ -8,9 +8,10 @@ public interface INavigationService
|
||||
{
|
||||
bool Navigate(WinoPage page,
|
||||
object parameter = null,
|
||||
NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame,
|
||||
NavigationReferenceFrame frame = NavigationReferenceFrame.InnerShellFrame,
|
||||
NavigationTransitionType transition = NavigationTransitionType.None);
|
||||
|
||||
Type GetPageType(WinoPage winoPage);
|
||||
void GoBack();
|
||||
bool ChangeApplicationMode(WinoApplicationMode mode);
|
||||
}
|
||||
|
||||
@@ -177,6 +177,15 @@ public static class GoogleIntegratorExtensions
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the timezone string from EventDateTime.
|
||||
/// Returns null for all-day events or if timezone is not specified.
|
||||
/// </summary>
|
||||
public static string GetEventTimeZone(EventDateTime eventDateTime)
|
||||
{
|
||||
return eventDateTime?.TimeZone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545.
|
||||
/// </summary>
|
||||
|
||||
@@ -229,23 +229,37 @@ public static class OutlookIntegratorExtensions
|
||||
|
||||
public static DateTimeOffset GetDateTimeOffsetFromDateTimeTimeZone(DateTimeTimeZone dateTimeTimeZone)
|
||||
{
|
||||
if (dateTimeTimeZone == null || string.IsNullOrEmpty(dateTimeTimeZone.DateTime) || string.IsNullOrEmpty(dateTimeTimeZone.TimeZone))
|
||||
if (dateTimeTimeZone == null || string.IsNullOrEmpty(dateTimeTimeZone.DateTime))
|
||||
{
|
||||
throw new ArgumentException("DateTimeTimeZone is null or empty.");
|
||||
throw new ArgumentException("DateTimeTimeZone or DateTime is null or empty.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Parse the DateTime string
|
||||
if (DateTime.TryParse(dateTimeTimeZone.DateTime, out DateTime parsedDateTime))
|
||||
if (!DateTime.TryParse(dateTimeTimeZone.DateTime, out DateTime parsedDateTime))
|
||||
{
|
||||
throw new ArgumentException("DateTime string is not in a valid format.");
|
||||
}
|
||||
|
||||
// If no timezone is provided, assume UTC
|
||||
if (string.IsNullOrEmpty(dateTimeTimeZone.TimeZone))
|
||||
{
|
||||
return new DateTimeOffset(parsedDateTime, TimeSpan.Zero);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Get TimeZoneInfo to get the offset
|
||||
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(dateTimeTimeZone.TimeZone);
|
||||
TimeSpan offset = timeZoneInfo.GetUtcOffset(parsedDateTime);
|
||||
return new DateTimeOffset(parsedDateTime, offset);
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("DateTime string is not in a valid format.");
|
||||
catch (TimeZoneNotFoundException)
|
||||
{
|
||||
// If timezone is not found, assume UTC as fallback
|
||||
return new DateTimeOffset(parsedDateTime, TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -102,6 +102,10 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
||||
DurationInSeconds = totalDurationInSeconds,
|
||||
Location = string.IsNullOrEmpty(calendarEvent.Location) ? parentRecurringEvent.Location : calendarEvent.Location,
|
||||
|
||||
// Store timezone information
|
||||
StartTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.Start) ?? parentRecurringEvent.StartTimeZone,
|
||||
EndTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.End) ?? parentRecurringEvent.EndTimeZone,
|
||||
|
||||
// Leave it empty if it's not populated.
|
||||
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent) == null ? string.Empty : GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
|
||||
Status = GetStatus(calendarEvent.Status),
|
||||
@@ -137,6 +141,11 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
|
||||
EndDateOffset = eventEndDateTimeOffset.Value.Offset,
|
||||
DurationInSeconds = totalDurationInSeconds,
|
||||
Location = calendarEvent.Location,
|
||||
|
||||
// Store timezone information from Google Calendar event
|
||||
StartTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.Start),
|
||||
EndTimeZone = GoogleIntegratorExtensions.GetEventTimeZone(calendarEvent.End),
|
||||
|
||||
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
|
||||
Status = GetStatus(calendarEvent.Status),
|
||||
Title = calendarEvent.Summary,
|
||||
|
||||
@@ -69,6 +69,11 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
|
||||
savingItem.EndDateOffset = eventEndDateTimeOffset.Offset;
|
||||
savingItem.DurationInSeconds = durationInSeconds;
|
||||
|
||||
// Store the timezone information from the event
|
||||
// This preserves the original timezone from Outlook, allowing proper reconstruction later
|
||||
savingItem.StartTimeZone = calendarEvent.Start?.TimeZone;
|
||||
savingItem.EndTimeZone = calendarEvent.End?.TimeZone;
|
||||
|
||||
savingItem.Title = calendarEvent.Subject;
|
||||
savingItem.Description = calendarEvent.Body?.Content;
|
||||
savingItem.Location = calendarEvent.Location?.DisplayName;
|
||||
@@ -103,6 +108,34 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
|
||||
savingItem.OrganizerDisplayName = calendarEvent.Organizer?.EmailAddress?.Name;
|
||||
savingItem.IsHidden = false;
|
||||
|
||||
// Set timestamps
|
||||
if (calendarEvent.CreatedDateTime.HasValue)
|
||||
savingItem.CreatedAt = calendarEvent.CreatedDateTime.Value;
|
||||
|
||||
if (calendarEvent.LastModifiedDateTime.HasValue)
|
||||
savingItem.UpdatedAt = calendarEvent.LastModifiedDateTime.Value;
|
||||
|
||||
// Set visibility
|
||||
if (calendarEvent.Sensitivity != null)
|
||||
{
|
||||
savingItem.Visibility = calendarEvent.Sensitivity.Value switch
|
||||
{
|
||||
Sensitivity.Normal => CalendarItemVisibility.Public,
|
||||
Sensitivity.Personal => CalendarItemVisibility.Private,
|
||||
Sensitivity.Private => CalendarItemVisibility.Private,
|
||||
Sensitivity.Confidential => CalendarItemVisibility.Confidential,
|
||||
_ => CalendarItemVisibility.Public
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
savingItem.Visibility = CalendarItemVisibility.Public;
|
||||
}
|
||||
|
||||
// Set IsLocked based on whether the user is the organizer
|
||||
// Read-only events are those where the current user is not the organizer
|
||||
savingItem.IsLocked = calendarEvent.IsOrganizer.HasValue && !calendarEvent.IsOrganizer.Value;
|
||||
|
||||
if (calendarEvent.ResponseStatus?.Response != null)
|
||||
{
|
||||
switch (calendarEvent.ResponseStatus.Response.Value)
|
||||
|
||||
@@ -299,6 +299,56 @@ public class SynchronizationManager : ISynchronizationManager
|
||||
return await SynchronizeMailAsync(options, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles calendar synchronization for the given account.
|
||||
/// </summary>
|
||||
/// <param name="options">Calendar synchronization options</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Synchronization result</returns>
|
||||
public async Task<CalendarSynchronizationResult> SynchronizeCalendarAsync(CalendarSynchronizationOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
var synchronizer = await GetOrCreateSynchronizerAsync(options.AccountId);
|
||||
if (synchronizer == null)
|
||||
{
|
||||
_logger.Error("Could not find or create synchronizer for account {AccountId}", options.AccountId);
|
||||
return CalendarSynchronizationResult.Failed;
|
||||
}
|
||||
|
||||
_logger.Information("Starting calendar synchronization for account {AccountId} with type {SyncType}",
|
||||
options.AccountId, options.Type);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await synchronizer.SynchronizeCalendarEventsAsync(options, cancellationToken);
|
||||
|
||||
_logger.Information("Calendar synchronization completed for account {AccountId} with state {State}",
|
||||
options.AccountId, result.CompletedState);
|
||||
|
||||
// TODO: Create notifications for new calendar events when INotificationBuilder supports it
|
||||
// if (result.DownloadedEvents?.Any() ?? false)
|
||||
// await _notificationBuilder.CreateCalendarNotificationsAsync(result.DownloadedEvents);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (AuthenticationAttentionException authEx)
|
||||
{
|
||||
_logger.Warning("Account {AccountId} requires attention due to authentication issues", options.AccountId);
|
||||
|
||||
// Create app notification for authentication attention
|
||||
_notificationBuilder.CreateAttentionRequiredNotification(authEx.Account);
|
||||
|
||||
return CalendarSynchronizationResult.Failed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Calendar synchronization failed for account {AccountId}", options.AccountId);
|
||||
return CalendarSynchronizationResult.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a MIME message for the given mail item.
|
||||
/// </summary>
|
||||
|
||||
@@ -1631,8 +1631,8 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
||||
{
|
||||
_logger.Information("No calendar sync identifier for calendar {Name}. Performing initial sync.", calendar.Name);
|
||||
|
||||
var startDate = DateTime.UtcNow.AddYears(-2).ToString("u");
|
||||
var endDate = DateTime.UtcNow.ToString("u");
|
||||
var startDate = DateTimeOffset.UtcNow.AddYears(-2).ToString("o");
|
||||
var endDate = DateTimeOffset.UtcNow.ToString("o");
|
||||
|
||||
eventsDeltaResponse = await _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.GetAsDeltaGetResponseAsync((requestConfiguration) =>
|
||||
{
|
||||
@@ -1658,20 +1658,7 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
||||
|
||||
_logger.Information("Performing delta sync for calendar {Name}.", calendar.Name);
|
||||
|
||||
var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.ToGetRequestInformation((requestConfiguration) =>
|
||||
{
|
||||
|
||||
//requestConfiguration.QueryParameters.StartDateTime = startDate;
|
||||
//requestConfiguration.QueryParameters.EndDateTime = endDate;
|
||||
});
|
||||
|
||||
//var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.ToGetRequestInformation((config) =>
|
||||
//{
|
||||
// config.QueryParameters.Top = (int)InitialMessageDownloadCountPerFolder;
|
||||
// config.QueryParameters.Select = outlookMessageSelectParameters;
|
||||
// config.QueryParameters.Orderby = ["receivedDateTime desc"];
|
||||
//});
|
||||
|
||||
var requestInformation = _graphClient.Me.Calendars[calendar.RemoteCalendarId].CalendarView.Delta.ToGetRequestInformation();
|
||||
|
||||
requestInformation.UrlTemplate = requestInformation.UrlTemplate.Insert(requestInformation.UrlTemplate.Length - 1, ",%24deltatoken");
|
||||
requestInformation.QueryParameters.Add("%24deltatoken", currentDeltaToken);
|
||||
@@ -1726,11 +1713,12 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
||||
|
||||
var deltaToken = GetDeltaTokenFromDeltaLink(latestDeltaLink);
|
||||
|
||||
await _outlookChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, deltaToken).ConfigureAwait(false);
|
||||
/// await _outlookChangeProcessor.UpdateCalendarDeltaSynchronizationToken(calendar.Id, deltaToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
// TODO: Return proper results.
|
||||
return CalendarSynchronizationResult.Empty;
|
||||
}
|
||||
|
||||
private async Task SynchronizeCalendarsAsync(CancellationToken cancellationToken = default)
|
||||
|
||||
+10
-7
@@ -27,7 +27,7 @@ using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Mail.ViewModels;
|
||||
|
||||
public partial class AppShellViewModel : MailBaseViewModel,
|
||||
public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
IRecipient<NavigateManageAccountsRequested>,
|
||||
IRecipient<MailtoProtocolMessageRequested>,
|
||||
IRecipient<RefreshUnreadCountsMessage>,
|
||||
@@ -83,7 +83,7 @@ public partial class AppShellViewModel : MailBaseViewModel,
|
||||
|
||||
private readonly SemaphoreSlim accountInitFolderUpdateSlim = new SemaphoreSlim(1);
|
||||
|
||||
public AppShellViewModel(IMailDialogService dialogService,
|
||||
public MailAppShellViewModel(IMailDialogService dialogService,
|
||||
INavigationService navigationService,
|
||||
IMimeFileService mimeFileService,
|
||||
INativeAppService nativeAppService,
|
||||
@@ -392,7 +392,7 @@ public partial class AppShellViewModel : MailBaseViewModel,
|
||||
|
||||
var args = new NavigateMailFolderEventArgs(baseFolderMenuItem, folderInitAwaitTask);
|
||||
|
||||
NavigationService.Navigate(WinoPage.MailListPage, args, NavigationReferenceFrame.ShellFrame);
|
||||
NavigationService.Navigate(WinoPage.MailListPage, args, NavigationReferenceFrame.InnerShellFrame);
|
||||
|
||||
UpdateWindowTitleForFolder(baseFolderMenuItem);
|
||||
});
|
||||
@@ -587,15 +587,15 @@ public partial class AppShellViewModel : MailBaseViewModel,
|
||||
}
|
||||
else if (clickedMenuItem is SettingsItem)
|
||||
{
|
||||
NavigationService.Navigate(WinoPage.SettingsPage, parameter, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.None);
|
||||
NavigationService.Navigate(WinoPage.SettingsPage, parameter, NavigationReferenceFrame.InnerShellFrame, NavigationTransitionType.None);
|
||||
}
|
||||
else if (clickedMenuItem is ManageAccountsMenuItem)
|
||||
{
|
||||
NavigationService.Navigate(WinoPage.ManageAccountsPage, parameter, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.None);
|
||||
NavigationService.Navigate(WinoPage.ManageAccountsPage, parameter, NavigationReferenceFrame.InnerShellFrame, NavigationTransitionType.None);
|
||||
}
|
||||
else if (clickedMenuItem is ContactsMenuItem)
|
||||
{
|
||||
NavigationService.Navigate(WinoPage.ContactsPage, parameter, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.None);
|
||||
NavigationService.Navigate(WinoPage.ContactsPage, parameter, NavigationReferenceFrame.InnerShellFrame, NavigationTransitionType.None);
|
||||
}
|
||||
else if (clickedMenuItem is IAccountMenuItem clickedAccountMenuItem)
|
||||
{
|
||||
@@ -1003,7 +1003,7 @@ public partial class AppShellViewModel : MailBaseViewModel,
|
||||
accountMenuItem.TotalItemsToSync = message.TotalItemsToSync;
|
||||
accountMenuItem.RemainingItemsToSync = message.RemainingItemsToSync;
|
||||
accountMenuItem.SynchronizationStatus = message.SynchronizationStatus;
|
||||
|
||||
|
||||
// If this account is part of a merged inbox, update the merged inbox progress as well
|
||||
if (accountMenuItem.ParentMenuItem is MergedAccountMenuItem mergedAccountMenuItem)
|
||||
{
|
||||
@@ -1040,6 +1040,9 @@ public partial class AppShellViewModel : MailBaseViewModel,
|
||||
{
|
||||
base.UnregisterRecipients();
|
||||
|
||||
Messenger.Unregister<AccountCreatedMessage>(this);
|
||||
Messenger.Unregister<AccountRemovedMessage>(this);
|
||||
Messenger.Unregister<AccountUpdatedMessage>(this);
|
||||
Messenger.Unregister<NavigateManageAccountsRequested>(this);
|
||||
Messenger.Unregister<MailtoProtocolMessageRequested>(this);
|
||||
Messenger.Unregister<RefreshUnreadCountsMessage>(this);
|
||||
@@ -43,7 +43,9 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
||||
protected override void RegisterRecipients()
|
||||
{
|
||||
base.RegisterRecipients();
|
||||
|
||||
|
||||
UnregisterRecipients();
|
||||
|
||||
Messenger.Register<MailAddedMessage>(this);
|
||||
Messenger.Register<MailRemovedMessage>(this);
|
||||
Messenger.Register<MailUpdatedMessage>(this);
|
||||
@@ -58,7 +60,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
||||
protected override void UnregisterRecipients()
|
||||
{
|
||||
base.UnregisterRecipients();
|
||||
|
||||
|
||||
Messenger.Unregister<MailAddedMessage>(this);
|
||||
Messenger.Unregister<MailRemovedMessage>(this);
|
||||
Messenger.Unregister<MailUpdatedMessage>(this);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Wino.Core;
|
||||
|
||||
namespace Wino.Mail.ViewModels;
|
||||
|
||||
public static class MailViewModelsContainerSetup
|
||||
{
|
||||
public static void RegisterViewModelService(this IServiceCollection services)
|
||||
{
|
||||
// View models use core services.
|
||||
services.RegisterCoreServices();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,14 @@
|
||||
<ResourceDictionary Source="Styles/WebViewEditorControl.xaml" />
|
||||
<ResourceDictionary Source="Controls/ListView/WinoListViewStyles.xaml" />
|
||||
|
||||
<ResourceDictionary Source="Styles/CalendarThemeResources.xaml" />
|
||||
|
||||
<ResourceDictionary Source="Styles/WinoDayTimelineCanvas.xaml" />
|
||||
<ResourceDictionary Source="Styles/WinoCalendarView.xaml" />
|
||||
<ResourceDictionary Source="Styles/WinoCalendarTypeSelectorControl.xaml" />
|
||||
|
||||
<styles:WinoExpanderStyle />
|
||||
<styles:WinoCalendarResources />
|
||||
<ResourceDictionary Source="AppThemes/Default.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -9,6 +9,9 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Microsoft.Windows.AppNotifications;
|
||||
using MimeKit.Cryptography;
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
@@ -17,12 +20,15 @@ using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Mail.Services;
|
||||
using Wino.Mail.ViewModels;
|
||||
using Wino.Mail.WinUI.Interfaces;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Services;
|
||||
namespace Wino.Mail.WinUI;
|
||||
|
||||
public partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
|
||||
public partial class App : WinoApplication,
|
||||
IRecipient<NewMailSynchronizationRequested>,
|
||||
IRecipient<NewCalendarSynchronizationRequested>
|
||||
{
|
||||
private ISynchronizationManager? _synchronizationManager;
|
||||
|
||||
@@ -60,11 +66,13 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
|
||||
services.AddTransient<ISettingsBuilderService, SettingsBuilderService>();
|
||||
services.AddTransient<IProviderService, ProviderService>();
|
||||
services.AddSingleton<IAuthenticatorConfig, MailAuthenticatorConfiguration>();
|
||||
services.AddSingleton<IAccountCalendarStateService, AccountCalendarStateService>();
|
||||
}
|
||||
|
||||
private void RegisterViewModels(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(typeof(AppShellViewModel));
|
||||
services.AddSingleton(typeof(MailAppShellViewModel));
|
||||
services.AddSingleton(typeof(CalendarAppShellViewModel));
|
||||
|
||||
services.AddTransient(typeof(MailListPageViewModel));
|
||||
services.AddTransient(typeof(MailRenderingPageViewModel));
|
||||
@@ -85,6 +93,10 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
|
||||
services.AddTransient(typeof(AliasManagementPageViewModel));
|
||||
services.AddTransient(typeof(ContactsPageViewModel));
|
||||
services.AddTransient(typeof(SignatureAndEncryptionPageViewModel));
|
||||
|
||||
services.AddTransient(typeof(CalendarPageViewModel));
|
||||
services.AddTransient(typeof(CalendarSettingsPageViewModel));
|
||||
services.AddTransient(typeof(EventDetailsPageViewModel));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -93,7 +105,7 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.RegisterViewModelService();
|
||||
services.RegisterCoreServices();
|
||||
services.RegisterSharedServices();
|
||||
services.RegisterCoreUWPServices();
|
||||
services.RegisterCoreViewModels();
|
||||
@@ -347,12 +359,11 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
|
||||
private void RegisterRecipients()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<NewMailSynchronizationRequested>(this);
|
||||
WeakReferenceMessenger.Default.Register<NewCalendarSynchronizationRequested>(this);
|
||||
}
|
||||
|
||||
public void Receive(NewMailSynchronizationRequested message)
|
||||
{
|
||||
_synchronizationManager?.SynchronizeMailAsync(message.Options);
|
||||
}
|
||||
public void Receive(NewMailSynchronizationRequested message) => _synchronizationManager?.SynchronizeMailAsync(message.Options);
|
||||
public void Receive(NewCalendarSynchronizationRequested message) => _synchronizationManager?.SynchronizeCalendarAsync(message.Options);
|
||||
|
||||
/// <summary>
|
||||
/// Handles activation redirected from another instance (single-instancing).
|
||||
@@ -378,4 +389,5 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class CalendarItemCommandBarFlyout : CommandBarFlyout
|
||||
{
|
||||
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
|
||||
|
||||
public CalendarItemViewModel Item
|
||||
{
|
||||
get { return (CalendarItemViewModel)GetValue(ItemProperty); }
|
||||
set { SetValue(ItemProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemCommandBarFlyout flyout)
|
||||
{
|
||||
flyout.UpdateMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMenuItems()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<UserControl
|
||||
x:Class="Wino.Calendar.Controls.CalendarItemControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Mail.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:local="using:Wino.Calendar.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400"
|
||||
CanDrag="True"
|
||||
DragStarting="ControlDragStarting"
|
||||
DropCompleted="ControlDropped"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
x:Name="MainGrid"
|
||||
CornerRadius="4"
|
||||
DoubleTapped="ControlDoubleTapped"
|
||||
RightTapped="ControlRightTapped"
|
||||
Tapped="ControlTapped"
|
||||
ToolTipService.ToolTip="{x:Bind CalendarItemTitle, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.ContextFlyout>
|
||||
<local:CalendarItemCommandBarFlyout Placement="Top">
|
||||
<local:CalendarItemCommandBarFlyout.PrimaryCommands>
|
||||
<AppBarButton Icon="Save" Label="save" />
|
||||
<AppBarButton Icon="Delete" Label="Delet" />
|
||||
</local:CalendarItemCommandBarFlyout.PrimaryCommands>
|
||||
</local:CalendarItemCommandBarFlyout>
|
||||
</Grid.ContextFlyout>
|
||||
|
||||
<Grid
|
||||
x:Name="MainBackground"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}" />
|
||||
|
||||
<Rectangle
|
||||
x:Name="MainBorder"
|
||||
Grid.ColumnSpan="2"
|
||||
Canvas.ZIndex="2"
|
||||
Stroke="{ThemeResource CalendarItemBorderBrush}"
|
||||
StrokeThickness="0" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="EventTitleTextblock"
|
||||
Margin="2,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="8"
|
||||
FontSize="13"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
HorizontalTextAlignment="Center"
|
||||
Text="{x:Bind CalendarItemTitle, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<!-- TODO: Event attributes -->
|
||||
<StackPanel
|
||||
x:Name="AttributeStack"
|
||||
Grid.Column="1"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<controls:WinoFontIcon
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
Icon="CalendarEventRepeat"
|
||||
Visibility="{x:Bind CalendarItem.IsRecurringEvent, Mode=OneWay}" />
|
||||
|
||||
<controls:WinoFontIcon
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
Icon="CalendarEventMuiltiDay"
|
||||
Visibility="{x:Bind CalendarItem.IsMultiDayEvent, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="SelectionStates">
|
||||
<VisualState x:Name="NonSelected" />
|
||||
<VisualState x:Name="Selected">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="1" />
|
||||
<Setter Target="MainBorder.Margin" Value="1" />
|
||||
<Setter Target="MainBorder.Stroke" Value="{ThemeResource CalendarItemSelectedBorderBrush}" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind CalendarItem.IsSelected, Mode=OneWay, FallbackValue=False}" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="DraggingStates">
|
||||
<VisualState x:Name="NotDragging" />
|
||||
<VisualState x:Name="Dragging">
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind IsDragging, Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="2" />
|
||||
<Setter Target="MainBorder.Stroke" Value="{ThemeResource CalendarItemDraggingBorderBrush}" />
|
||||
<Setter Target="MainBorder.StrokeDashArray" Value="2.5" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="EventDurationStates">
|
||||
<!-- Regular event template in the panel. -->
|
||||
<VisualState x:Name="RegularEvent" />
|
||||
|
||||
<!-- All-Day template for top area. -->
|
||||
<VisualState x:Name="AllDayEvent">
|
||||
<VisualState.Setters>
|
||||
|
||||
<Setter Target="AttributeStack.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="MainGrid.MinHeight" Value="30" />
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!-- Multi-Day template for top area. -->
|
||||
<VisualState x:Name="CustomAreaMultiDayEvent">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainBackground.Opacity" Value="1" />
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||
<Setter Target="AttributeStack.Visibility" Value="Visible" />
|
||||
<Setter Target="AttributeStack.Margin" Value="0,0,4,0" />
|
||||
<Setter Target="AttributeStack.VerticalAlignment" Value="Center" />
|
||||
<Setter Target="MainGrid.MinHeight" Value="30" />
|
||||
<Setter Target="EventTitleTextblock.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="EventTitleTextblock.HorizontalTextAlignment" Value="Left" />
|
||||
<Setter Target="EventTitleTextblock.Margin" Value="6,0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!--
|
||||
Ghost rendering for multi-day events in the panel.
|
||||
All-Multi day area template is CustomAreaMultiDayEvent
|
||||
-->
|
||||
|
||||
<VisualState x:Name="MultiDayEvent">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MainGrid.CornerRadius" Value="0" />
|
||||
<Setter Target="MainBackground.Opacity" Value="0.2" />
|
||||
<Setter Target="MainGrid.IsHitTestVisible" Value="False" />
|
||||
<Setter Target="MainBorder.StrokeThickness" Value="0" />
|
||||
<Setter Target="AttributeStack.Visibility" Value="Collapsed" />
|
||||
<Setter Target="EventTitleTextblock.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Itenso.TimePeriod;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Messages;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public sealed partial class CalendarItemControl : UserControl
|
||||
{
|
||||
// Single tap has a delay to report double taps properly.
|
||||
private bool isSingleTap = false;
|
||||
|
||||
public static readonly DependencyProperty CalendarItemProperty = DependencyProperty.Register(nameof(CalendarItem), typeof(CalendarItemViewModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarItemChanged)));
|
||||
public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(nameof(IsDragging), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsCustomEventAreaProperty = DependencyProperty.Register(nameof(IsCustomEventArea), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty CalendarItemTitleProperty = DependencyProperty.Register(nameof(CalendarItemTitle), typeof(string), typeof(CalendarItemControl), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty DisplayingDateProperty = DependencyProperty.Register(nameof(DisplayingDate), typeof(CalendarDayModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDisplayDateChanged)));
|
||||
|
||||
/// <summary>
|
||||
/// Whether the control is displaying as regular event or all-multi day area in the day control.
|
||||
/// </summary>
|
||||
public bool IsCustomEventArea
|
||||
{
|
||||
get { return (bool)GetValue(IsCustomEventAreaProperty); }
|
||||
set { SetValue(IsCustomEventAreaProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Day that the calendar item is rendered at.
|
||||
/// It's needed for title manipulation and some other adjustments later on.
|
||||
/// </summary>
|
||||
public CalendarDayModel DisplayingDate
|
||||
{
|
||||
get { return (CalendarDayModel)GetValue(DisplayingDateProperty); }
|
||||
set { SetValue(DisplayingDateProperty, value); }
|
||||
}
|
||||
|
||||
public string CalendarItemTitle
|
||||
{
|
||||
get { return (string)GetValue(CalendarItemTitleProperty); }
|
||||
set { SetValue(CalendarItemTitleProperty, value); }
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItem
|
||||
{
|
||||
get { return (CalendarItemViewModel)GetValue(CalendarItemProperty); }
|
||||
set { SetValue(CalendarItemProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsDragging
|
||||
{
|
||||
get { return (bool)GetValue(IsDraggingProperty); }
|
||||
set { SetValue(IsDraggingProperty, value); }
|
||||
}
|
||||
|
||||
public CalendarItemControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private static void OnDisplayDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemControl control)
|
||||
{
|
||||
control.UpdateControlVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemControl control)
|
||||
{
|
||||
control.UpdateControlVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateControlVisuals()
|
||||
{
|
||||
// Depending on the calendar item's duration and attributes, we might need to change the display title.
|
||||
// 1. Multi-Day events should display the start date and end date.
|
||||
// 2. Multi-Day events that occupy the whole day just shows 'all day'.
|
||||
// 3. Other events should display the title.
|
||||
|
||||
if (CalendarItem == null) return;
|
||||
if (DisplayingDate == null) return;
|
||||
|
||||
if (CalendarItem.IsMultiDayEvent)
|
||||
{
|
||||
// Multi day events are divided into 3 categories:
|
||||
// 1. All day events
|
||||
// 2. Events that started after the period.
|
||||
// 3. Events that started before the period and finishes within the period.
|
||||
|
||||
var periodRelation = CalendarItem.Period.GetRelation(DisplayingDate.Period);
|
||||
|
||||
if (periodRelation == Itenso.TimePeriod.PeriodRelation.StartInside ||
|
||||
periodRelation == PeriodRelation.EnclosingStartTouching)
|
||||
{
|
||||
// hour -> title
|
||||
CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDate.TimeOfDay)} -> {CalendarItem.Title}";
|
||||
}
|
||||
else if (
|
||||
periodRelation == PeriodRelation.EndInside ||
|
||||
periodRelation == PeriodRelation.EnclosingEndTouching)
|
||||
{
|
||||
// title <- hour
|
||||
CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDate.TimeOfDay)}";
|
||||
}
|
||||
else if (periodRelation == PeriodRelation.Enclosing)
|
||||
{
|
||||
// This event goes all day and it's multi-day.
|
||||
// Item must be hidden in the calendar but displayed on the custom area at the top.
|
||||
|
||||
CalendarItemTitle = $"{Translator.CalendarItemAllDay} {CalendarItem.Title}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not expected, but there it is.
|
||||
CalendarItemTitle = CalendarItem.Title;
|
||||
}
|
||||
|
||||
// Debug.WriteLine($"{CalendarItem.Title} Period relation with {DisplayingDate.Period.ToString()}: {periodRelation}");
|
||||
}
|
||||
else
|
||||
{
|
||||
CalendarItemTitle = CalendarItem.Title;
|
||||
}
|
||||
|
||||
UpdateVisualStates();
|
||||
}
|
||||
|
||||
private void UpdateVisualStates()
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
if (CalendarItem.IsAllDayEvent)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
||||
}
|
||||
else if (CalendarItem.IsMultiDayEvent)
|
||||
{
|
||||
if (IsCustomEventArea)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide it.
|
||||
VisualStateManager.GoToState(this, "MultiDayEvent", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, "RegularEvent", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void ControlDragStarting(UIElement sender, DragStartingEventArgs args) => IsDragging = true;
|
||||
|
||||
private void ControlDropped(UIElement sender, DropCompletedEventArgs args) => IsDragging = false;
|
||||
|
||||
private async void ControlTapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
isSingleTap = true;
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
if (isSingleTap)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new CalendarItemTappedMessage(CalendarItem, DisplayingDate));
|
||||
}
|
||||
}
|
||||
|
||||
private void ControlDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
isSingleTap = false;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new CalendarItemDoubleTappedMessage(CalendarItem));
|
||||
}
|
||||
|
||||
private void ControlRightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new CalendarItemRightTappedMessage(CalendarItem));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.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 partial 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;
|
||||
|
||||
var t = FindName("ScrollingHost");
|
||||
}
|
||||
|
||||
public void GoPreviousFlip()
|
||||
{
|
||||
var backPeer = new ButtonAutomationPeer(PreviousButton);
|
||||
backPeer.Invoke();
|
||||
}
|
||||
|
||||
public void GoNextFlip()
|
||||
{
|
||||
var nextPeer = new ButtonAutomationPeer(NextButton);
|
||||
nextPeer.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class DayColumnControl : Control
|
||||
{
|
||||
private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText);
|
||||
private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder);
|
||||
private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText);
|
||||
|
||||
private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl);
|
||||
|
||||
private const string TodayState = nameof(TodayState);
|
||||
private const string NotTodayState = nameof(NotTodayState);
|
||||
|
||||
private TextBlock HeaderDateDayText;
|
||||
private TextBlock ColumnHeaderText;
|
||||
private Border IsTodayBorder;
|
||||
private ItemsControl AllDayItemsControl;
|
||||
|
||||
public CalendarDayModel DayModel
|
||||
{
|
||||
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;
|
||||
AllDayItemsControl = GetTemplateChild(PART_AllDayItemsControl) as ItemsControl;
|
||||
|
||||
UpdateValues();
|
||||
}
|
||||
|
||||
private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (control is DayColumnControl columnControl)
|
||||
{
|
||||
columnControl.UpdateValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
if (HeaderDateDayText == null || IsTodayBorder == null || DayModel == null) return;
|
||||
|
||||
HeaderDateDayText.Text = DayModel.RepresentingDate.Day.ToString();
|
||||
|
||||
// Monthly template does not use it.
|
||||
if (ColumnHeaderText != null)
|
||||
{
|
||||
ColumnHeaderText.Text = DayModel.RepresentingDate.ToString("dddd", DayModel.CalendarRenderOptions.CalendarSettings.CultureInfo);
|
||||
}
|
||||
|
||||
AllDayItemsControl.ItemsSource = DayModel.EventsCollection.AllDayEvents;
|
||||
|
||||
bool isToday = DayModel.RepresentingDate.Date == DateTime.Now.Date;
|
||||
|
||||
VisualStateManager.GoToState(this, isToday ? TodayState : NotTodayState, false);
|
||||
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Calendar.Args;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarControl : Control
|
||||
{
|
||||
private const string PART_WinoFlipView = nameof(PART_WinoFlipView);
|
||||
private const string PART_IdleGrid = nameof(PART_IdleGrid);
|
||||
|
||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||
|
||||
public event EventHandler ScrollPositionChanging;
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection<DayRangeRenderModel>), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1));
|
||||
public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged)));
|
||||
public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged)));
|
||||
public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged)));
|
||||
|
||||
public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||
public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||
public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the day-week-month-year display type.
|
||||
/// Orientation is not determined by this property, but Orientation property.
|
||||
/// This property is used to determine the template to use for the calendar.
|
||||
/// </summary>
|
||||
public CalendarDisplayType DisplayType
|
||||
{
|
||||
get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); }
|
||||
set { SetValue(DisplayTypeProperty, value); }
|
||||
}
|
||||
|
||||
public CalendarOrientation Orientation
|
||||
{
|
||||
get { return (CalendarOrientation)GetValue(OrientationProperty); }
|
||||
set { SetValue(OrientationProperty, value); }
|
||||
}
|
||||
|
||||
public ItemsPanelTemplate VerticalItemsPanelTemplate
|
||||
{
|
||||
get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); }
|
||||
set { SetValue(VerticalItemsPanelTemplateProperty, value); }
|
||||
}
|
||||
|
||||
public ItemsPanelTemplate HorizontalItemsPanelTemplate
|
||||
{
|
||||
get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); }
|
||||
set { SetValue(HorizontalItemsPanelTemplateProperty, value); }
|
||||
}
|
||||
|
||||
public DayRangeRenderModel SelectedFlipViewDayRange
|
||||
{
|
||||
get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); }
|
||||
set { SetValue(SelectedFlipViewDayRangeProperty, value); }
|
||||
}
|
||||
|
||||
public ScrollViewer ActiveScrollViewer
|
||||
{
|
||||
get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); }
|
||||
set { SetValue(ActiveScrollViewerProperty, value); }
|
||||
}
|
||||
|
||||
public WinoDayTimelineCanvas ActiveCanvas
|
||||
{
|
||||
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
||||
set { SetValue(ActiveCanvasProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsFlipIdle
|
||||
{
|
||||
get { return (bool)GetValue(IsFlipIdleProperty); }
|
||||
set { SetValue(IsFlipIdleProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of day ranges to render.
|
||||
/// Each day range usually represents a week, but it may support other ranges.
|
||||
/// </summary>
|
||||
public ObservableCollection<DayRangeRenderModel> DayRanges
|
||||
{
|
||||
get { return (ObservableCollection<DayRangeRenderModel>)GetValue(DayRangesProperty); }
|
||||
set { SetValue(DayRangesProperty, value); }
|
||||
}
|
||||
|
||||
public int SelectedFlipViewIndex
|
||||
{
|
||||
get { return (int)GetValue(SelectedFlipViewIndexProperty); }
|
||||
set { SetValue(SelectedFlipViewIndexProperty, value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private WinoCalendarFlipView InternalFlipView;
|
||||
private Grid IdleGrid;
|
||||
|
||||
public WinoCalendarControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(WinoCalendarControl);
|
||||
SizeChanged += CalendarSizeChanged;
|
||||
}
|
||||
|
||||
private static void OnCalendarOrientationPropertiesUpdated(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (calendar is WinoCalendarControl control)
|
||||
{
|
||||
control.ManageCalendarOrientation();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIdleStateChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (calendar is WinoCalendarControl calendarControl)
|
||||
{
|
||||
calendarControl.UpdateIdleState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void OnActiveVerticalScrollViewerChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (calendar is WinoCalendarControl calendarControl)
|
||||
{
|
||||
if (e.OldValue is ScrollViewer oldScrollViewer)
|
||||
{
|
||||
calendarControl.DeregisterScrollChanges(oldScrollViewer);
|
||||
}
|
||||
|
||||
if (e.NewValue is ScrollViewer newScrollViewer)
|
||||
{
|
||||
calendarControl.RegisterScrollChanges(newScrollViewer);
|
||||
}
|
||||
|
||||
calendarControl.ManageHighlightedDateRange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void OnActiveCanvasChanged(DependencyObject calendar, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (calendar is WinoCalendarControl calendarControl)
|
||||
{
|
||||
if (e.OldValue is WinoDayTimelineCanvas oldCanvas)
|
||||
{
|
||||
// Dismiss any selection on the old canvas.
|
||||
calendarControl.DeregisterCanvas(oldCanvas);
|
||||
}
|
||||
|
||||
if (e.NewValue is WinoDayTimelineCanvas newCanvas)
|
||||
{
|
||||
calendarControl.RegisterCanvas(newCanvas);
|
||||
}
|
||||
|
||||
calendarControl.ManageHighlightedDateRange();
|
||||
}
|
||||
}
|
||||
|
||||
private void ManageCalendarOrientation()
|
||||
{
|
||||
if (InternalFlipView == null || HorizontalItemsPanelTemplate == null || VerticalItemsPanelTemplate == null) return;
|
||||
|
||||
InternalFlipView.ItemsPanel = Orientation == CalendarOrientation.Horizontal ? HorizontalItemsPanelTemplate : VerticalItemsPanelTemplate;
|
||||
}
|
||||
|
||||
private void ManageHighlightedDateRange()
|
||||
=> SelectedFlipViewDayRange = InternalFlipView.SelectedItem as DayRangeRenderModel;
|
||||
|
||||
private void DeregisterCanvas(WinoDayTimelineCanvas canvas)
|
||||
{
|
||||
if (canvas == null) return;
|
||||
|
||||
canvas.SelectedDateTime = null;
|
||||
canvas.TimelineCellSelected -= ActiveTimelineCellSelected;
|
||||
canvas.TimelineCellUnselected -= ActiveTimelineCellUnselected;
|
||||
}
|
||||
|
||||
private void RegisterCanvas(WinoDayTimelineCanvas canvas)
|
||||
{
|
||||
if (canvas == null) return;
|
||||
|
||||
canvas.SelectedDateTime = null;
|
||||
canvas.TimelineCellSelected += ActiveTimelineCellSelected;
|
||||
canvas.TimelineCellUnselected += ActiveTimelineCellUnselected;
|
||||
}
|
||||
|
||||
private void RegisterScrollChanges(ScrollViewer scrollViewer)
|
||||
{
|
||||
if (scrollViewer == null) return;
|
||||
|
||||
scrollViewer.ViewChanging += ScrollViewChanging;
|
||||
}
|
||||
|
||||
private void DeregisterScrollChanges(ScrollViewer scrollViewer)
|
||||
{
|
||||
if (scrollViewer == null) return;
|
||||
|
||||
scrollViewer.ViewChanging -= ScrollViewChanging;
|
||||
}
|
||||
|
||||
private void ScrollViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
|
||||
=> ScrollPositionChanging?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
private void CalendarSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (ActiveCanvas == null) return;
|
||||
|
||||
ActiveCanvas.SelectedDateTime = null;
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
InternalFlipView = GetTemplateChild(PART_WinoFlipView) as WinoCalendarFlipView;
|
||||
IdleGrid = GetTemplateChild(PART_IdleGrid) as Grid;
|
||||
|
||||
UpdateIdleState();
|
||||
ManageCalendarOrientation();
|
||||
}
|
||||
|
||||
private void UpdateIdleState()
|
||||
{
|
||||
InternalFlipView.Opacity = IsFlipIdle ? 0 : 1;
|
||||
IdleGrid.Visibility = IsFlipIdle ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ActiveTimelineCellUnselected(object sender, TimelineCellUnselectedArgs e)
|
||||
=> TimelineCellUnselected?.Invoke(this, e);
|
||||
|
||||
private void ActiveTimelineCellSelected(object sender, TimelineCellSelectedArgs e)
|
||||
=> TimelineCellSelected?.Invoke(this, e);
|
||||
|
||||
public void NavigateToDay(DateTime dateTime) => InternalFlipView.NavigateToDay(dateTime);
|
||||
|
||||
public async void NavigateToHour(TimeSpan timeSpan)
|
||||
{
|
||||
if (ActiveScrollViewer == null) return;
|
||||
|
||||
// Total height of the FlipViewItem is the same as vertical ScrollViewer to position day headers.
|
||||
|
||||
await Task.Yield();
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
|
||||
{
|
||||
double hourHeght = 60;
|
||||
double totalHeight = ActiveScrollViewer.ScrollableHeight;
|
||||
double scrollPosition = timeSpan.TotalHours * hourHeght;
|
||||
|
||||
ActiveScrollViewer.ChangeView(null, scrollPosition, null, disableAnimation: false);
|
||||
});
|
||||
}
|
||||
public void ResetTimelineSelection()
|
||||
{
|
||||
if (ActiveCanvas == null) return;
|
||||
|
||||
ActiveCanvas.SelectedDateTime = null;
|
||||
}
|
||||
|
||||
public void GoNextRange()
|
||||
{
|
||||
if (InternalFlipView == null) return;
|
||||
|
||||
InternalFlipView.GoNextFlip();
|
||||
}
|
||||
|
||||
public void GoPreviousRange()
|
||||
{
|
||||
if (InternalFlipView == null) return;
|
||||
|
||||
InternalFlipView.GoPreviousFlip();
|
||||
}
|
||||
|
||||
public void UnselectActiveTimelineCell()
|
||||
{
|
||||
if (ActiveCanvas == null) return;
|
||||
|
||||
ActiveCanvas.SelectedDateTime = null;
|
||||
}
|
||||
|
||||
public CalendarItemControl GetCalendarItemControl(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
return this.FindDescendants<CalendarItemControl>().FirstOrDefault(a => a.CalendarItem == calendarItemViewModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Collections;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarFlipView : CustomCalendarFlipView
|
||||
{
|
||||
public static readonly DependencyProperty IsIdleProperty = DependencyProperty.Register(nameof(IsIdle), typeof(bool), typeof(WinoCalendarFlipView), new PropertyMetadata(true));
|
||||
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty ActiveVerticalScrollViewerProperty = DependencyProperty.Register(nameof(ActiveVerticalScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active canvas that is currently displayed in the flip view.
|
||||
/// Each day-range of flip view item has a canvas that displays the day timeline.
|
||||
/// </summary>
|
||||
public WinoDayTimelineCanvas ActiveCanvas
|
||||
{
|
||||
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
||||
set { SetValue(ActiveCanvasProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scroll viewer that is currently active in the flip view.
|
||||
/// It's the vertical scroll that scrolls the timeline only, not the header part that belongs
|
||||
/// to parent FlipView control.
|
||||
/// </summary>
|
||||
public ScrollViewer ActiveVerticalScrollViewer
|
||||
{
|
||||
get { return (ScrollViewer)GetValue(ActiveVerticalScrollViewerProperty); }
|
||||
set { SetValue(ActiveVerticalScrollViewerProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsIdle
|
||||
{
|
||||
get { return (bool)GetValue(IsIdleProperty); }
|
||||
set { SetValue(IsIdleProperty, value); }
|
||||
}
|
||||
|
||||
public WinoCalendarFlipView()
|
||||
{
|
||||
RegisterPropertyChangedCallback(SelectedIndexProperty, new DependencyPropertyChangedCallback(OnSelectedIndexUpdated));
|
||||
RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged));
|
||||
}
|
||||
|
||||
private static void OnItemsSourceChanged(DependencyObject d, DependencyProperty e)
|
||||
{
|
||||
if (d is WinoCalendarFlipView flipView)
|
||||
{
|
||||
flipView.RegisterItemsSourceChange();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnSelectedIndexUpdated(DependencyObject d, DependencyProperty e)
|
||||
{
|
||||
if (d is WinoCalendarFlipView flipView)
|
||||
{
|
||||
flipView.UpdateActiveCanvas();
|
||||
flipView.UpdateActiveScrollViewer();
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterItemsSourceChange()
|
||||
{
|
||||
if (GetItemsSource() is INotifyCollectionChanged notifyCollectionChanged)
|
||||
{
|
||||
notifyCollectionChanged.CollectionChanged += ItemsSourceUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemsSourceUpdated(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
IsIdle = e.Action == NotifyCollectionChangedAction.Reset || e.Action == NotifyCollectionChangedAction.Replace;
|
||||
}
|
||||
|
||||
private async Task<FlipViewItem> GetCurrentFlipViewItem()
|
||||
{
|
||||
// TODO: Refactor this mechanism by listening to PrepareContainerForItemOverride and Loaded events together.
|
||||
while (ContainerFromIndex(SelectedIndex) == null)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
return ContainerFromIndex(SelectedIndex) as FlipViewItem;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void UpdateActiveScrollViewer()
|
||||
{
|
||||
if (SelectedIndex < 0)
|
||||
ActiveVerticalScrollViewer = null;
|
||||
else
|
||||
{
|
||||
GetCurrentFlipViewItem().ContinueWith(task =>
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
{
|
||||
var flipViewItem = task.Result;
|
||||
|
||||
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
ActiveVerticalScrollViewer = flipViewItem.FindDescendant<ScrollViewer>();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateActiveCanvas()
|
||||
{
|
||||
if (SelectedIndex < 0)
|
||||
ActiveCanvas = null;
|
||||
else
|
||||
{
|
||||
GetCurrentFlipViewItem().ContinueWith(task =>
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
{
|
||||
var flipViewItem = task.Result;
|
||||
|
||||
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
ActiveCanvas = flipViewItem.FindDescendant<WinoDayTimelineCanvas>();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified date in the calendar.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">Date to navigate.</param>
|
||||
public async void NavigateToDay(DateTime dateTime)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
|
||||
{
|
||||
// Find the day range that contains the date.
|
||||
var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date));
|
||||
|
||||
if (dayRange != null)
|
||||
{
|
||||
var navigationItemIndex = GetItemsSource().IndexOf(dayRange);
|
||||
|
||||
if (Math.Abs(navigationItemIndex - SelectedIndex) > 4)
|
||||
{
|
||||
// Difference between dates are high.
|
||||
// No need to animate this much, just go without animating.
|
||||
|
||||
SelectedIndex = navigationItemIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Until we reach the day in the flip, simulate next-prev button clicks.
|
||||
// This will make sure the FlipView animations are triggered.
|
||||
// Setting SelectedIndex directly doesn't trigger the animations.
|
||||
|
||||
while (SelectedIndex != navigationItemIndex)
|
||||
{
|
||||
if (SelectedIndex > navigationItemIndex)
|
||||
{
|
||||
GoPreviousFlip();
|
||||
}
|
||||
else
|
||||
{
|
||||
GoNextFlip();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource()
|
||||
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Itenso.TimePeriod;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.Foundation;
|
||||
using Wino.Calendar.Models;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarPanel : Panel
|
||||
{
|
||||
private const double LastItemRightExtraMargin = 12d;
|
||||
|
||||
// Store each ICalendarItem measurements by their Id.
|
||||
private readonly Dictionary<ICalendarItem, CalendarItemMeasurement> _measurements = new Dictionary<ICalendarItem, CalendarItemMeasurement>();
|
||||
|
||||
public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0)));
|
||||
public static readonly DependencyProperty HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d));
|
||||
public static readonly DependencyProperty PeriodProperty = DependencyProperty.Register(nameof(Period), typeof(ITimePeriod), typeof(WinoCalendarPanel), new PropertyMetadata(null));
|
||||
|
||||
public ITimePeriod Period
|
||||
{
|
||||
get { return (ITimePeriod)GetValue(PeriodProperty); }
|
||||
set { SetValue(PeriodProperty, value); }
|
||||
}
|
||||
|
||||
public double HourHeight
|
||||
{
|
||||
get { return (double)GetValue(HourHeightProperty); }
|
||||
set { SetValue(HourHeightProperty, value); }
|
||||
}
|
||||
|
||||
public Thickness EventItemMargin
|
||||
{
|
||||
get { return (Thickness)GetValue(EventItemMarginProperty); }
|
||||
set { SetValue(EventItemMarginProperty, value); }
|
||||
}
|
||||
|
||||
private void ResetMeasurements() => _measurements.Clear();
|
||||
|
||||
private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight)
|
||||
{
|
||||
var childStart = calendarItemViewModel.StartDate;
|
||||
|
||||
if (childStart <= Period.Start)
|
||||
{
|
||||
// Event started before or exactly at the periods tart. This might be a multi-day event.
|
||||
// We can simply consider event must not have a top margin.
|
||||
|
||||
return 0d;
|
||||
}
|
||||
|
||||
double minutesFromStart = (childStart - Period.Start).TotalMinutes;
|
||||
return (minutesFromStart / 1440) * availableHeight;
|
||||
}
|
||||
|
||||
private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
||||
{
|
||||
return (calendarItemMeasurement.Right - calendarItemMeasurement.Left) * availableWidth;
|
||||
}
|
||||
|
||||
private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
||||
=> availableWidth * calendarItemMeasurement.Left;
|
||||
|
||||
private double GetChildHeight(ICalendarItem child)
|
||||
{
|
||||
// All day events are not measured.
|
||||
if (child.IsAllDayEvent) return 0;
|
||||
|
||||
double childDurationInMinutes = 0d;
|
||||
double availableHeight = HourHeight * 24;
|
||||
|
||||
var periodRelation = child.Period.GetRelation(Period);
|
||||
|
||||
// Debug.WriteLine($"Render relation of {child.Title} ({child.Period.Start} - {child.Period.End}) is {periodRelation} with {Period.Start.Day}");
|
||||
|
||||
if (!child.IsMultiDayEvent)
|
||||
{
|
||||
childDurationInMinutes = child.Period.Duration.TotalMinutes;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multi-day event.
|
||||
// Check how many of the event falls into the current period.
|
||||
childDurationInMinutes = (child.Period.End - Period.Start).TotalMinutes;
|
||||
}
|
||||
|
||||
return (childDurationInMinutes / 1440) * availableHeight;
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
ResetMeasurements();
|
||||
return base.MeasureOverride(availableSize);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
if (Period == null || HourHeight == 0d) return finalSize;
|
||||
|
||||
// Measure/arrange each child height and width.
|
||||
// This is a vertical calendar. Therefore the height of each child is the duration of the event.
|
||||
// Children weights for left and right will be saved if they don't exist.
|
||||
// This is important because we don't want to measure the weights again.
|
||||
// They don't change until new event is added or removed.
|
||||
// Width of the each child may depend on the rectangle packing algorithm.
|
||||
// Children are first categorized into columns. Then each column is shifted to the left until
|
||||
// no overlap occurs. The width of each child is calculated based on the number of columns it spans.
|
||||
|
||||
double availableHeight = finalSize.Height;
|
||||
double availableWidth = finalSize.Width;
|
||||
|
||||
var calendarControls = Children.Cast<ContentPresenter>();
|
||||
|
||||
if (!calendarControls.Any()) return base.ArrangeOverride(finalSize);
|
||||
|
||||
var events = calendarControls.Select(a => a.Content as CalendarItemViewModel);
|
||||
|
||||
LayoutEvents(events);
|
||||
|
||||
foreach (var control in calendarControls)
|
||||
{
|
||||
// We can't arrange this child.
|
||||
if (!(control.Content is ICalendarItem child)) continue;
|
||||
|
||||
bool isHorizontallyLastItem = false;
|
||||
|
||||
double childWidth = 0,
|
||||
childHeight = Math.Max(0, GetChildHeight(child)),
|
||||
childTop = Math.Max(0, GetChildTopMargin(child, availableHeight)),
|
||||
childLeft = 0;
|
||||
|
||||
// No need to measure anything here.
|
||||
if (childHeight == 0) continue;
|
||||
|
||||
if (!_measurements.ContainsKey(child))
|
||||
{
|
||||
// Multi-day event.
|
||||
|
||||
childLeft = 0;
|
||||
childWidth = availableWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
var childMeasurement = _measurements[child];
|
||||
|
||||
childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
|
||||
childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
|
||||
|
||||
isHorizontallyLastItem = childMeasurement.Right == 1;
|
||||
}
|
||||
|
||||
// Add additional right margin to items that falls on the right edge of the panel.
|
||||
double extraRightMargin = 0;
|
||||
|
||||
// Multi-day events don't have any margin and their hit test is disabled.
|
||||
if (!child.IsMultiDayEvent)
|
||||
{
|
||||
// Max of 5% of the width or 20px max.
|
||||
extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0;
|
||||
}
|
||||
|
||||
if (childWidth < 0) childWidth = 1;
|
||||
|
||||
// Regular events must have 2px margin
|
||||
if (!child.IsMultiDayEvent && !child.IsAllDayEvent)
|
||||
{
|
||||
childLeft += 2;
|
||||
childTop += 2;
|
||||
childHeight -= 2;
|
||||
childWidth -= 2;
|
||||
}
|
||||
|
||||
var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, Math.Max(childWidth - extraRightMargin, 1), childHeight);
|
||||
|
||||
// Make sure measured size will fit in the arranged box.
|
||||
var measureSize = arrangementRect.ToSize();
|
||||
control.Measure(measureSize);
|
||||
control.Arrange(arrangementRect);
|
||||
|
||||
//Debug.WriteLine($"{child.Title}, Measured: {measureSize}, Arranged: {arrangementRect}");
|
||||
}
|
||||
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
#region ColumSpanning and Packing Algorithm
|
||||
|
||||
private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement)
|
||||
{
|
||||
if (_measurements.ContainsKey(calendarItem))
|
||||
{
|
||||
_measurements[calendarItem] = measurement;
|
||||
}
|
||||
else
|
||||
{
|
||||
_measurements.Add(calendarItem, 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.StartDate).ThenBy(ev => ev.EndDate))
|
||||
{
|
||||
// Multi-day events are not measured.
|
||||
if (ev.IsMultiDayEvent) continue;
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarView : Control
|
||||
{
|
||||
private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder);
|
||||
private const string PART_CalendarView = nameof(PART_CalendarView);
|
||||
|
||||
public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged)));
|
||||
public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged)));
|
||||
public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty TodayBackgroundColorProperty = DependencyProperty.Register(nameof(TodayBackgroundColor), typeof(Color), typeof(WinoCalendarView), new PropertyMetadata(null));
|
||||
|
||||
public Color TodayBackgroundColor
|
||||
{
|
||||
get { return (Color)GetValue(TodayBackgroundColorProperty); }
|
||||
set { SetValue(TodayBackgroundColorProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the command to execute when a date is picked.
|
||||
/// Unused.
|
||||
/// </summary>
|
||||
public ICommand DateClickedCommand
|
||||
{
|
||||
get { return (ICommand)GetValue(DateClickedCommandProperty); }
|
||||
set { SetValue(DateClickedCommandProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the highlighted range of dates.
|
||||
/// </summary>
|
||||
public DateRange HighlightedDateRange
|
||||
{
|
||||
get { return (DateRange)GetValue(HighlightedDateRangeProperty); }
|
||||
set { SetValue(HighlightedDateRangeProperty, value); }
|
||||
}
|
||||
|
||||
public Brush VisibleDateBackground
|
||||
{
|
||||
get { return (Brush)GetValue(VisibleDateBackgroundProperty); }
|
||||
set { SetValue(VisibleDateBackgroundProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private CalendarView CalendarView;
|
||||
|
||||
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 || TodayBackgroundColor == null || CalendarView == null) return;
|
||||
|
||||
var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants<CalendarViewDayItem>(CalendarView);
|
||||
|
||||
foreach (var calendarDayItem in markDateCalendarDayItems)
|
||||
{
|
||||
var border = WinoVisualTreeHelper.GetChildObject<Border>(calendarDayItem, PART_DayViewItemBorder);
|
||||
|
||||
if (border == null) return;
|
||||
|
||||
if (calendarDayItem.Date.Date == DateTime.Today.Date)
|
||||
{
|
||||
border.Background = new SolidColorBrush(TodayBackgroundColor);
|
||||
}
|
||||
else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date)
|
||||
{
|
||||
border.Background = VisibleDateBackground;
|
||||
}
|
||||
else
|
||||
{
|
||||
border.Background = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.Foundation;
|
||||
using Wino.Calendar.Args;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoDayTimelineCanvas : Control, IDisposable
|
||||
{
|
||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||
|
||||
private const string PART_InternalCanvas = nameof(PART_InternalCanvas);
|
||||
private 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;
|
||||
|
||||
// TODO: These will leak. Dispose them properly when needed.
|
||||
Canvas.Draw += OnCanvasDraw;
|
||||
Canvas.PointerPressed += OnCanvasPointerPressed;
|
||||
|
||||
ForceDraw();
|
||||
}
|
||||
|
||||
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is WinoDayTimelineCanvas control)
|
||||
{
|
||||
if (e.OldValue != null && e.NewValue == null)
|
||||
{
|
||||
control.RaiseCellUnselected();
|
||||
}
|
||||
|
||||
control.ForceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseCellUnselected()
|
||||
{
|
||||
TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs());
|
||||
}
|
||||
|
||||
private void OnCanvasPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (RenderOptions == null) return;
|
||||
|
||||
var hourHeight = RenderOptions.CalendarSettings.HourHeight;
|
||||
|
||||
// When users click to cell we need to find the day, hour and minutes (first 30 minutes or second 30 minutes) that it represents on the timeline.
|
||||
|
||||
PointerPoint positionerRootPoint = e.GetCurrentPoint(PositionerUIElement);
|
||||
PointerPoint canvasPointerPoint = e.GetCurrentPoint(Canvas);
|
||||
|
||||
Point touchPoint = canvasPointerPoint.Position;
|
||||
|
||||
var singleDayWidth = (Canvas.ActualWidth / RenderOptions.TotalDayCount);
|
||||
|
||||
int day = (int)(touchPoint.X / singleDayWidth);
|
||||
int hour = (int)(touchPoint.Y / hourHeight);
|
||||
|
||||
bool isSecondHalf = touchPoint.Y % hourHeight > (hourHeight / 2);
|
||||
|
||||
var diffX = positionerRootPoint.Position.X - touchPoint.X;
|
||||
var diffY = positionerRootPoint.Position.Y - touchPoint.Y;
|
||||
|
||||
var cellStartRelativePositionX = diffX + (day * singleDayWidth);
|
||||
var cellEndRelativePositionX = cellStartRelativePositionX + singleDayWidth;
|
||||
|
||||
var cellStartRelativePositionY = diffY + (hour * hourHeight) + (isSecondHalf ? hourHeight / 2 : 0);
|
||||
var cellEndRelativePositionY = cellStartRelativePositionY + (isSecondHalf ? (hourHeight / 2) : hourHeight);
|
||||
|
||||
var cellSize = new Size(cellEndRelativePositionX - cellStartRelativePositionX, hourHeight / 2);
|
||||
var positionerPoint = new Point(cellStartRelativePositionX, cellStartRelativePositionY);
|
||||
|
||||
var clickedDateTime = RenderOptions.DateRange.StartDate.AddDays(day).AddHours(hour).AddMinutes(isSecondHalf ? 30 : 0);
|
||||
|
||||
// If there is already a selected date, in order to mimic the popup behavior, we need to dismiss the previous selection first.
|
||||
// Next click will be a new selection.
|
||||
|
||||
// Raise the events directly here instead of DP to not lose pointer position.
|
||||
if (clickedDateTime == SelectedDateTime || SelectedDateTime != null)
|
||||
{
|
||||
SelectedDateTime = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedDateTime = clickedDateTime;
|
||||
TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize));
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Clicked: {clickedDateTime}");
|
||||
}
|
||||
|
||||
public WinoDayTimelineCanvas()
|
||||
{
|
||||
DefaultStyleKey = typeof(WinoDayTimelineCanvas);
|
||||
}
|
||||
|
||||
private static void OnRenderingPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is WinoDayTimelineCanvas control)
|
||||
{
|
||||
control.ForceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceDraw() => Canvas?.Invalidate();
|
||||
|
||||
private bool CanDrawTimeline()
|
||||
{
|
||||
return RenderOptions != null
|
||||
&& Canvas != null
|
||||
&& Canvas.ReadyToDraw
|
||||
&& WorkingHourCellBackgroundColor != null
|
||||
&& SeperatorColor != null
|
||||
&& HalfHourSeperatorColor != null
|
||||
&& SelectedCellBackgroundBrush != null;
|
||||
}
|
||||
|
||||
private void OnCanvasDraw(CanvasControl sender, CanvasDrawEventArgs args)
|
||||
{
|
||||
if (!CanDrawTimeline()) return;
|
||||
|
||||
int hours = 24;
|
||||
|
||||
double canvasWidth = Canvas.ActualWidth;
|
||||
double canvasHeight = Canvas.ActualHeight;
|
||||
|
||||
if (canvasWidth == 0 || canvasHeight == 0) return;
|
||||
|
||||
// Calculate the width of each rectangle (1 day column)
|
||||
// Equal distribution of the whole width.
|
||||
double rectWidth = canvasWidth / RenderOptions.TotalDayCount;
|
||||
|
||||
// Calculate the height of each rectangle (1 hour row)
|
||||
double rectHeight = RenderOptions.CalendarSettings.HourHeight;
|
||||
|
||||
// Define stroke and fill colors
|
||||
var strokeColor = SeperatorColor.Color;
|
||||
float strokeThickness = 0.5f;
|
||||
|
||||
for (int day = 0; day < RenderOptions.TotalDayCount; day++)
|
||||
{
|
||||
var currentDay = RenderOptions.DateRange.StartDate.AddDays(day);
|
||||
|
||||
bool isWorkingDay = RenderOptions.CalendarSettings.WorkingDays.Contains(currentDay.DayOfWeek);
|
||||
|
||||
// Loop through each hour (rows)
|
||||
for (int hour = 0; hour < hours; hour++)
|
||||
{
|
||||
var renderTime = TimeSpan.FromHours(hour);
|
||||
|
||||
var representingDateTime = currentDay.AddHours(hour);
|
||||
|
||||
// Calculate the position and size of the rectangle
|
||||
double x = day * rectWidth;
|
||||
double y = hour * rectHeight;
|
||||
|
||||
var rectangle = new Rect(x, y, rectWidth, rectHeight);
|
||||
|
||||
// Draw the rectangle border.
|
||||
// This is the main rectangle.
|
||||
args.DrawingSession.DrawRectangle(rectangle, strokeColor, strokeThickness);
|
||||
|
||||
// Fill another rectangle with the working hour background color
|
||||
// This rectangle must be placed with -1 margin to prevent invisible borders of the main rectangle.
|
||||
if (isWorkingDay && renderTime >= RenderOptions.CalendarSettings.WorkingHourStart && renderTime <= RenderOptions.CalendarSettings.WorkingHourEnd)
|
||||
{
|
||||
var backgroundRectangle = new Rect(x + 1, y + 1, rectWidth - 1, rectHeight - 1);
|
||||
|
||||
args.DrawingSession.DrawRectangle(backgroundRectangle, strokeColor, strokeThickness);
|
||||
args.DrawingSession.FillRectangle(backgroundRectangle, WorkingHourCellBackgroundColor.Color);
|
||||
}
|
||||
|
||||
// Draw a line in the center of the rectangle for representing half hours.
|
||||
double lineY = y + rectHeight / 2;
|
||||
|
||||
args.DrawingSession.DrawLine((float)x, (float)lineY, (float)(x + rectWidth), (float)lineY, HalfHourSeperatorColor.Color, strokeThickness, new CanvasStrokeStyle()
|
||||
{
|
||||
DashStyle = CanvasDashStyle.Dot
|
||||
});
|
||||
}
|
||||
|
||||
// Draw selected item background color for the date if possible.
|
||||
if (SelectedDateTime != null)
|
||||
{
|
||||
var selectedDateTime = SelectedDateTime.Value;
|
||||
if (selectedDateTime.Date == currentDay.Date)
|
||||
{
|
||||
var selectionRectHeight = rectHeight / 2;
|
||||
var selectedY = selectedDateTime.Hour * rectHeight + (selectedDateTime.Minute / 60) * rectHeight;
|
||||
|
||||
// Second half of the hour is selected.
|
||||
if (selectedDateTime.TimeOfDay.Minutes == 30)
|
||||
{
|
||||
selectedY += rectHeight / 2;
|
||||
}
|
||||
|
||||
var selectedRectangle = new Rect(day * rectWidth, selectedY, rectWidth, selectionRectHeight);
|
||||
args.DrawingSession.FillRectangle(selectedRectangle, SelectedCellBackgroundBrush.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Canvas == null) return;
|
||||
|
||||
Canvas.Draw -= OnCanvasDraw;
|
||||
Canvas.PointerPressed -= OnCanvasPointerPressed;
|
||||
Canvas.RemoveFromVisualTree();
|
||||
|
||||
Canvas = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Ical.Net.CalendarComponents;
|
||||
using Ical.Net.DataTypes;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Collections;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.Calendar.Helpers;
|
||||
|
||||
public static class CalendarXamlHelpers
|
||||
{
|
||||
public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection)
|
||||
=> (CalendarItemViewModel)collection.AllDayEvents.FirstOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Returns full date + duration info in Event Details page details title.
|
||||
/// </summary>
|
||||
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||
{
|
||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||
|
||||
var start = calendarItemViewModel.Period.Start;
|
||||
var end = calendarItemViewModel.Period.End;
|
||||
|
||||
string timeFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "h:mm tt" : "HH:mm";
|
||||
string dateFormat = settings.DayHeaderDisplayType == DayHeaderDisplayType.TwelveHour ? "dddd, dd MMMM h:mm tt" : "dddd, dd MMMM HH:mm";
|
||||
|
||||
if (calendarItemViewModel.IsMultiDayEvent)
|
||||
{
|
||||
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
if (calendarItemViewModel == null || !calendarItemViewModel.IsRecurringChild) return string.Empty;
|
||||
|
||||
// Parse recurrence rules
|
||||
var calendarEvent = new CalendarEvent
|
||||
{
|
||||
Start = new CalDateTime(calendarItemViewModel.StartDate),
|
||||
End = new CalDateTime(calendarItemViewModel.EndDate),
|
||||
};
|
||||
|
||||
var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator);
|
||||
|
||||
foreach (var line in recurrenceLines)
|
||||
{
|
||||
calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line));
|
||||
}
|
||||
|
||||
if (calendarEvent.RecurrenceRules == null || !calendarEvent.RecurrenceRules.Any())
|
||||
{
|
||||
return "No recurrence pattern.";
|
||||
}
|
||||
|
||||
var recurrenceRule = calendarEvent.RecurrenceRules.First();
|
||||
var daysOfWeek = string.Join(", ", recurrenceRule.ByDay.Select(day => day.DayOfWeek.ToString()));
|
||||
string timeZone = calendarEvent.DtStart.TzId ?? "UTC";
|
||||
|
||||
return $"Every {daysOfWeek}, effective {calendarEvent.DtStart.Value.ToShortDateString()} " +
|
||||
$"from {calendarEvent.DtStart.Value.ToShortTimeString()} to {calendarEvent.DtEnd.Value.ToShortTimeString()} " +
|
||||
$"{timeZone}.";
|
||||
}
|
||||
|
||||
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||
{
|
||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||
|
||||
// Single event in a day.
|
||||
if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent)
|
||||
{
|
||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}";
|
||||
}
|
||||
else if (calendarItemViewModel.IsMultiDayEvent)
|
||||
{
|
||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// All day event.
|
||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})";
|
||||
}
|
||||
}
|
||||
|
||||
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
||||
CalendarItemViewModel calendarItemViewModel,
|
||||
CalendarDisplayType calendarDisplayType)
|
||||
{
|
||||
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
||||
|
||||
// All and/or multi day events always go to the top of the screen.
|
||||
if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom;
|
||||
|
||||
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<abstract:AppShellAbstract
|
||||
x:Class="Wino.Views.AppShell"
|
||||
<abstract:MailAppShellAbstract
|
||||
x:Class="Wino.Views.MailAppShell"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Views.Abstract"
|
||||
@@ -444,7 +444,7 @@
|
||||
|
||||
<!-- Main Content -->
|
||||
<Frame
|
||||
x:Name="ShellFrame"
|
||||
x:Name="InnerShellFrame"
|
||||
Padding="0,0,7,7"
|
||||
IsNavigationStackEnabled="False"
|
||||
Navigated="ShellFrameContentNavigated">
|
||||
@@ -475,4 +475,4 @@
|
||||
</muxc:NavigationView>
|
||||
|
||||
</Grid>
|
||||
</abstract:AppShellAbstract>
|
||||
</abstract:MailAppShellAbstract>
|
||||
@@ -9,7 +9,6 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
@@ -18,10 +17,10 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Mail.WinUI.Controls;
|
||||
using Wino.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Mail.WinUI.Controls;
|
||||
using Wino.MenuFlyouts;
|
||||
using Wino.MenuFlyouts.Context;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
@@ -32,7 +31,7 @@ using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views;
|
||||
|
||||
public sealed partial class AppShell : AppShellAbstract,
|
||||
public sealed partial class MailAppShell : MailAppShellAbstract,
|
||||
IRecipient<AccountMenuItemExtended>,
|
||||
IRecipient<NavigateMailFolderEvent>,
|
||||
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
|
||||
@@ -41,26 +40,11 @@ public sealed partial class AppShell : AppShellAbstract,
|
||||
[GeneratedDependencyProperty]
|
||||
public partial UIElement? TopShellContent { get; set; }
|
||||
|
||||
public AppShell() : base()
|
||||
public MailAppShell() : base()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
//protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
//{
|
||||
// base.OnNavigatedTo(e);
|
||||
|
||||
// WeakReferenceMessenger.Default.Register<InfoBarMessageRequested>(this);
|
||||
// WeakReferenceMessenger.Default.Register<AccountMenuItemExtended>(this);
|
||||
// WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this);
|
||||
// WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this);
|
||||
//}
|
||||
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
||||
{
|
||||
base.OnNavigatingFrom(e);
|
||||
}
|
||||
|
||||
private async void ItemDroppedOnFolder(object sender, DragEventArgs e)
|
||||
{
|
||||
// Validate package content.
|
||||
@@ -224,7 +208,7 @@ public sealed partial class AppShell : AppShellAbstract,
|
||||
{
|
||||
var navigateFolderArgs = new NavigateMailFolderEventArgs(message.BaseFolderMenuItem, message.FolderInitLoadAwaitTask);
|
||||
|
||||
ViewModel.NavigationService.Navigate(WinoPage.MailListPage, navigateFolderArgs, NavigationReferenceFrame.ShellFrame);
|
||||
ViewModel.NavigationService.Navigate(WinoPage.MailListPage, navigateFolderArgs, NavigationReferenceFrame.InnerShellFrame);
|
||||
|
||||
// Prevent double navigation.
|
||||
navigationView.SelectionChanged -= MenuSelectionChanged;
|
||||
@@ -330,15 +314,15 @@ public sealed partial class AppShell : AppShellAbstract,
|
||||
});
|
||||
}
|
||||
|
||||
private void NavigationViewDisplayModeChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewDisplayModeChangedEventArgs args)
|
||||
private void NavigationViewDisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
|
||||
{
|
||||
if (args.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal)
|
||||
if (args.DisplayMode == NavigationViewDisplayMode.Minimal)
|
||||
{
|
||||
ShellFrame.Margin = new Thickness(7, 0, 0, 0);
|
||||
InnerShellFrame.Margin = new Thickness(7, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShellFrame.Margin = new Thickness(0);
|
||||
InnerShellFrame.Margin = new Thickness(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="Wino.Mail.WinUI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Wino.Mail.WinUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="Wino.Mail.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<Grid />
|
||||
</Window>
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace Wino.Mail.WinUI;
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Calendar.Args;
|
||||
|
||||
/// <summary>
|
||||
/// When selected timeline cell is unselected.
|
||||
/// </summary>
|
||||
public class TimelineCellUnselectedArgs : EventArgs { }
|
||||
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.Selectors;
|
||||
|
||||
public partial class CustomAreaCalendarItemSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate AllDayTemplate { get; set; }
|
||||
public DataTemplate MultiDayTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
if (item is CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
return calendarItemViewModel.IsMultiDayEvent ? MultiDayTemplate : AllDayTemplate;
|
||||
}
|
||||
|
||||
return base.SelectTemplateCore(item, container);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Selectors;
|
||||
|
||||
public partial class WinoCalendarItemTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public CalendarDisplayType DisplayType { get; set; }
|
||||
|
||||
public DataTemplate DayWeekWorkWeekTemplate { get; set; }
|
||||
public DataTemplate MonthlyTemplate { get; set; }
|
||||
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
switch (DisplayType)
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
case CalendarDisplayType.Week:
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
return DayWeekWorkWeekTemplate;
|
||||
case CalendarDisplayType.Month:
|
||||
return MonthlyTemplate;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return base.SelectTemplateCore(item, container);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Mail.WinUI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulated state manager for collectively managing the state of account calendars.
|
||||
/// Callers must react to the events to update their state only from this service.
|
||||
/// </summary>
|
||||
public partial class AccountCalendarStateService : ObservableObject, IAccountCalendarStateService
|
||||
{
|
||||
public event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
||||
public event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; set; }
|
||||
|
||||
private ObservableCollection<GroupedAccountCalendarViewModel> _internalGroupedAccountCalendars = new ObservableCollection<GroupedAccountCalendarViewModel>();
|
||||
|
||||
public IEnumerable<AccountCalendarViewModel> ActiveCalendars
|
||||
{
|
||||
get
|
||||
{
|
||||
return GroupedAccountCalendars
|
||||
.SelectMany(a => a.AccountCalendars)
|
||||
.Where(b => b.IsChecked);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable
|
||||
{
|
||||
get
|
||||
{
|
||||
return GroupedAccountCalendars
|
||||
.Select(a => a.AccountCalendars)
|
||||
.SelectMany(b => b)
|
||||
.GroupBy(c => c.Account);
|
||||
}
|
||||
}
|
||||
|
||||
public AccountCalendarStateService()
|
||||
{
|
||||
GroupedAccountCalendars = new ReadOnlyObservableCollection<GroupedAccountCalendarViewModel>(_internalGroupedAccountCalendars);
|
||||
}
|
||||
|
||||
private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e)
|
||||
=> CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel);
|
||||
|
||||
private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e)
|
||||
=> AccountCalendarSelectionStateChanged?.Invoke(this, e);
|
||||
|
||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||
{
|
||||
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
||||
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
||||
|
||||
_internalGroupedAccountCalendars.Add(groupedAccountCalendar);
|
||||
}
|
||||
|
||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||
{
|
||||
groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged;
|
||||
groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged;
|
||||
|
||||
_internalGroupedAccountCalendars.Remove(groupedAccountCalendar);
|
||||
}
|
||||
|
||||
public void ClearGroupedAccountCalendar()
|
||||
{
|
||||
foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars)
|
||||
{
|
||||
RemoveGroupedAccountCalendar(groupedAccountCalendar);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar)
|
||||
{
|
||||
// Find the group that this calendar belongs to.
|
||||
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
// If the group doesn't exist, create it.
|
||||
group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar });
|
||||
AddGroupedAccountCalendar(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
group.AccountCalendars.Add(accountCalendar);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar)
|
||||
{
|
||||
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
||||
|
||||
// We don't expect but just in case.
|
||||
if (group == null) return;
|
||||
|
||||
group.AccountCalendars.Remove(accountCalendar);
|
||||
|
||||
if (group.AccountCalendars.Count == 0)
|
||||
{
|
||||
RemoveGroupedAccountCalendar(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,21 @@ using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Calendar.Views;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Mail.WinUI.Interfaces;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
using Wino.Helpers;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.ViewModels.Messages;
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Mail.WinUI.Interfaces;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
using Wino.Mail.WinUI.Views.Calendar;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Views;
|
||||
using Wino.Views.Account;
|
||||
using Wino.Views.Mail;
|
||||
using Wino.Views.Settings;
|
||||
|
||||
namespace Wino.Services;
|
||||
@@ -62,6 +65,8 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
||||
WinoPage.KeyboardShortcutsPage => typeof(KeyboardShortcutsPage),
|
||||
WinoPage.ContactsPage => typeof(ContactsPage),
|
||||
WinoPage.SignatureAndEncryptionPage => typeof(SignatureAndEncryptionPage),
|
||||
WinoPage.CalendarPage => typeof(CalendarPage),
|
||||
WinoPage.EventDetailsPage => typeof(EventDetailsPage),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
@@ -71,16 +76,36 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
||||
if (WinoApplication.MainWindow is not IWinoShellWindow shellWindow) throw new ArgumentException("MainWindow must implement IWinoShellWindow");
|
||||
if (shellWindow.GetMainFrame() is not Frame mainFrame) throw new ArgumentException("MainFrame cannot be null.");
|
||||
|
||||
if (frameType == NavigationReferenceFrame.ShellFrame) return shellWindow.GetMainFrame();
|
||||
|
||||
return WinoVisualTreeHelper.GetChildObject<Frame>(mainFrame.Content as UIElement, frameType.ToString());
|
||||
}
|
||||
|
||||
public bool ChangeApplicationMode(WinoApplicationMode mode)
|
||||
{
|
||||
var coreFrame = GetCoreFrame(NavigationReferenceFrame.ShellFrame);
|
||||
|
||||
if (coreFrame == null) return false;
|
||||
|
||||
if (mode == WinoApplicationMode.Mail)
|
||||
{
|
||||
coreFrame.Navigate(typeof(MailAppShell), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
coreFrame.Navigate(typeof(CalendarAppShell), null);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Navigate(WinoPage page,
|
||||
object? parameter = null,
|
||||
NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame,
|
||||
NavigationReferenceFrame frame = NavigationReferenceFrame.InnerShellFrame,
|
||||
NavigationTransitionType transition = NavigationTransitionType.None)
|
||||
{
|
||||
var pageType = GetPageType(page);
|
||||
Frame shellFrame = GetCoreFrame(NavigationReferenceFrame.ShellFrame);
|
||||
Frame shellFrame = GetCoreFrame(NavigationReferenceFrame.InnerShellFrame);
|
||||
|
||||
_statePersistanceService.IsReadingMail = _renderingPageTypes.Contains(page);
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,12 +2,12 @@ using System;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.UI;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Mail.WinUI.Interfaces;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
@@ -21,6 +21,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
|
||||
{
|
||||
public IStatePersistanceService StatePersistanceService { get; } = WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception("StatePersistanceService not registered in DI container.");
|
||||
public IPreferencesService PreferencesService { get; } = WinoApplication.Current.Services.GetService<IPreferencesService>() ?? throw new Exception("PreferencesService not registered in DI container.");
|
||||
public INavigationService NavigationService { get; } = WinoApplication.Current.Services.GetService<INavigationService>() ?? throw new Exception("NavigationService not registered in DI container.");
|
||||
|
||||
public ICommand ShowWinoCommand { get; set; }
|
||||
public ICommand ExitWinoCommand { get; set; }
|
||||
@@ -70,7 +71,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
|
||||
{
|
||||
// TODO: Handle protocol activations.
|
||||
|
||||
MainShellFrame.Navigate(typeof(AppShell));
|
||||
MainShellFrame.Navigate(typeof(MailAppShell));
|
||||
}
|
||||
|
||||
public Microsoft.UI.Xaml.Controls.TitleBar GetTitleBar() => ShellTitleBar;
|
||||
@@ -98,7 +99,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
|
||||
|
||||
public void Receive(TitleBarShellContentUpdated message)
|
||||
{
|
||||
if (MainShellFrame.Content is AppShell shellPage)
|
||||
if (MainShellFrame.Content is MailAppShell shellPage)
|
||||
{
|
||||
ShellTitleBar.Content = shellPage.TopShellContent;
|
||||
}
|
||||
@@ -161,6 +162,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
|
||||
|
||||
private void RestoreFromTray()
|
||||
{
|
||||
|
||||
this.Show();
|
||||
BringToFront();
|
||||
}
|
||||
@@ -193,4 +195,19 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient
|
||||
WeakReferenceMessenger.Default.Unregister<TitleBarShellContentUpdated>(this);
|
||||
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
||||
}
|
||||
|
||||
private void SegmentedChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is Segmented segmentedControl)
|
||||
{
|
||||
if (segmentedControl.SelectedIndex == 0)
|
||||
{
|
||||
NavigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Calendar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<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">
|
||||
<!-- CalendarControl -->
|
||||
<SolidColorBrush x:Key="CalendarSeperatorBrush">#b2bec3</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarFieldWorkingHoursBackgroundBrush">#dfe4ea</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarFieldSelectedBackgroundBrush">#2e86de</SolidColorBrush>
|
||||
|
||||
<!-- CalendarView -->
|
||||
<SolidColorBrush x:Key="WinoCalendarViewBorderBrush">#2e86de</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="WinoCalendarViewVisibleDayBackgroundBrush">#dfe4ea</SolidColorBrush>
|
||||
|
||||
<!-- Calendar Item Control -->
|
||||
<SolidColorBrush x:Key="CalendarItemBorderBrush">#000000</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarItemSelectedBorderBrush">#000000</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarItemDraggingBorderBrush">#000000</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
|
||||
<!-- CalendarControl -->
|
||||
<SolidColorBrush x:Key="CalendarSeperatorBrush">#525252</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarFieldWorkingHoursBackgroundBrush">#262626</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarFieldSelectedBackgroundBrush">#121212</SolidColorBrush>
|
||||
|
||||
<!-- CalendarView -->
|
||||
<SolidColorBrush x:Key="WinoCalendarViewBorderBrush">#3d3d3d</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="WinoCalendarViewVisibleDayBackgroundBrush">#4b4b4b</SolidColorBrush>
|
||||
|
||||
<!-- Calendar Item Control -->
|
||||
<SolidColorBrush x:Key="CalendarItemBorderBrush">#000000</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarItemSelectedBorderBrush">#FFFFFF</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="CalendarItemDraggingBorderBrush">#000000</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,424 @@
|
||||
<ResourceDictionary
|
||||
x:Class="Wino.Styles.WinoCalendarResources"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Calendar.Controls"
|
||||
xmlns:controls2="using:Wino.Mail.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:data="using:Wino.Calendar.ViewModels.Data"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:Wino.Core.Domain.Models.Calendar"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:selectors="using:Wino.Calendar.Selectors"
|
||||
xmlns:selectors1="using:Wino.Selectors"
|
||||
xmlns:toolkitControls="using:CommunityToolkit.WinUI.Controls">
|
||||
|
||||
<!-- 08:00 or 8 AM/PM on the left etc. -->
|
||||
<DataTemplate x:Key="DayCalendarHourHeaderTemplate" x:DataType="models:DayHeaderRenderModel">
|
||||
<Grid Height="{x:Bind HourHeight}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Text="{x:Bind DayHeader}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Vertical panel that renders items on canvas. -->
|
||||
<DataTemplate x:Key="DayCalendarItemVerticalRenderTemplate" x:DataType="models:CalendarDayModel">
|
||||
<ItemsControl x:Name="RegularEventItemsControl" ItemsSource="{x:Bind EventsCollection.RegularEvents}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<!-- Default Calendar Item View Model Template -->
|
||||
<DataTemplate x:DataType="data:CalendarItemViewModel">
|
||||
<controls:CalendarItemControl
|
||||
CalendarItem="{x:Bind}"
|
||||
DisplayingDate="{Binding ElementName=RegularEventItemsControl, Path=DataContext}"
|
||||
IsCustomEventArea="False" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemContainerTransitions>
|
||||
<TransitionCollection>
|
||||
<PaneThemeTransition Edge="Left" />
|
||||
</TransitionCollection>
|
||||
</ItemsControl.ItemContainerTransitions>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:WinoCalendarPanel HourHeight="{Binding Path=CalendarRenderOptions.CalendarSettings.HourHeight}" Period="{Binding Path=Period}" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Equally distributed days of week representation in FlipView. -->
|
||||
<!-- Used for day-week-work week templates. -->
|
||||
<!-- Horizontal template -->
|
||||
<DataTemplate x:Key="FlipTemplate" x:DataType="models:DayRangeRenderModel">
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
Background="Transparent"
|
||||
ColumnSpacing="0"
|
||||
RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" MinHeight="100" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ItemsControl 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>
|
||||
|
||||
<!-- Template that displays 35 days in total. -->
|
||||
<!-- Used for monthly view -->
|
||||
<!-- Vertical template -->
|
||||
<DataTemplate x:Key="MonthlyFlipTemplate" x:DataType="models:DayRangeRenderModel">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Height="20">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind CalendarDays}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:CalendarDayModel">
|
||||
<controls:DayColumnControl DayModel="{x:Bind Mode=OneWay}" Template="{StaticResource MonthlyColumnControlTemplate}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls2:EqualGridPanel Columns="7" Rows="5" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<ItemsPanelTemplate x:Key="VerticalFlipViewItemsPanel">
|
||||
<VirtualizingStackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
<ItemsPanelTemplate x:Key="HorizontalFlipViewItemsPanel">
|
||||
<VirtualizingStackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
|
||||
|
||||
|
||||
<!-- Default style for WinoCalendarControl -->
|
||||
<Style TargetType="controls:WinoCalendarControl">
|
||||
<Style.Setters>
|
||||
<Setter Property="HorizontalItemsPanelTemplate" Value="{StaticResource HorizontalFlipViewItemsPanel}" />
|
||||
<Setter Property="VerticalItemsPanelTemplate" Value="{StaticResource VerticalFlipViewItemsPanel}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:WinoCalendarControl">
|
||||
<Grid>
|
||||
<controls:WinoCalendarFlipView
|
||||
x:Name="PART_WinoFlipView"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
ActiveCanvas="{x:Bind ActiveCanvas, Mode=TwoWay}"
|
||||
ActiveVerticalScrollViewer="{x:Bind ActiveScrollViewer, Mode=TwoWay}"
|
||||
Background="Transparent"
|
||||
IsIdle="{x:Bind IsFlipIdle, Mode=TwoWay}"
|
||||
IsTabStop="False"
|
||||
ItemsSource="{TemplateBinding DayRanges}"
|
||||
SelectedIndex="{Binding SelectedFlipViewIndex, RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay}">
|
||||
<controls:WinoCalendarFlipView.ItemTemplateSelector>
|
||||
<selectors1:WinoCalendarItemTemplateSelector
|
||||
DayWeekWorkWeekTemplate="{StaticResource FlipTemplate}"
|
||||
DisplayType="{x:Bind DisplayType, Mode=OneWay}"
|
||||
MonthlyTemplate="{StaticResource MonthlyFlipTemplate}" />
|
||||
</controls:WinoCalendarFlipView.ItemTemplateSelector>
|
||||
</controls:WinoCalendarFlipView>
|
||||
|
||||
<Grid x:Name="PART_IdleGrid" Visibility="Collapsed">
|
||||
<muxc:ProgressRing
|
||||
Width="50"
|
||||
Height="50"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsActive="True" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
|
||||
<!-- Top header control for days. -->
|
||||
<ControlTemplate x:Key="DailyColumnControlTemplate" TargetType="controls:DayColumnControl">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="7" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Name of the day. Monday, Tuesday etc. at the top. -->
|
||||
<TextBlock
|
||||
x:Name="PART_ColumnHeaderText"
|
||||
Margin="8,0,0,0"
|
||||
FontSize="16"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
Grid.RowSpan="2"
|
||||
BorderBrush="{ThemeResource CalendarSeperatorBrush}"
|
||||
BorderThickness="1,1,0,1" />
|
||||
|
||||
<!-- Border for today indication. -->
|
||||
<Border
|
||||
x:Name="PART_IsTodayBorder"
|
||||
Grid.Row="1"
|
||||
Height="5"
|
||||
Margin="2,0,2,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource SystemAccentColor}"
|
||||
CornerRadius="2"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<!-- Place where full day events go. -->
|
||||
<Grid
|
||||
x:Name="PART_DayDataAreaGrid"
|
||||
Grid.Row="2"
|
||||
Padding="6"
|
||||
BorderBrush="{ThemeResource CalendarSeperatorBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="35" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Day number -->
|
||||
<TextBlock x:Name="PART_HeaderDateDayText" FontSize="17" />
|
||||
|
||||
<!-- Extras -->
|
||||
<StackPanel Grid.Column="1" HorizontalAlignment="Right" />
|
||||
|
||||
<!-- All-Multi Day Events -->
|
||||
<ItemsControl
|
||||
x:Name="PART_AllDayItemsControl"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,6">
|
||||
<ItemsControl.ItemTemplateSelector>
|
||||
<selectors:CustomAreaCalendarItemSelector>
|
||||
<selectors:CustomAreaCalendarItemSelector.AllDayTemplate>
|
||||
<DataTemplate x:DataType="data:CalendarItemViewModel">
|
||||
<controls:CalendarItemControl
|
||||
CalendarItem="{x:Bind}"
|
||||
DisplayingDate="{Binding DataContext, ElementName=PART_AllDayItemsControl}"
|
||||
IsCustomEventArea="True" />
|
||||
</DataTemplate>
|
||||
</selectors:CustomAreaCalendarItemSelector.AllDayTemplate>
|
||||
<selectors:CustomAreaCalendarItemSelector.MultiDayTemplate>
|
||||
<DataTemplate x:DataType="data:CalendarItemViewModel">
|
||||
<controls:CalendarItemControl
|
||||
CalendarItem="{x:Bind}"
|
||||
DisplayingDate="{Binding DataContext, ElementName=PART_AllDayItemsControl}"
|
||||
IsCustomEventArea="True" />
|
||||
</DataTemplate>
|
||||
</selectors:CustomAreaCalendarItemSelector.MultiDayTemplate>
|
||||
</selectors:CustomAreaCalendarItemSelector>
|
||||
</ItemsControl.ItemTemplateSelector>
|
||||
<ItemsControl.ItemContainerTransitions>
|
||||
<TransitionCollection>
|
||||
<AddDeleteThemeTransition />
|
||||
</TransitionCollection>
|
||||
</ItemsControl.ItemContainerTransitions>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" Spacing="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="TodayOrNotStates">
|
||||
<VisualState x:Name="NotTodayState" />
|
||||
<VisualState x:Name="TodayState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_IsTodayBorder.Visibility" Value="Visible" />
|
||||
<Setter Target="PART_HeaderDateDayText.Foreground" Value="{ThemeResource SystemAccentColor}" />
|
||||
<Setter Target="PART_HeaderDateDayText.FontWeight" Value="Semibold" />
|
||||
<Setter Target="PART_ColumnHeaderText.FontWeight" Value="Semibold" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- Monthly data control for months -->
|
||||
<ControlTemplate x:Key="MonthlyColumnControlTemplate" TargetType="controls:DayColumnControl">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="5" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Border for today indication. -->
|
||||
<Border
|
||||
x:Name="PART_IsTodayBorder"
|
||||
Grid.Row="0"
|
||||
Height="5"
|
||||
Margin="2,0,2,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource SystemAccentColor}"
|
||||
CornerRadius="2"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<!-- Border -->
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
BorderBrush="{ThemeResource CalendarSeperatorBrush}"
|
||||
BorderThickness="1,1,0,1" />
|
||||
|
||||
<!-- Place where full day events go. -->
|
||||
<Grid
|
||||
x:Name="PART_DayDataAreaGrid"
|
||||
Grid.Row="1"
|
||||
Padding="6"
|
||||
BorderBrush="{ThemeResource CalendarSeperatorBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" MinHeight="35" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Extras -->
|
||||
<StackPanel Grid.Column="1" HorizontalAlignment="Right" />
|
||||
|
||||
<!-- All events summary. -->
|
||||
<ScrollViewer
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,6"
|
||||
Padding="0,0,16,0">
|
||||
<ItemsControl x:Name="PART_AllDayItemsControl">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="data:CalendarItemViewModel">
|
||||
<controls:CalendarItemControl
|
||||
CalendarItem="{x:Bind}"
|
||||
DisplayingDate="{Binding DataContext, ElementName=PART_AllDayItemsControl}"
|
||||
IsCustomEventArea="True" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemContainerTransitions>
|
||||
<TransitionCollection>
|
||||
<AddDeleteThemeTransition />
|
||||
</TransitionCollection>
|
||||
</ItemsControl.ItemContainerTransitions>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" Spacing="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="TodayOrNotStates">
|
||||
<VisualState x:Name="NotTodayState" />
|
||||
<VisualState x:Name="TodayState">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_IsTodayBorder.Visibility" Value="Visible" />
|
||||
<Setter Target="PART_HeaderDateDayText.Foreground" Value="{ThemeResource SystemAccentColor}" />
|
||||
<Setter Target="PART_HeaderDateDayText.FontWeight" Value="Semibold" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- Default style for DayColumnControl -->
|
||||
<Style TargetType="controls:DayColumnControl">
|
||||
<Setter Property="Template" Value="{StaticResource DailyColumnControlTemplate}" />
|
||||
</Style>
|
||||
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Wino.Styles;
|
||||
|
||||
public sealed partial class WinoCalendarResources : ResourceDictionary
|
||||
{
|
||||
public WinoCalendarResources()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Calendar.Controls"
|
||||
xmlns:controls1="using:Wino.Mail.WinUI.Controls"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls">
|
||||
|
||||
<Style TargetType="controls:WinoCalendarTypeSelectorControl">
|
||||
<Style.Setters>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:WinoCalendarTypeSelectorControl">
|
||||
<CommandBar
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
DefaultLabelPosition="Right">
|
||||
|
||||
<CommandBar.PrimaryCommands>
|
||||
<!-- Today -->
|
||||
<AppBarButton
|
||||
x:Name="PART_TodayButton"
|
||||
Foreground="{ThemeResource ApplicationForegroundThemeBrush}"
|
||||
Label="Today">
|
||||
<AppBarButton.Icon>
|
||||
<controls1:WinoFontIcon Icon="CalendarToday" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<!-- Day -->
|
||||
<!-- TODO: Specific days -->
|
||||
<AppBarToggleButton
|
||||
x:Name="PART_DayToggle"
|
||||
Foreground="{ThemeResource ApplicationForegroundThemeBrush}"
|
||||
Label="Day">
|
||||
<AppBarToggleButton.Icon>
|
||||
<controls1:WinoFontIcon Icon="CalendarDay" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<!-- Week -->
|
||||
<!-- TODO: Work week -->
|
||||
|
||||
<AppBarToggleButton
|
||||
x:Name="PART_WeekToggle"
|
||||
Foreground="{ThemeResource ApplicationForegroundThemeBrush}"
|
||||
Label="Week">
|
||||
<AppBarToggleButton.Icon>
|
||||
<controls1:WinoFontIcon Icon="CalendarWeek" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<!-- Month -->
|
||||
<AppBarToggleButton
|
||||
x:Name="PART_MonthToggle"
|
||||
Foreground="{ThemeResource ApplicationForegroundThemeBrush}"
|
||||
Label="Month">
|
||||
<AppBarToggleButton.Icon>
|
||||
<controls1:WinoFontIcon FontSize="44" Icon="CalendarMonth" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
<!-- Year -->
|
||||
<AppBarToggleButton
|
||||
x:Name="PART_YearToggle"
|
||||
Foreground="{ThemeResource ApplicationForegroundThemeBrush}"
|
||||
Label="Year">
|
||||
<AppBarToggleButton.Icon>
|
||||
<controls1:WinoFontIcon Icon="CalendarYear" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
|
||||
</CommandBar.PrimaryCommands>
|
||||
</CommandBar>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style.Setters>
|
||||
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Calendar.Controls">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="CalendarThemeResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style TargetType="controls:WinoCalendarView">
|
||||
<Setter Property="VisibleDateBackground" Value="{ThemeResource WinoCalendarViewVisibleDayBackgroundBrush}" />
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:WinoCalendarView">
|
||||
<CalendarView
|
||||
x:Name="PART_CalendarView"
|
||||
BorderBrush="{ThemeResource WinoCalendarViewBorderBrush}"
|
||||
CalendarItemCornerRadius="5"
|
||||
CornerRadius="4"
|
||||
DayItemMargin="0"
|
||||
IsTodayHighlighted="False"
|
||||
SelectionMode="Single">
|
||||
<CalendarView.CalendarViewDayItemStyle>
|
||||
<Style TargetType="CalendarViewDayItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="CalendarViewDayItem">
|
||||
<Grid>
|
||||
<Border
|
||||
x:Name="PART_DayViewItemBorder"
|
||||
Margin="0,-1,0,-3"
|
||||
CornerRadius="5">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</CalendarView.CalendarViewDayItemStyle>
|
||||
</CalendarView>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -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,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Mail.Views.Abstract;
|
||||
|
||||
public abstract class CalendarAppShellAbstract : BasePage<CalendarAppShellViewModel> { }
|
||||
@@ -0,0 +1,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public abstract class CalendarPageAbstract : BasePage<CalendarPageViewModel> { }
|
||||
@@ -0,0 +1,5 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
|
||||
namespace Wino.Mail.WinUI.Views.Abstract;
|
||||
|
||||
public abstract class EventDetailsPageAbstract : BasePage<EventDetailsPageViewModel> { }
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Wino.Mail.WinUI;
|
||||
using Wino.Mail.ViewModels;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Views.Abstract;
|
||||
|
||||
public abstract class AppShellAbstract : BasePage<AppShellViewModel>
|
||||
public abstract class MailAppShellAbstract : BasePage<MailAppShellViewModel>
|
||||
{
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,66 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Mail.Views.Abstract;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Mail.WinUI.Views.Calendar;
|
||||
|
||||
public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
|
||||
IRecipient<CalendarDisplayTypeChangedMessage>
|
||||
{
|
||||
private const string STATE_HorizontalCalendar = "HorizontalCalendar";
|
||||
private const string STATE_VerticalCalendar = "VerticalCalendar";
|
||||
|
||||
public Frame GetShellFrame() => InnerShellFrame;
|
||||
|
||||
public CalendarAppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Window.Current.SetTitleBar(DragArea);
|
||||
ManageCalendarDisplayType();
|
||||
}
|
||||
|
||||
private void ManageCalendarDisplayType()
|
||||
{
|
||||
// Go to different states based on the display type.
|
||||
if (ViewModel.IsVerticalCalendar)
|
||||
{
|
||||
VisualStateManager.GoToState(this, STATE_VerticalCalendar, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
|
||||
|
||||
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
|
||||
|
||||
public void Receive(CalendarDisplayTypeChangedMessage message)
|
||||
{
|
||||
ManageCalendarDisplayType();
|
||||
}
|
||||
|
||||
//private void ShellFrameContentNavigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||
// => RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
|
||||
|
||||
//private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args)
|
||||
// => ViewModel.NavigationService.GoBack();
|
||||
|
||||
protected override void RegisterRecipients()
|
||||
{
|
||||
base.RegisterRecipients();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<CalendarDisplayTypeChangedMessage>(this);
|
||||
}
|
||||
|
||||
protected override void UnregisterRecipients()
|
||||
{
|
||||
base.UnregisterRecipients();
|
||||
|
||||
WeakReferenceMessenger.Default.Unregister<CalendarDisplayTypeChangedMessage>(this);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Wino.Calendar.Args;
|
||||
using Wino.Calendar.Views.Abstract;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Views;
|
||||
|
||||
public sealed partial class CalendarPage : CalendarPageAbstract,
|
||||
IRecipient<ScrollToDateMessage>,
|
||||
IRecipient<ScrollToHourMessage>,
|
||||
IRecipient<GoNextDateRequestedMessage>,
|
||||
IRecipient<GoPreviousDateRequestedMessage>
|
||||
{
|
||||
private const int PopupDialogOffset = 12;
|
||||
|
||||
public CalendarPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
|
||||
ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged;
|
||||
}
|
||||
|
||||
private void CalendarItemDetailContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (ViewModel.DisplayDetailsCalendarItemViewModel != null)
|
||||
{
|
||||
var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel);
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
EventDetailsPopup.PlacementTarget = control;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(ScrollToHourMessage message) => CalendarControl.NavigateToHour(message.TimeSpan);
|
||||
public void Receive(ScrollToDateMessage message) => CalendarControl.NavigateToDay(message.Date);
|
||||
public void Receive(GoNextDateRequestedMessage message) => CalendarControl.GoNextRange();
|
||||
public void Receive(GoPreviousDateRequestedMessage message) => CalendarControl.GoPreviousRange();
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
if (e.NavigationMode == NavigationMode.Back) return;
|
||||
|
||||
if (e.Parameter is CalendarPageNavigationArgs args)
|
||||
{
|
||||
if (args.RequestDefaultNavigation)
|
||||
{
|
||||
// Go today.
|
||||
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(DateTime.Now.Date, CalendarInitInitiative.App));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Go specified date.
|
||||
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(args.NavigationDate, CalendarInitInitiative.User));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CellSelected(object sender, TimelineCellSelectedArgs e)
|
||||
{
|
||||
// Dismiss event details if exists and cancel the selection.
|
||||
// This is to prevent the event details from being displayed when the user clicks somewhere else.
|
||||
|
||||
if (EventDetailsPopup.IsOpen)
|
||||
{
|
||||
CalendarControl.UnselectActiveTimelineCell();
|
||||
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ViewModel.SelectedQuickEventDate = e.ClickedDate;
|
||||
|
||||
TeachingTipPositionerGrid.Width = e.CellSize.Width;
|
||||
TeachingTipPositionerGrid.Height = e.CellSize.Height;
|
||||
|
||||
Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X);
|
||||
Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y);
|
||||
|
||||
// Adjust the start and end time in the flyout.
|
||||
var startTime = ViewModel.SelectedQuickEventDate.Value.TimeOfDay;
|
||||
var endTime = startTime.Add(TimeSpan.FromMinutes(30));
|
||||
|
||||
ViewModel.SelectQuickEventTimeRange(startTime, endTime);
|
||||
|
||||
QuickEventPopupDialog.IsOpen = true;
|
||||
}
|
||||
|
||||
private void CellUnselected(object sender, TimelineCellUnselectedArgs e)
|
||||
{
|
||||
QuickEventPopupDialog.IsOpen = false;
|
||||
}
|
||||
|
||||
private void QuickEventAccountSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
QuickEventAccountSelectorFlyout.Hide();
|
||||
}
|
||||
|
||||
private void QuickEventPopupClosed(object sender, object e)
|
||||
{
|
||||
// Reset the timeline selection when the tip is closed.
|
||||
CalendarControl.ResetTimelineSelection();
|
||||
}
|
||||
|
||||
private void PopupPlacementChanged(object sender, object e)
|
||||
{
|
||||
if (sender is Popup senderPopup)
|
||||
{
|
||||
// When the quick event Popup is positioned for different calendar types,
|
||||
// we must adjust the offset to make sure the tip is not hidden and has nice
|
||||
// spacing from the cell.
|
||||
|
||||
switch (senderPopup.ActualPlacement)
|
||||
{
|
||||
case PopupPlacementMode.Top:
|
||||
senderPopup.VerticalOffset = PopupDialogOffset * -1;
|
||||
break;
|
||||
case PopupPlacementMode.Bottom:
|
||||
senderPopup.VerticalOffset = PopupDialogOffset;
|
||||
break;
|
||||
case PopupPlacementMode.Left:
|
||||
senderPopup.HorizontalOffset = PopupDialogOffset * -1;
|
||||
break;
|
||||
case PopupPlacementMode.Right:
|
||||
senderPopup.HorizontalOffset = PopupDialogOffset;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void StartTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
|
||||
=> ViewModel.SelectedStartTimeString = args.Text;
|
||||
|
||||
private void EndTimeDurationSubmitted(ComboBox sender, ComboBoxTextSubmittedEventArgs args)
|
||||
=> ViewModel.SelectedEndTimeString = args.Text;
|
||||
|
||||
private void EventDetailsPopupClosed(object sender, object e)
|
||||
{
|
||||
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
||||
}
|
||||
|
||||
private void CalendarScrolling(object sender, EventArgs e)
|
||||
{
|
||||
// In case of scrolling, we must dismiss the event details dialog.
|
||||
ViewModel.DisplayDetailsCalendarItemViewModel = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<abstract:EventDetailsPageAbstract
|
||||
x:Class="Wino.Calendar.Views.EventDetailsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract"
|
||||
xmlns:calendar="using:Wino.Core.Domain.Entities.Calendar"
|
||||
xmlns:calendarHelpers="using:Wino.Calendar.Helpers"
|
||||
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:helpers="using:Wino.Helpers"
|
||||
xmlns:local="using:Wino.Calendar.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Style="{StaticResource PageStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<Style x:Key="ActionBarElementContainerStackStyle" TargetType="StackPanel">
|
||||
<Setter Property="Spacing" Value="6" />
|
||||
<Setter Property="Padding" Value="10,0,4,0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="AppBarElementContainer">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="EventDetailsPanelGridStyle" TargetType="Grid">
|
||||
<Setter Property="Padding" Value="0,12" />
|
||||
<Setter Property="Margin" Value="12,0" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="EventDetailsPanelTitleStyle"
|
||||
BasedOn="{StaticResource SubtitleTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="Margin" Value="0,0,0,20" />
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
<Grid Padding="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Action Bar -->
|
||||
<Border
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource WinoContentZoneBackgroud}"
|
||||
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="7">
|
||||
<CommandBar
|
||||
HorizontalAlignment="Left"
|
||||
Background="Transparent"
|
||||
DefaultLabelPosition="Right"
|
||||
IsSticky="True"
|
||||
OverflowButtonVisibility="Auto">
|
||||
<AppBarToggleButton
|
||||
x:Name="ReadOnlyToggle"
|
||||
Content="Read-only event"
|
||||
IsChecked="True" />
|
||||
<AppBarButton Label="{x:Bind domain:Translator.Buttons_Save}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="Save" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
<AppBarButton Label="{x:Bind domain:Translator.Buttons_Delete}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="Delete" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<!-- Join Online -->
|
||||
<AppBarButton Label="Join Online">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="EventJoinOnline" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<!-- Join Options -->
|
||||
<AppBarButton Label="Accept">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Foreground="#527257" Icon="EventAccept" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton Label="Tentative">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Foreground="#805682" Icon="EventTentative" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton Label="Decline">
|
||||
<AppBarButton.Icon>
|
||||
<PathIcon Data="F1 M 10.253906 9.375 L 16.064453 15.185547 C 16.18815 15.309245 16.25 15.455729 16.25 15.625 C 16.25 15.794271 16.18815 15.940756 16.064453 16.064453 C 15.940754 16.188152 15.79427 16.25 15.625 16.25 C 15.455729 16.25 15.309244 16.188152 15.185547 16.064453 L 9.375 10.253906 L 3.564453 16.064453 C 3.440755 16.188152 3.294271 16.25 3.125 16.25 C 2.955729 16.25 2.809245 16.188152 2.685547 16.064453 C 2.561849 15.940756 2.5 15.794271 2.5 15.625 C 2.5 15.455729 2.561849 15.309245 2.685547 15.185547 L 8.496094 9.375 L 2.685547 3.564453 C 2.561849 3.440756 2.5 3.294271 2.5 3.125 C 2.5 2.95573 2.561849 2.809246 2.685547 2.685547 C 2.809245 2.56185 2.955729 2.5 3.125 2.5 C 3.294271 2.5 3.440755 2.56185 3.564453 2.685547 L 9.375 8.496094 L 15.185547 2.685547 C 15.309244 2.56185 15.455729 2.5 15.625 2.5 C 15.79427 2.5 15.940754 2.56185 16.064453 2.685547 C 16.18815 2.809246 16.25 2.95573 16.25 3.125 C 16.25 3.294271 16.18815 3.440756 16.064453 3.564453 Z " Foreground="#d94b4e" />
|
||||
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarButton Label="Respond">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Foreground="#805682" Icon="EventRespond" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<!-- Show as -->
|
||||
<AppBarElementContainer>
|
||||
<StackPanel Style="{StaticResource ActionBarElementContainerStackStyle}">
|
||||
<TextBlock VerticalAlignment="Center" Text="Show as" />
|
||||
<ComboBox Width="150" />
|
||||
</StackPanel>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<!-- Reminder -->
|
||||
<AppBarElementContainer>
|
||||
<StackPanel Style="{StaticResource ActionBarElementContainerStackStyle}">
|
||||
<coreControls:WinoFontIcon FontSize="16" Icon="Reminder" />
|
||||
<TextBlock VerticalAlignment="Center" Text="Reminder" />
|
||||
<ComboBox Width="150" />
|
||||
</StackPanel>
|
||||
</AppBarElementContainer>
|
||||
|
||||
<AppBarSeparator />
|
||||
|
||||
<!-- Edit Series -->
|
||||
<AppBarButton Label="Edit Series">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="EventEditSeries" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
</CommandBar>
|
||||
</Border>
|
||||
|
||||
<!-- Event details -->
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Details -->
|
||||
<Grid x:Name="DetailsGrid" Style="{StaticResource EventDetailsPanelGridStyle}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="Details" />
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<!-- Read-Only Event -->
|
||||
<Grid
|
||||
x:Name="ReadOnlyDetailsGrid"
|
||||
RowSpacing="6"
|
||||
Visibility="{x:Bind ReadOnlyToggle.IsChecked, Mode=OneWay}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="16" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock
|
||||
Style="{StaticResource SubheaderTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.CurrentEvent.Title, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Date and Duration -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetEventDetailsDateString(ViewModel.CurrentEvent, ViewModel.CurrentSettings), Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Recurrence Info -->
|
||||
<Grid
|
||||
Grid.Row="3"
|
||||
ColumnSpacing="6"
|
||||
Visibility="{x:Bind ViewModel.CurrentEvent.IsRecurringEvent}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<coreControls:WinoFontIcon FontSize="14" Icon="CalendarEventRepeat" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetRecurrenceString(ViewModel.CurrentEvent), Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Editable Event -->
|
||||
<Grid Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(ReadOnlyDetailsGrid.Visibility), Mode=OneWay}">
|
||||
<TextBlock Text="editing" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- People -->
|
||||
<Grid
|
||||
x:Name="PeopleGrid"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource EventDetailsPanelGridStyle}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="People" />
|
||||
|
||||
<Grid Grid.Row="1" RowSpacing="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<AutoSuggestBox
|
||||
Margin="6,0"
|
||||
BorderThickness="0"
|
||||
PlaceholderText="Invite someone" />
|
||||
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
IsItemClickEnabled="True"
|
||||
ItemsSource="{x:Bind ViewModel.CurrentEvent.Attendees, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="calendar:CalendarEventAttendee">
|
||||
<Grid Margin="0,6" ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<PersonPicture
|
||||
Width="40"
|
||||
Height="40"
|
||||
DisplayName="{x:Bind Name}" />
|
||||
|
||||
<!-- TODO: Organizer -->
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
FontSize="13"
|
||||
Text="{x:Bind Email}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Attachments -->
|
||||
<Grid
|
||||
x:Name="AttachmentsGrid"
|
||||
Grid.Column="2"
|
||||
Style="{StaticResource EventDetailsPanelGridStyle}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="Attachments" />
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</abstract:EventDetailsPageAbstract>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Wino.Mail.WinUI.Views.Abstract;
|
||||
|
||||
namespace Wino.Calendar.Views;
|
||||
|
||||
public sealed partial class EventDetailsPage : EventDetailsPageAbstract
|
||||
{
|
||||
public EventDetailsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
+30
-24
@@ -1,5 +1,5 @@
|
||||
<abstract:ComposePageAbstract
|
||||
x:Class="Wino.Views.ComposePage"
|
||||
x:Class="Wino.Views.Mail.ComposePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Views.Abstract"
|
||||
@@ -439,31 +439,34 @@
|
||||
</ToggleSplitButton>
|
||||
</AppBarElementContainer>
|
||||
<AppBarElementContainer>
|
||||
<muxc:ToggleSplitButton x:Name="SignatureToggleButton"
|
||||
Margin="8,0,0,0"
|
||||
IsChecked="{x:Bind ViewModel.IsSmimeSignatureEnabled, Mode=TwoWay}"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeSignature}"
|
||||
IsEnabled="{x:Bind ViewModel.AreCertificatesAvailable, Mode=OneWay}">
|
||||
<muxc:ToggleSplitButton
|
||||
x:Name="SignatureToggleButton"
|
||||
Margin="8,0,0,0"
|
||||
IsChecked="{x:Bind ViewModel.IsSmimeSignatureEnabled, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.AreCertificatesAvailable, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeSignature}">
|
||||
<muxc:ToggleSplitButton.Content>
|
||||
<Viewbox Width="16" Height="16" Margin="0,0,4,0">
|
||||
<PathIcon
|
||||
Data="M15 18c.835.629 1.875 1.001 3 1.001a4.978 4.978 0 0 0 3-.999v3.246a.75.75 0 0 1-1.09.67l-.09-.055L18 20.591l-1.82 1.272a.75.75 0 0 1-1.172-.51l-.007-.105L15 18.001Zm4.25-14.996a2.75 2.75 0 0 1 2.745 2.582l.005.168.001 5.246a5.027 5.027 0 0 0-1.5-1.331L20.5 5.754a1.25 1.25 0 0 0-1.122-1.244l-.128-.006H4.75a1.25 1.25 0 0 0-1.244 1.122l-.006.128v9.5c0 .647.492 1.18 1.122 1.243l.128.007h8.92c.1.172.21.338.33.496v1.004H4.75a2.75 2.75 0 0 1-2.745-2.583L2 15.254v-9.5a2.75 2.75 0 0 1 2.582-2.745l.168-.005h14.5ZM18 10A4 4 0 1 1 18 18 4 4 0 0 1 18 10Zm-6.75 2.5a.75.75 0 0 1 .102 1.493L11.25 14h-4.5a.75.75 0 0 1-.102-1.493l.102-.007h4.5Zm6-5.5a.75.75 0 0 1 .102 1.493l-.102.007H6.75a.75.75 0 0 1-.102-1.493L6.75 7h10.5Z" />
|
||||
<Viewbox
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,4,0">
|
||||
<PathIcon Data="M15 18c.835.629 1.875 1.001 3 1.001a4.978 4.978 0 0 0 3-.999v3.246a.75.75 0 0 1-1.09.67l-.09-.055L18 20.591l-1.82 1.272a.75.75 0 0 1-1.172-.51l-.007-.105L15 18.001Zm4.25-14.996a2.75 2.75 0 0 1 2.745 2.582l.005.168.001 5.246a5.027 5.027 0 0 0-1.5-1.331L20.5 5.754a1.25 1.25 0 0 0-1.122-1.244l-.128-.006H4.75a1.25 1.25 0 0 0-1.244 1.122l-.006.128v9.5c0 .647.492 1.18 1.122 1.243l.128.007h8.92c.1.172.21.338.33.496v1.004H4.75a2.75 2.75 0 0 1-2.745-2.583L2 15.254v-9.5a2.75 2.75 0 0 1 2.582-2.745l.168-.005h14.5ZM18 10A4 4 0 1 1 18 18 4 4 0 0 1 18 10Zm-6.75 2.5a.75.75 0 0 1 .102 1.493L11.25 14h-4.5a.75.75 0 0 1-.102-1.493l.102-.007h4.5Zm6-5.5a.75.75 0 0 1 .102 1.493l-.102.007H6.75a.75.75 0 0 1-.102-1.493L6.75 7h10.5Z" />
|
||||
</Viewbox>
|
||||
</muxc:ToggleSplitButton.Content>
|
||||
<muxc:ToggleSplitButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<ListView
|
||||
Width="320"
|
||||
ItemsSource="{x:Bind ViewModel.AvailableCertificates, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedSigningCertificate, Mode=TwoWay}"
|
||||
Width="320">
|
||||
SelectedItem="{x:Bind ViewModel.SelectedSigningCertificate, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="x509:X509Certificate2" xmlns:x509="using:System.Security.Cryptography.X509Certificates">
|
||||
<StackPanel Orientation="Vertical" Padding="8,4">
|
||||
<TextBlock Text="{x:Bind Subject}" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{x:Bind Issuer}" FontSize="12" />
|
||||
<DataTemplate xmlns:x509="using:System.Security.Cryptography.X509Certificates" x:DataType="x509:X509Certificate2">
|
||||
<StackPanel Padding="8,4" Orientation="Vertical">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Subject}" />
|
||||
<TextBlock FontSize="12" Text="{x:Bind Issuer}" />
|
||||
<TextBlock>
|
||||
<Run Text="{x:Bind domain:Translator.Composer_CertificateExpires}"/>
|
||||
<Run Text="{x:Bind NotAfter, Mode=OneWay}"/>
|
||||
<Run Text="{x:Bind domain:Translator.Composer_CertificateExpires}" />
|
||||
<Run Text="{x:Bind NotAfter, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
@@ -474,14 +477,17 @@
|
||||
</muxc:ToggleSplitButton>
|
||||
</AppBarElementContainer>
|
||||
<AppBarElementContainer>
|
||||
<ToggleButton x:Name="EncryptionToggleButton"
|
||||
Margin="8,0,0,0"
|
||||
IsChecked="{x:Bind ViewModel.IsSmimeEncryptionEnabled, Mode=TwoWay}"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeEncryption}">
|
||||
<ToggleButton
|
||||
x:Name="EncryptionToggleButton"
|
||||
Margin="8,0,0,0"
|
||||
IsChecked="{x:Bind ViewModel.IsSmimeEncryptionEnabled, Mode=TwoWay}"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeEncryption}">
|
||||
<ToggleButton.Content>
|
||||
<Viewbox Width="16" Height="16" Margin="0,0,4,0">
|
||||
<PathIcon
|
||||
Data="M12 2a4 4 0 0 1 4 4v2h1.75A2.25 2.25 0 0 1 20 10.25v9.5A2.25 2.25 0 0 1 17.75 22H6.25A2.25 2.25 0 0 1 4 19.75v-9.5A2.25 2.25 0 0 1 6.25 8H8V6a4 4 0 0 1 4-4Zm5.75 7.5H6.25a.75.75 0 0 0-.75.75v9.5c0 .414.336.75.75.75h11.5a.75.75 0 0 0 .75-.75v-9.5a.75.75 0 0 0-.75-.75Zm-5.75 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-10A2.5 2.5 0 0 0 9.5 6v2h5V6A2.5 2.5 0 0 0 12 3.5Z" />
|
||||
<Viewbox
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,4,0">
|
||||
<PathIcon Data="M12 2a4 4 0 0 1 4 4v2h1.75A2.25 2.25 0 0 1 20 10.25v9.5A2.25 2.25 0 0 1 17.75 22H6.25A2.25 2.25 0 0 1 4 19.75v-9.5A2.25 2.25 0 0 1 6.25 8H8V6a4 4 0 0 1 4-4Zm5.75 7.5H6.25a.75.75 0 0 0-.75.75v9.5c0 .414.336.75.75.75h11.5a.75.75 0 0 0 .75-.75v-9.5a.75.75 0 0 0-.75-.75Zm-5.75 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-10A2.5 2.5 0 0 0 9.5 6v2h5V6A2.5 2.5 0 0 0 12 3.5Z" />
|
||||
</Viewbox>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
+4
-4
@@ -20,13 +20,13 @@ using Windows.UI.Core.Preview;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Models.Reader;
|
||||
using Wino.Mail.WinUI.Extensions;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.WinUI.Extensions;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views;
|
||||
namespace Wino.Views.Mail;
|
||||
|
||||
public sealed partial class ComposePage : ComposePageAbstract,
|
||||
IRecipient<CreateNewComposeMailRequested>,
|
||||
@@ -308,8 +308,8 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
||||
if (sender is Button senderButton && senderButton.Tag is MessageImportance importance)
|
||||
{
|
||||
ViewModel.SelectedMessageImportance = importance;
|
||||
if (ImportanceSplitButton.Content is Viewbox viewbox &&
|
||||
viewbox.Child is SymbolIcon symbolIcon &&
|
||||
if (ImportanceSplitButton.Content is Viewbox viewbox &&
|
||||
viewbox.Child is SymbolIcon symbolIcon &&
|
||||
senderButton.Content is SymbolIcon contentIcon)
|
||||
{
|
||||
symbolIcon.Symbol = contentIcon.Symbol;
|
||||
@@ -1,5 +1,5 @@
|
||||
<abstract:IdlePageAbstract
|
||||
x:Class="Wino.Views.IdlePage"
|
||||
x:Class="Wino.Views.Mail.IdlePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Views.Abstract"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views;
|
||||
namespace Wino.Views.Mail;
|
||||
|
||||
public sealed partial class IdlePage : IdlePageAbstract
|
||||
{
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<abstract:MailListPageAbstract
|
||||
x:Class="Wino.Views.MailListPage"
|
||||
x:Class="Wino.Views.Mail.MailListPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Views.Abstract"
|
||||
+1
-1
@@ -30,7 +30,7 @@ using Wino.MenuFlyouts.Context;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views;
|
||||
namespace Wino.Views.Mail;
|
||||
|
||||
public sealed partial class MailListPage : MailListPageAbstract,
|
||||
IRecipient<ClearMailSelectionsRequested>,
|
||||
+30
-23
@@ -1,5 +1,5 @@
|
||||
<abstract:MailRenderingPageAbstract
|
||||
x:Class="Wino.Views.MailRenderingPage"
|
||||
x:Class="Wino.Views.Mail.MailRenderingPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:abstract="using:Wino.Views.Abstract"
|
||||
@@ -209,46 +209,53 @@
|
||||
</HyperlinkButton>
|
||||
|
||||
|
||||
<!-- S/MIME Signed Email Indicator -->
|
||||
<!-- S/MIME Signed Email Indicator -->
|
||||
<HyperlinkButton
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Center"
|
||||
Command="{x:Bind ViewModel.ShowSmimeSigningCertificateInfoCommand}"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.SmimeSignedTooltip}"
|
||||
Visibility="{x:Bind ViewModel.IsSmimeSigned, Mode=OneWay}"
|
||||
Command="{x:Bind ViewModel.ShowSmimeSigningCertificateInfoCommand}">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Height="Auto">
|
||||
<Viewbox Width="16" Height="16" Margin="0,0,4,0">
|
||||
<PathIcon
|
||||
Data="M15 18c.835.629 1.875 1.001 3 1.001a4.978 4.978 0 0 0 3-.999v3.246a.75.75 0 0 1-1.09.67l-.09-.055L18 20.591l-1.82 1.272a.75.75 0 0 1-1.172-.51l-.007-.105L15 18.001Zm4.25-14.996a2.75 2.75 0 0 1 2.745 2.582l.005.168.001 5.246a5.027 5.027 0 0 0-1.5-1.331L20.5 5.754a1.25 1.25 0 0 0-1.122-1.244l-.128-.006H4.75a1.25 1.25 0 0 0-1.244 1.122l-.006.128v9.5c0 .647.492 1.18 1.122 1.243l.128.007h8.92c.1.172.21.338.33.496v1.004H4.75a2.75 2.75 0 0 1-2.745-2.583L2 15.254v-9.5a2.75 2.75 0 0 1 2.582-2.745l.168-.005h14.5ZM18 10A4 4 0 1 1 18 18 4 4 0 0 1 18 10Zm-6.75 2.5a.75.75 0 0 1 .102 1.493L11.25 14h-4.5a.75.75 0 0 1-.102-1.493l.102-.007h4.5Zm6-5.5a.75.75 0 0 1 .102 1.493l-.102.007H6.75a.75.75 0 0 1-.102-1.493L6.75 7h10.5Z" />
|
||||
Visibility="{x:Bind ViewModel.IsSmimeSigned, Mode=OneWay}">
|
||||
<Grid
|
||||
Width="Auto"
|
||||
Height="Auto"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Viewbox
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,4,0">
|
||||
<PathIcon Data="M15 18c.835.629 1.875 1.001 3 1.001a4.978 4.978 0 0 0 3-.999v3.246a.75.75 0 0 1-1.09.67l-.09-.055L18 20.591l-1.82 1.272a.75.75 0 0 1-1.172-.51l-.007-.105L15 18.001Zm4.25-14.996a2.75 2.75 0 0 1 2.745 2.582l.005.168.001 5.246a5.027 5.027 0 0 0-1.5-1.331L20.5 5.754a1.25 1.25 0 0 0-1.122-1.244l-.128-.006H4.75a1.25 1.25 0 0 0-1.244 1.122l-.006.128v9.5c0 .647.492 1.18 1.122 1.243l.128.007h8.92c.1.172.21.338.33.496v1.004H4.75a2.75 2.75 0 0 1-2.745-2.583L2 15.254v-9.5a2.75 2.75 0 0 1 2.582-2.745l.168-.005h14.5ZM18 10A4 4 0 1 1 18 18 4 4 0 0 1 18 10Zm-6.75 2.5a.75.75 0 0 1 .102 1.493L11.25 14h-4.5a.75.75 0 0 1-.102-1.493l.102-.007h4.5Zm6-5.5a.75.75 0 0 1 .102 1.493l-.102.007H6.75a.75.75 0 0 1-.102-1.493L6.75 7h10.5Z" />
|
||||
</Viewbox>
|
||||
<muxc:InfoBadge
|
||||
Visibility="{x:Bind ViewModel.SmimeSignaturesInvalid, Mode=OneWay}"
|
||||
Width="8"
|
||||
Height="8"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Style="{ThemeResource CautionIconInfoBadgeStyle}"/>
|
||||
Style="{ThemeResource CautionIconInfoBadgeStyle}"
|
||||
Visibility="{x:Bind ViewModel.SmimeSignaturesInvalid, Mode=OneWay}" />
|
||||
<muxc:InfoBadge
|
||||
Visibility="{x:Bind ViewModel.SmimeSignaturesValid, Mode=OneWay}"
|
||||
Width="8"
|
||||
Height="8"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Style="{ThemeResource SuccessIconInfoBadgeStyle}"/>
|
||||
Style="{ThemeResource SuccessIconInfoBadgeStyle}"
|
||||
Visibility="{x:Bind ViewModel.SmimeSignaturesValid, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</HyperlinkButton>
|
||||
|
||||
<!-- S/MIME Encrypted Email Indicator -->
|
||||
<Viewbox Width="16" Height="16"
|
||||
Grid.Column="3"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,4"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.SmimeEncryptedTooltip}"
|
||||
Visibility="{x:Bind ViewModel.IsSmimeEncrypted, Mode=OneWay}">
|
||||
<PathIcon
|
||||
Data="M12 2a4 4 0 0 1 4 4v2h1.75A2.25 2.25 0 0 1 20 10.25v9.5A2.25 2.25 0 0 1 17.75 22H6.25A2.25 2.25 0 0 1 4 19.75v-9.5A2.25 2.25 0 0 1 6.25 8H8V6a4 4 0 0 1 4-4Zm5.75 7.5H6.25a.75.75 0 0 0-.75.75v9.5c0 .414.336.75.75.75h11.5a.75.75 0 0 0 .75-.75v-9.5a.75.75 0 0 0-.75-.75Zm-5.75 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-10A2.5 2.5 0 0 0 9.5 6v2h5V6A2.5 2.5 0 0 0 12 3.5Z" />
|
||||
</Viewbox>
|
||||
<!-- S/MIME Encrypted Email Indicator -->
|
||||
<Viewbox
|
||||
Grid.Column="3"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Center"
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.SmimeEncryptedTooltip}"
|
||||
Visibility="{x:Bind ViewModel.IsSmimeEncrypted, Mode=OneWay}">
|
||||
<PathIcon Data="M12 2a4 4 0 0 1 4 4v2h1.75A2.25 2.25 0 0 1 20 10.25v9.5A2.25 2.25 0 0 1 17.75 22H6.25A2.25 2.25 0 0 1 4 19.75v-9.5A2.25 2.25 0 0 1 6.25 8H8V6a4 4 0 0 1 4-4Zm5.75 7.5H6.25a.75.75 0 0 0-.75.75v9.5c0 .414.336.75.75.75h11.5a.75.75 0 0 0 .75-.75v-9.5a.75.75 0 0 0-.75-.75Zm-5.75 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-10A2.5 2.5 0 0 0 9.5 6v2h5V6A2.5 2.5 0 0 0 12 3.5Z" />
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
<CommandBar
|
||||
x:Name="RendererBar"
|
||||
+1
-1
@@ -21,7 +21,7 @@ using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views;
|
||||
namespace Wino.Views.Mail;
|
||||
|
||||
public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
||||
IRecipient<HtmlRenderingRequested>,
|
||||
@@ -90,6 +90,15 @@
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\ListView\WinoListViewStyles.xaml" />
|
||||
<None Remove="ShellWindow.xaml" />
|
||||
<None Remove="Styles\CalendarThemeResources.xaml" />
|
||||
<None Remove="Styles\DayHeaderControl.xaml" />
|
||||
<None Remove="Styles\WinoCalendarResources.xaml" />
|
||||
<None Remove="Styles\WinoCalendarTypeSelectorControl.xaml" />
|
||||
<None Remove="Styles\WinoCalendarView.xaml" />
|
||||
<None Remove="Styles\WinoDayTimelineCanvas.xaml" />
|
||||
<None Remove="Views\Calendar\CalendarAppShell.xaml" />
|
||||
<None Remove="Views\Calendar\CalendarPage.xaml" />
|
||||
<None Remove="Views\Calendar\EventDetailsPage.xaml" />
|
||||
<None Remove="Views\Settings\ContactsPage.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -152,6 +161,7 @@
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core.ViewModels\Wino.Core.ViewModels.csproj" />
|
||||
<ProjectReference Include="..\Wino.Mail.ViewModels\Wino.Mail.ViewModels.csproj" />
|
||||
@@ -173,7 +183,7 @@
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="AppShell.xaml">
|
||||
<Page Update="MailAppShell.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Controls\MailItemDisplayInformationControl.xaml">
|
||||
@@ -279,6 +289,40 @@
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\Mail\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\CalendarThemeResources.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Styles\DayHeaderControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Styles\WinoCalendarResources.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Styles\WinoCalendarTypeSelectorControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Styles\WinoCalendarView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Styles\WinoDayTimelineCanvas.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\Calendar\CalendarAppShell.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Views\Calendar\CalendarPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Views\Calendar\EventDetailsPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
|
||||
@@ -13,4 +13,4 @@ public record NewMailSynchronizationRequested(MailSynchronizationOptions Options
|
||||
/// Triggers a new calendar synchronization if possible.
|
||||
/// </summary>
|
||||
/// <param name="Options">Options for synchronization.</param>
|
||||
public record NewCalendarSynchronizationRequested(CalendarSynchronizationOptions Options) : IClientMessage;
|
||||
public record NewCalendarSynchronizationRequested(CalendarSynchronizationOptions Options) : IClientMessage, IUIMessage;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
@@ -43,11 +44,6 @@ public class DatabaseService : IDatabaseService
|
||||
|
||||
private async Task CreateTablesAsync()
|
||||
{
|
||||
//typeof(AccountCalendar),
|
||||
// typeof(CalendarEventAttendee),
|
||||
// typeof(CalendarItem),
|
||||
// typeof(Reminder),
|
||||
|
||||
await Task.WhenAll(
|
||||
Connection.CreateTableAsync<MailCopy>(),
|
||||
Connection.CreateTableAsync<MailItemFolder>(),
|
||||
@@ -59,7 +55,11 @@ public class DatabaseService : IDatabaseService
|
||||
Connection.CreateTableAsync<MailAccountPreferences>(),
|
||||
Connection.CreateTableAsync<MailAccountAlias>(),
|
||||
Connection.CreateTableAsync<Thumbnail>(),
|
||||
Connection.CreateTableAsync<KeyboardShortcut>()
|
||||
Connection.CreateTableAsync<KeyboardShortcut>(),
|
||||
Connection.CreateTableAsync<AccountCalendar>(),
|
||||
Connection.CreateTableAsync<CalendarEventAttendee>(),
|
||||
Connection.CreateTableAsync<CalendarItem>(),
|
||||
Connection.CreateTableAsync<Reminder>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -6,7 +6,6 @@
|
||||
</Configurations>
|
||||
<Folder Name="/Solution Items/" Id="8ec462fd-d22e-90a8-e5ce-7e832ba40c5d">
|
||||
<File Path=".editorconfig" />
|
||||
<File Path=".github/copilot-instructions.md" />
|
||||
<File Path="Directory.Build.Props" />
|
||||
<File Path="Directory.Packages.props" />
|
||||
<File Path="nuget.config" />
|
||||
@@ -18,6 +17,11 @@
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
<Platform Solution="*|x86" Project="x86" />
|
||||
</Project>
|
||||
<Project Path="Wino.Calendar.ViewModels/Wino.Calendar.ViewModels.csproj">
|
||||
<Platform Solution="*|arm64" Project="arm64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
<Platform Solution="*|x86" Project="x86" />
|
||||
</Project>
|
||||
<Project Path="Wino.Core.Domain/Wino.Core.Domain.csproj">
|
||||
<BuildDependency Project="Wino.SourceGenerators/Wino.SourceGenerators.csproj" />
|
||||
<Platform Solution="*|arm64" Project="arm64" />
|
||||
|
||||
Reference in New Issue
Block a user