Compare commits
98 Commits
v1.10.1
...
feature/Ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db85bfa39 | ||
|
|
03c9ac1e11 | ||
|
|
1ee0063b62 | ||
|
|
3e889d8c08 | ||
|
|
a01395aed3 | ||
|
|
7b3459abff | ||
|
|
9a88f798fc | ||
|
|
256fd1cce2 | ||
|
|
a8cb332232 | ||
|
|
89ea2b23a2 | ||
|
|
9b214a66c8 | ||
|
|
4c4689ec8d | ||
|
|
c4e561dee6 | ||
|
|
69bfe5b750 | ||
|
|
137b3dc2ea | ||
|
|
ea5f879181 | ||
|
|
25d5f34f68 | ||
|
|
c8a6df77ac | ||
|
|
7b6ac46b6a | ||
|
|
d77c648d54 | ||
|
|
c3f47c5fa1 | ||
|
|
f37a51b46f | ||
|
|
9feb3f35c3 | ||
|
|
5b44cf03ce | ||
|
|
86a6382463 | ||
|
|
df991a3829 | ||
|
|
f243c86b50 | ||
|
|
b77be0a5e9 | ||
|
|
83be587c1a | ||
|
|
c6048aea80 | ||
|
|
13b495b0f6 | ||
|
|
ac64c35efa | ||
|
|
127b58601f | ||
|
|
1f795b45e9 | ||
|
|
d26e35ee9a | ||
|
|
70e69e9dac | ||
|
|
3d88f4212d | ||
|
|
ad90a9c8f3 | ||
|
|
b43176764b | ||
|
|
77f24282e0 | ||
|
|
533f1f1102 | ||
|
|
92c5d8bd44 | ||
|
|
d754ecb486 | ||
|
|
b18987a95c | ||
|
|
0daec61f31 | ||
|
|
8ecf301eb8 | ||
|
|
6080646e89 | ||
|
|
970a521b66 | ||
|
|
9b5a92f942 | ||
|
|
c4e0f13d67 | ||
|
|
b6821746d0 | ||
|
|
b98fc91a99 | ||
|
|
bd7f7b867e | ||
|
|
32a3fea8d7 | ||
|
|
3561beab1d | ||
|
|
1d1fd52cae | ||
|
|
c4ba438150 | ||
|
|
37f0ee08b1 | ||
|
|
240b02c94e | ||
|
|
e8142ff3df | ||
|
|
832b363da7 | ||
|
|
cf8f1ecd67 | ||
|
|
ee5129830c | ||
|
|
9facfaffa8 | ||
|
|
31b859ba1a | ||
|
|
b0f5a24c30 | ||
|
|
b60b594e44 | ||
|
|
a8cee1016b | ||
|
|
b551af01fa | ||
|
|
b178869a8e | ||
|
|
8e1c60d5f0 | ||
|
|
71ea49439e | ||
|
|
9d0a2f6535 | ||
|
|
c091fffe90 | ||
|
|
7e05d05f94 | ||
|
|
bd5b51c62f | ||
|
|
1d5eb2eced | ||
|
|
5073ead8fe | ||
|
|
f61bcb621b | ||
|
|
42b695854b | ||
|
|
496ae8b1b2 | ||
|
|
4215a2592f | ||
|
|
bca62033a1 | ||
|
|
18a91f9223 | ||
|
|
474d7c7a26 | ||
|
|
3f9a51ff46 | ||
|
|
df3b5c41f9 | ||
|
|
8800d11ab0 | ||
|
|
f021834ceb | ||
|
|
f54a39a549 | ||
|
|
c312ff3faf | ||
|
|
db833594f4 | ||
|
|
d36cf59829 | ||
|
|
caae751698 | ||
|
|
f7836eedce | ||
|
|
3ddc1a6229 | ||
|
|
cf9869b71e | ||
|
|
d31d8f574e |
@@ -149,7 +149,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_prefer_simple_using_statement = true:suggestion
|
||||
csharp_style_namespace_declarations = block_scoped:silent
|
||||
csharp_style_namespace_declarations = file_scoped:error
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
@@ -287,4 +287,6 @@ csharp_style_prefer_top_level_statements = true:silent
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
csharp_style_prefer_readonly_struct = true:suggestion
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
|
||||
csharp_style_prefer_primary_constructors = true:silent
|
||||
csharp_prefer_system_threading_lock = true:suggestion
|
||||
@@ -1,64 +1,67 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="ColorHashSharp" Version="1.0.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Diagnostics" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.Uwp.Controls.MarkdownTextBlock" Version="0.1.250206-build.2040" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Animations" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Behaviors" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Segmented" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.SettingsControls" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Sizers" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.TabbedCommandBar" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.TokenizingTextBox" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Extensions" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="EmailValidation" Version="1.2.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.72" />
|
||||
<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.12.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Graph" Version="5.69.0" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client" Version="4.68.0" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.68.0" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.68.0" />
|
||||
<PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
|
||||
<PackageVersion Include="Microsoft.UI.Xaml" Version="2.8.7" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Uwp.Managed" Version="3.0.0" />
|
||||
<PackageVersion Include="MimeKit" Version="4.10.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.1" />
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Exceptions" Version="8.4.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.ApplicationInsights" Version="4.0.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="3.116.1" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="SqlKata" Version="4.0.1" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
|
||||
<PackageVersion Include="Win2D.uwp" Version="1.28.2" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.2.0" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Google.Apis.Auth" Version="1.69.0" />
|
||||
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.69.0.3667" />
|
||||
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.68.0.3427" />
|
||||
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.68.0.3359" />
|
||||
<PackageVersion Include="HtmlKit" Version="1.2.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.10.0" />
|
||||
<PackageVersion Include="TimePeriodLibrary.NET" Version="2.1.5" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="ColorHashSharp" Version="1.0.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Common" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Diagnostics" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.Uwp.Controls.MarkdownTextBlock" Version="0.1.250206-build.2040" />
|
||||
<PackageVersion Include="CommunityToolkit.Labs.Uwp.DependencyPropertyGenerator" Version="0.1.250206-build.2040" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Animations" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Behaviors" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Sizers" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.TabbedCommandBar" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.TokenizingTextBox" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Extensions" Version="8.2.250402" />
|
||||
<PackageVersion Include="CommunityToolkit.Uwp.Controls.Primitives" Version="8.2.250129-preview2" />
|
||||
<PackageVersion Include="EmailValidation" Version="1.3.0" />
|
||||
<PackageVersion Include="gravatar-dotnet" Version="0.1.3" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.12.0" />
|
||||
<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.13.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
|
||||
<PackageVersion Include="Microsoft.Graph" Version="5.75.0" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client" Version="4.70.1" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.70.1" />
|
||||
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.70.1" />
|
||||
<PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
|
||||
<PackageVersion Include="Microsoft.UI.Xaml" Version="2.8.7" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Uwp.Managed" Version="3.0.0" />
|
||||
<PackageVersion Include="MimeKit" Version="4.11.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="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Exceptions" Version="8.4.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.ApplicationInsights" Version="4.0.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="3.116.1" />
|
||||
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageVersion Include="SqlKata" Version="4.0.1" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.4" />
|
||||
<PackageVersion Include="Win2D.uwp" Version="1.28.2" />
|
||||
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Google.Apis.Auth" Version="1.69.0" />
|
||||
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.69.0.3667" />
|
||||
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.68.0.3427" />
|
||||
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.68.0.3359" />
|
||||
<PackageVersion Include="HtmlKit" Version="1.2.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.11.0" />
|
||||
<PackageVersion Include="TimePeriodLibrary.NET" Version="2.1.6" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,17 +1,16 @@
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Authentication
|
||||
namespace Wino.Authentication;
|
||||
|
||||
public abstract class BaseAuthenticator
|
||||
{
|
||||
public abstract class BaseAuthenticator
|
||||
public abstract MailProviderType ProviderType { get; }
|
||||
protected IAuthenticatorConfig AuthenticatorConfig { get; }
|
||||
|
||||
protected BaseAuthenticator(IAuthenticatorConfig authenticatorConfig)
|
||||
{
|
||||
public abstract MailProviderType ProviderType { get; }
|
||||
protected IAuthenticatorConfig AuthenticatorConfig { get; }
|
||||
|
||||
protected BaseAuthenticator(IAuthenticatorConfig authenticatorConfig)
|
||||
{
|
||||
|
||||
AuthenticatorConfig = authenticatorConfig;
|
||||
}
|
||||
AuthenticatorConfig = authenticatorConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,45 +7,44 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
|
||||
namespace Wino.Authentication
|
||||
namespace Wino.Authentication;
|
||||
|
||||
public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
|
||||
{
|
||||
public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
|
||||
public GmailAuthenticator(IAuthenticatorConfig authConfig) : base(authConfig)
|
||||
{
|
||||
public GmailAuthenticator(IAuthenticatorConfig authConfig) : base(authConfig)
|
||||
}
|
||||
|
||||
public string ClientId => AuthenticatorConfig.GmailAuthenticatorClientId;
|
||||
public bool ProposeCopyAuthURL { get; set; }
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Gmail;
|
||||
|
||||
/// <summary>
|
||||
/// Generates the token information for the given account.
|
||||
/// For gmail, interactivity is automatically handled when you get the token.
|
||||
/// </summary>
|
||||
/// <param name="account">Account to get token for.</param>
|
||||
public Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
|
||||
=> GetTokenInformationAsync(account);
|
||||
|
||||
public async Task<TokenInformationEx> GetTokenInformationAsync(MailAccount account)
|
||||
{
|
||||
var userCredential = await GetGoogleUserCredentialAsync(account);
|
||||
|
||||
if (userCredential.Token.IsStale)
|
||||
{
|
||||
await userCredential.RefreshTokenAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
public string ClientId => AuthenticatorConfig.GmailAuthenticatorClientId;
|
||||
public bool ProposeCopyAuthURL { get; set; }
|
||||
return new TokenInformationEx(userCredential.Token.AccessToken, account.Address);
|
||||
}
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Gmail;
|
||||
|
||||
/// <summary>
|
||||
/// Generates the token information for the given account.
|
||||
/// For gmail, interactivity is automatically handled when you get the token.
|
||||
/// </summary>
|
||||
/// <param name="account">Account to get token for.</param>
|
||||
public Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
|
||||
=> GetTokenInformationAsync(account);
|
||||
|
||||
public async Task<TokenInformationEx> GetTokenInformationAsync(MailAccount account)
|
||||
private Task<UserCredential> GetGoogleUserCredentialAsync(MailAccount account)
|
||||
{
|
||||
return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets()
|
||||
{
|
||||
var userCredential = await GetGoogleUserCredentialAsync(account);
|
||||
|
||||
if (userCredential.Token.IsStale)
|
||||
{
|
||||
await userCredential.RefreshTokenAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
return new TokenInformationEx(userCredential.Token.AccessToken, account.Address);
|
||||
}
|
||||
|
||||
private Task<UserCredential> GetGoogleUserCredentialAsync(MailAccount account)
|
||||
{
|
||||
return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets()
|
||||
{
|
||||
ClientId = ClientId
|
||||
}, AuthenticatorConfig.GmailScope, account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
|
||||
}
|
||||
ClientId = ClientId
|
||||
}, AuthenticatorConfig.GmailScope, account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,116 +11,116 @@ using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
|
||||
namespace Wino.Authentication
|
||||
namespace Wino.Authentication;
|
||||
|
||||
public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
{
|
||||
public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
private const string TokenCacheFileName = "OutlookCache.bin";
|
||||
private bool isTokenCacheAttached = false;
|
||||
|
||||
// Outlook
|
||||
private const string Authority = "https://login.microsoftonline.com/common";
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
||||
|
||||
private readonly IPublicClientApplication _publicClientApplication;
|
||||
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||
|
||||
public OutlookAuthenticator(INativeAppService nativeAppService,
|
||||
IApplicationConfiguration applicationConfiguration,
|
||||
IAuthenticatorConfig authenticatorConfig) : base(authenticatorConfig)
|
||||
{
|
||||
private const string TokenCacheFileName = "OutlookCache.bin";
|
||||
private bool isTokenCacheAttached = false;
|
||||
_applicationConfiguration = applicationConfiguration;
|
||||
|
||||
// Outlook
|
||||
private const string Authority = "https://login.microsoftonline.com/common";
|
||||
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
||||
|
||||
private readonly IPublicClientApplication _publicClientApplication;
|
||||
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||
|
||||
public OutlookAuthenticator(INativeAppService nativeAppService,
|
||||
IApplicationConfiguration applicationConfiguration,
|
||||
IAuthenticatorConfig authenticatorConfig) : base(authenticatorConfig)
|
||||
var options = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
|
||||
{
|
||||
_applicationConfiguration = applicationConfiguration;
|
||||
Title = "Wino Mail",
|
||||
ListOperatingSystemAccounts = true,
|
||||
};
|
||||
|
||||
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
|
||||
var outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId)
|
||||
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd)
|
||||
.WithBroker(options)
|
||||
.WithDefaultRedirectUri()
|
||||
.WithAuthority(Authority);
|
||||
|
||||
var options = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
|
||||
{
|
||||
Title = "Wino Mail",
|
||||
ListOperatingSystemAccounts = true,
|
||||
};
|
||||
_publicClientApplication = outlookAppBuilder.Build();
|
||||
}
|
||||
|
||||
var outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId)
|
||||
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd)
|
||||
.WithBroker(options)
|
||||
.WithDefaultRedirectUri()
|
||||
.WithAuthority(Authority);
|
||||
public string[] Scope => AuthenticatorConfig.OutlookScope;
|
||||
|
||||
_publicClientApplication = outlookAppBuilder.Build();
|
||||
}
|
||||
|
||||
public string[] Scope => AuthenticatorConfig.OutlookScope;
|
||||
|
||||
private async Task EnsureTokenCacheAttachedAsync()
|
||||
private async Task EnsureTokenCacheAttachedAsync()
|
||||
{
|
||||
if (!isTokenCacheAttached)
|
||||
{
|
||||
if (!isTokenCacheAttached)
|
||||
{
|
||||
var storageProperties = new StorageCreationPropertiesBuilder(TokenCacheFileName, _applicationConfiguration.PublisherSharedFolderPath).Build();
|
||||
var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
|
||||
msalcachehelper.RegisterCache(_publicClientApplication.UserTokenCache);
|
||||
var storageProperties = new StorageCreationPropertiesBuilder(TokenCacheFileName, _applicationConfiguration.PublisherSharedFolderPath).Build();
|
||||
var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
|
||||
msalcachehelper.RegisterCache(_publicClientApplication.UserTokenCache);
|
||||
|
||||
isTokenCacheAttached = true;
|
||||
}
|
||||
isTokenCacheAttached = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TokenInformationEx> GetTokenInformationAsync(MailAccount account)
|
||||
public async Task<TokenInformationEx> GetTokenInformationAsync(MailAccount account)
|
||||
{
|
||||
await EnsureTokenCacheAttachedAsync();
|
||||
|
||||
var storedAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(
|
||||
a => string.Equals(a.Username?.Trim(), account.Address?.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (storedAccount == null)
|
||||
return await GenerateTokenInformationAsync(account);
|
||||
|
||||
try
|
||||
{
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(Scope, storedAccount).ExecuteAsync();
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
catch (MsalUiRequiredException)
|
||||
{
|
||||
// Somehow MSAL is not able to refresh the token silently.
|
||||
// Force interactive login.
|
||||
|
||||
return await GenerateTokenInformationAsync(account);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
|
||||
{
|
||||
try
|
||||
{
|
||||
await EnsureTokenCacheAttachedAsync();
|
||||
|
||||
var storedAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address);
|
||||
var authResult = await _publicClientApplication
|
||||
.AcquireTokenInteractive(Scope)
|
||||
.ExecuteAsync();
|
||||
|
||||
if (storedAccount == null)
|
||||
return await GenerateTokenInformationAsync(account);
|
||||
// If the account is null, it means it's the initial creation of it.
|
||||
// If not, make sure the authenticated user address matches the username.
|
||||
// When people refresh their token, accounts must match.
|
||||
|
||||
try
|
||||
if (account?.Address != null && !account.Address.Equals(authResult.Account.Username, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(Scope, storedAccount).ExecuteAsync();
|
||||
throw new AuthenticationException("Authenticated address does not match with your account address. If you are signing with a Office365, it is not officially supported yet.");
|
||||
}
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
catch (MsalUiRequiredException)
|
||||
{
|
||||
// Somehow MSAL is not able to refresh the token silently.
|
||||
// Force interactive login.
|
||||
|
||||
return await GenerateTokenInformationAsync(account);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
|
||||
public async Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
|
||||
catch (MsalClientException msalClientException)
|
||||
{
|
||||
try
|
||||
{
|
||||
await EnsureTokenCacheAttachedAsync();
|
||||
if (msalClientException.ErrorCode == "authentication_canceled" || msalClientException.ErrorCode == "access_denied")
|
||||
throw new AccountSetupCanceledException();
|
||||
|
||||
var authResult = await _publicClientApplication
|
||||
.AcquireTokenInteractive(Scope)
|
||||
.ExecuteAsync();
|
||||
|
||||
// If the account is null, it means it's the initial creation of it.
|
||||
// If not, make sure the authenticated user address matches the username.
|
||||
// When people refresh their token, accounts must match.
|
||||
|
||||
if (account?.Address != null && !account.Address.Equals(authResult.Account.Username, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new AuthenticationException("Authenticated address does not match with your account address.");
|
||||
}
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
catch (MsalClientException msalClientException)
|
||||
{
|
||||
if (msalClientException.ErrorCode == "authentication_canceled" || msalClientException.ErrorCode == "access_denied")
|
||||
throw new AccountSetupCanceledException();
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
throw new AuthenticationException(Translator.Exception_UnknowErrorDuringAuthentication, new Exception(Translator.Exception_TokenGenerationFailed));
|
||||
throw;
|
||||
}
|
||||
|
||||
throw new AuthenticationException(Translator.Exception_UnknowErrorDuringAuthentication, new Exception(Translator.Exception_TokenGenerationFailed));
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,90 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="58272BurakKSE.WinoCalendar"
|
||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Wino Calendar</DisplayName>
|
||||
<PublisherDisplayName>Burak KÖSE</PublisherDisplayName>
|
||||
<Logo>Images\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Wino Calendar"
|
||||
Description="Wino.Calendar.Packaging"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<!-- Registration of full trust backend application. -->
|
||||
<uap:Extension Category="windows.appService">
|
||||
<uap:AppService Name="WinoInteropService" />
|
||||
</uap:Extension>
|
||||
|
||||
<!-- Protocol activation: Google OAuth -->
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="google.pw.oauth2">
|
||||
<uap:DisplayName>Wino Google Authentication Protocol</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
|
||||
<!-- Protocol activation: Launch UWP app from Full Trust Process -->
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="wino.calendar.launch">
|
||||
<uap:DisplayName>Wino Calendara Launcher Protocol</uap:DisplayName>
|
||||
</uap:Protocol>
|
||||
</uap:Extension>
|
||||
|
||||
<!-- Startup Task -->
|
||||
<uap5:Extension
|
||||
Category="windows.startupTask"
|
||||
Executable="Wino.Server\Wino.Server.exe"
|
||||
EntryPoint="Windows.FullTrustApplication">
|
||||
<uap5:StartupTask
|
||||
TaskId="WinoServer"
|
||||
Enabled="false"
|
||||
DisplayName="Wino Mail" />
|
||||
</uap5:Extension>
|
||||
|
||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="Wino.Server\Wino.Server.exe">
|
||||
<desktop:FullTrustProcess>
|
||||
<desktop:ParameterGroup GroupId="WinoServer" Parameters="Calendar" />
|
||||
</desktop:FullTrustProcess>
|
||||
</desktop:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="confirmAppClose" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|AnyCPU">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|AnyCPU">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>7485b18c-f5ab-4abe-ba7f-05b6623c67c8</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.22621.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
|
||||
<NoWarn>$(NoWarn);NU1702</NoWarn>
|
||||
<EntryPointProjectUniqueName>..\Wino.Calendar\Wino.Calendar.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\StoreLogo.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Calendar\Wino.Calendar.csproj" />
|
||||
<ProjectReference Include="..\Wino.Server\Wino.Server.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,48 +1,37 @@
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
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.UI;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class AccountDetailsPageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
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)
|
||||
{
|
||||
private readonly IAccountService _accountService;
|
||||
CalendarDialogService = calendarDialogService;
|
||||
_accountService = accountService;
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
}
|
||||
|
||||
public AccountProviderDetailViewModel Account { get; private set; }
|
||||
public ICalendarDialogService CalendarDialogService { get; }
|
||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||
[RelayCommand]
|
||||
private void EditAccountDetails()
|
||||
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsEditAccountDetails_Title, WinoPage.EditAccountDetailsPage, Account));
|
||||
|
||||
public AccountDetailsPageViewModel(ICalendarDialogService calendarDialogService, IAccountService accountService, IAccountCalendarStateService accountCalendarStateService)
|
||||
{
|
||||
CalendarDialogService = calendarDialogService;
|
||||
_accountService = accountService;
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task RenameAccount()
|
||||
{
|
||||
if (Account == null)
|
||||
return;
|
||||
|
||||
var updatedAccount = await CalendarDialogService.ShowEditAccountDialogAsync(Account.Account);
|
||||
|
||||
if (updatedAccount != null)
|
||||
{
|
||||
await _accountService.UpdateAccountAsync(updatedAccount);
|
||||
|
||||
ReportUIChange(new AccountUpdatedMessage(updatedAccount));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
}
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -13,135 +14,144 @@ using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
||||
{
|
||||
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)
|
||||
{
|
||||
private readonly IProviderService _providerService;
|
||||
CalendarDialogService = dialogService;
|
||||
_providerService = 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)
|
||||
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(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var accountDetails = GetAccountProviderDetails(account);
|
||||
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;
|
||||
Accounts.Add(accountDetails);
|
||||
}
|
||||
});
|
||||
|
||||
var availableProviders = _providerService.GetAvailableProviders();
|
||||
await ManageStorePurchasesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var accountCreationDialogResult = await DialogService.ShowAccountProviderSelectionDialogAsync(availableProviders);
|
||||
[RelayCommand]
|
||||
private async Task AddNewAccountAsync()
|
||||
{
|
||||
if (IsAccountCreationBlocked)
|
||||
{
|
||||
var isPurchaseClicked = await DialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_AccountLimitMessage, Translator.DialogMessage_AccountLimitTitle, Translator.Buttons_Purchase);
|
||||
|
||||
if (accountCreationDialogResult == null) return;
|
||||
if (!isPurchaseClicked) return;
|
||||
|
||||
var accountCreationCancellationTokenSource = new CancellationTokenSource();
|
||||
var accountCreationDialog = CalendarDialogService.GetAccountCreationDialog(accountCreationDialogResult);
|
||||
await PurchaseUnlimitedAccountAsync();
|
||||
|
||||
accountCreationDialog.ShowDialog(accountCreationCancellationTokenSource);
|
||||
accountCreationDialog.State = AccountCreationDialogState.SigningIn;
|
||||
return;
|
||||
}
|
||||
|
||||
// For OAuth authentications, we just generate token and assign it to the MailAccount.
|
||||
var availableProviders = _providerService.GetAvailableProviders();
|
||||
|
||||
var createdAccount = new MailAccount()
|
||||
{
|
||||
ProviderType = accountCreationDialogResult.ProviderType,
|
||||
Name = accountCreationDialogResult.AccountName,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
var accountCreationDialogResult = await DialogService.ShowAccountProviderSelectionDialogAsync(availableProviders);
|
||||
|
||||
var tokenInformationResponse = await WinoServerConnectionManager
|
||||
.GetResponseAsync<TokenInformationEx, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
|
||||
createdAccount,
|
||||
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
|
||||
if (accountCreationDialogResult == null) return;
|
||||
|
||||
if (accountCreationDialog.State == AccountCreationDialogState.Canceled)
|
||||
throw new AccountSetupCanceledException();
|
||||
var accountCreationCancellationTokenSource = new CancellationTokenSource();
|
||||
var accountCreationDialog = CalendarDialogService.GetAccountCreationDialog(accountCreationDialogResult);
|
||||
|
||||
tokenInformationResponse.ThrowIfFailed();
|
||||
await accountCreationDialog.ShowDialogAsync(accountCreationCancellationTokenSource);
|
||||
await Task.Delay(500);
|
||||
|
||||
await AccountService.CreateAccountAsync(createdAccount, null);
|
||||
// For OAuth authentications, we just generate token and assign it to the MailAccount.
|
||||
|
||||
// Sync profile information if supported.
|
||||
if (createdAccount.IsProfileInfoSyncSupported)
|
||||
{
|
||||
// Start profile information synchronization.
|
||||
// It's only available for Outlook and Gmail synchronizers.
|
||||
var createdAccount = new MailAccount()
|
||||
{
|
||||
ProviderType = accountCreationDialogResult.ProviderType,
|
||||
Name = accountCreationDialogResult.AccountName,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var profileSyncOptions = new MailSynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = MailSynchronizationType.UpdateProfile
|
||||
};
|
||||
var tokenInformationResponse = await WinoServerConnectionManager
|
||||
.GetResponseAsync<TokenInformationEx, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
|
||||
createdAccount,
|
||||
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
|
||||
|
||||
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
||||
if (accountCreationDialog.State == AccountCreationDialogState.Canceled)
|
||||
throw new AccountSetupCanceledException();
|
||||
|
||||
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
||||
tokenInformationResponse.ThrowIfFailed();
|
||||
|
||||
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
||||
await AccountService.CreateAccountAsync(createdAccount, null);
|
||||
|
||||
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
|
||||
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
|
||||
// Sync profile information if supported.
|
||||
if (createdAccount.IsProfileInfoSyncSupported)
|
||||
{
|
||||
// Start profile information synchronization.
|
||||
// It's only available for Outlook and Gmail synchronizers.
|
||||
|
||||
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
|
||||
}
|
||||
|
||||
accountCreationDialog.State = AccountCreationDialogState.FetchingEvents;
|
||||
|
||||
// Start synchronizing events.
|
||||
var synchronizationOptions = new CalendarSynchronizationOptions()
|
||||
var profileSyncOptions = new MailSynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
Type = MailSynchronizationType.UpdateProfile
|
||||
};
|
||||
|
||||
var synchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(new NewCalendarSynchronizationRequested(synchronizationOptions, SynchronizationSource.Client));
|
||||
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 timer = new Stopwatch();
|
||||
|
||||
var synchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(new NewCalendarSynchronizationRequested(synchronizationOptions, SynchronizationSource.Client));
|
||||
|
||||
timer.Stop();
|
||||
|
||||
Debug.WriteLine("Synchronization completed in {timer.ElapsedMilliseconds} ms");
|
||||
|
||||
// TODO: Properly handle synchronization errors.
|
||||
|
||||
accountCreationDialog.Complete(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,347 +21,346 @@ using Wino.Messaging.Client.Calendar;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||
IRecipient<VisibleDateRangeChangedMessage>,
|
||||
IRecipient<CalendarEnableStatusChangedMessage>,
|
||||
IRecipient<NavigateManageAccountsRequested>,
|
||||
IRecipient<CalendarDisplayTypeChangedMessage>,
|
||||
IRecipient<DetailsPageStateChangedMessage>
|
||||
{
|
||||
public partial class AppShellViewModel : CalendarBaseViewModel,
|
||||
IRecipient<VisibleDateRangeChangedMessage>,
|
||||
IRecipient<CalendarEnableStatusChangedMessage>,
|
||||
IRecipient<NavigateManageAccountsRequested>,
|
||||
IRecipient<CalendarDisplayTypeChangedMessage>,
|
||||
IRecipient<DetailsPageStateChangedMessage>
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
public IStatePersistanceService StatePersistenceService { get; }
|
||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||
public INavigationService NavigationService { get; }
|
||||
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEventDetailsPageActive;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedMenuItemIndex = -1;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isCalendarEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active connection status of the Wino server.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private WinoServerConnectionStatus activeConnectionStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display date of the calendar.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private DateTimeOffset _displayDate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private DateRange highlightedDateRange;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableRangeCollection<string> dateNavigationHeaderItems = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedDateNavigationHeaderIndex;
|
||||
|
||||
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
|
||||
|
||||
// For updating account calendars asynchronously.
|
||||
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
|
||||
|
||||
public AppShellViewModel(IPreferencesService preferencesService,
|
||||
IStatePersistanceService statePersistanceService,
|
||||
IAccountService accountService,
|
||||
ICalendarService calendarService,
|
||||
IAccountCalendarStateService accountCalendarStateService,
|
||||
INavigationService navigationService,
|
||||
IWinoServerConnectionManager serverConnectionManager)
|
||||
{
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
public IStatePersistanceService StatePersistenceService { get; }
|
||||
public IAccountCalendarStateService AccountCalendarStateService { get; }
|
||||
public INavigationService NavigationService { get; }
|
||||
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
||||
_accountService = accountService;
|
||||
_calendarService = calendarService;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEventDetailsPageActive;
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedMenuItemIndex = -1;
|
||||
NavigationService = navigationService;
|
||||
ServerConnectionManager = serverConnectionManager;
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isCalendarEnabled;
|
||||
StatePersistenceService = statePersistanceService;
|
||||
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active connection status of the Wino server.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private WinoServerConnectionStatus activeConnectionStatus;
|
||||
private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display date of the calendar.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private DateTimeOffset _displayDate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the highlighted range in the CalendarView and displayed date range in FlipView.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private DateRange highlightedDateRange;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableRangeCollection<string> dateNavigationHeaderItems = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedDateNavigationHeaderIndex;
|
||||
|
||||
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
|
||||
|
||||
// For updating account calendars asynchronously.
|
||||
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
|
||||
|
||||
public AppShellViewModel(IPreferencesService preferencesService,
|
||||
IStatePersistanceService statePersistanceService,
|
||||
IAccountService accountService,
|
||||
ICalendarService calendarService,
|
||||
IAccountCalendarStateService accountCalendarStateService,
|
||||
INavigationService navigationService,
|
||||
IWinoServerConnectionManager serverConnectionManager)
|
||||
private void PrefefencesChanged(object sender, string e)
|
||||
{
|
||||
if (e == nameof(StatePersistenceService.CalendarDisplayType))
|
||||
{
|
||||
_accountService = accountService;
|
||||
_calendarService = calendarService;
|
||||
Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType));
|
||||
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
||||
|
||||
NavigationService = navigationService;
|
||||
ServerConnectionManager = serverConnectionManager;
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
StatePersistenceService = statePersistanceService;
|
||||
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
||||
// Change the calendar.
|
||||
DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectedCalendarItemsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
private void PrefefencesChanged(object sender, string e)
|
||||
UpdateDateNavigationHeaderItems();
|
||||
|
||||
await InitializeAccountCalendarsAsync();
|
||||
|
||||
TodayClicked();
|
||||
}
|
||||
|
||||
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
|
||||
{
|
||||
// When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
|
||||
// Reporting all these changes one by one to the UI is not efficient and may cause problems in the future.
|
||||
|
||||
// Update all calendar states at once.
|
||||
try
|
||||
{
|
||||
if (e == nameof(StatePersistenceService.CalendarDisplayType))
|
||||
await _accountCalendarUpdateSemaphoreSlim.WaitAsync();
|
||||
|
||||
foreach (var calendar in e.AccountCalendars)
|
||||
{
|
||||
Messenger.Send(new CalendarDisplayTypeChangedMessage(StatePersistenceService.CalendarDisplayType));
|
||||
|
||||
|
||||
// Change the calendar.
|
||||
DateClicked(new CalendarViewDayClickedEventArgs(GetDisplayTypeSwitchDate()));
|
||||
await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
catch (Exception ex)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
UpdateDateNavigationHeaderItems();
|
||||
|
||||
await InitializeAccountCalendarsAsync();
|
||||
|
||||
TodayClicked();
|
||||
Log.Error(ex, "Error while waiting for account calendar update semaphore.");
|
||||
}
|
||||
|
||||
private async void AccountCalendarStateCollectivelyChanged(object sender, GroupedAccountCalendarViewModel e)
|
||||
finally
|
||||
{
|
||||
// When using three-state checkbox, multiple accounts will be selected/unselected at the same time.
|
||||
// Reporting all these changes one by one to the UI is not efficient and may cause problems in the future.
|
||||
|
||||
// Update all calendar states at once.
|
||||
try
|
||||
{
|
||||
await _accountCalendarUpdateSemaphoreSlim.WaitAsync();
|
||||
|
||||
foreach (var calendar in e.AccountCalendars)
|
||||
{
|
||||
await _calendarService.UpdateAccountCalendarAsync(calendar.AccountCalendar).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while waiting for account calendar update semaphore.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_accountCalendarUpdateSemaphoreSlim.Release();
|
||||
}
|
||||
_accountCalendarUpdateSemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
|
||||
=> await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false);
|
||||
private async void UpdateAccountCalendarRequested(object sender, AccountCalendarViewModel e)
|
||||
=> await _calendarService.UpdateAccountCalendarAsync(e.AccountCalendar).ConfigureAwait(false);
|
||||
|
||||
private async Task InitializeAccountCalendarsAsync()
|
||||
private async Task InitializeAccountCalendarsAsync()
|
||||
{
|
||||
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
await Dispatcher.ExecuteOnUIThread(() => AccountCalendarStateService.ClearGroupedAccountCalendar());
|
||||
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||
var calendarViewModels = new List<AccountCalendarViewModel>();
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var account in accounts)
|
||||
foreach (var calendar in accountCalendars)
|
||||
{
|
||||
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||
var calendarViewModels = new List<AccountCalendarViewModel>();
|
||||
var calendarViewModel = new AccountCalendarViewModel(account, calendar);
|
||||
|
||||
foreach (var calendar in accountCalendars)
|
||||
{
|
||||
var calendarViewModel = new AccountCalendarViewModel(account, calendar);
|
||||
|
||||
calendarViewModels.Add(calendarViewModel);
|
||||
}
|
||||
|
||||
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
|
||||
|
||||
await Dispatcher.ExecuteOnUIThread(() =>
|
||||
{
|
||||
AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceNavigateCalendarDate()
|
||||
{
|
||||
if (SelectedMenuItemIndex == -1)
|
||||
{
|
||||
var args = new CalendarPageNavigationArgs()
|
||||
{
|
||||
NavigationDate = _navigationDate ?? DateTime.Now.Date
|
||||
};
|
||||
|
||||
// Already on calendar. Just navigate.
|
||||
NavigationService.Navigate(WinoPage.CalendarPage, args);
|
||||
|
||||
_navigationDate = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedMenuItemIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
|
||||
{
|
||||
switch (newValue)
|
||||
{
|
||||
case -1:
|
||||
ForceNavigateCalendarDate();
|
||||
break;
|
||||
case 0:
|
||||
NavigationService.Navigate(WinoPage.ManageAccountsPage);
|
||||
break;
|
||||
case 1:
|
||||
NavigationService.Navigate(WinoPage.SettingsPage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Sync()
|
||||
{
|
||||
// Sync all calendars.
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
}, SynchronizationSource.Client);
|
||||
|
||||
Messenger.Send(t);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When calendar type switches, we need to navigate to the most ideal date.
|
||||
/// This method returns that date.
|
||||
/// </summary>
|
||||
private DateTime GetDisplayTypeSwitchDate()
|
||||
{
|
||||
var settings = PreferencesService.GetCurrentCalendarSettings();
|
||||
switch (StatePersistenceService.CalendarDisplayType)
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;
|
||||
|
||||
return HighlightedDateRange.StartDate;
|
||||
case CalendarDisplayType.Week:
|
||||
if (HighlightedDateRange == null || HighlightedDateRange.IsInRange(DateTime.Now))
|
||||
{
|
||||
return DateTime.Now.Date.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||
}
|
||||
|
||||
return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
break;
|
||||
case CalendarDisplayType.Month:
|
||||
break;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
calendarViewModels.Add(calendarViewModel);
|
||||
}
|
||||
|
||||
return DateTime.Today.Date;
|
||||
}
|
||||
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
|
||||
|
||||
private DateTime? _navigationDate;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ICalendarService _calendarService;
|
||||
|
||||
#region Commands
|
||||
|
||||
[RelayCommand]
|
||||
private void TodayClicked()
|
||||
{
|
||||
_navigationDate = DateTime.Now.Date;
|
||||
|
||||
ForceNavigateCalendarDate();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);
|
||||
|
||||
[RelayCommand]
|
||||
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
|
||||
|
||||
[RelayCommand]
|
||||
private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs)
|
||||
{
|
||||
_navigationDate = clickedDateArgs.ClickedDate;
|
||||
|
||||
ForceNavigateCalendarDate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the header navigation items based on visible date range and calendar type.
|
||||
/// </summary>
|
||||
private void UpdateDateNavigationHeaderItems()
|
||||
{
|
||||
DateNavigationHeaderItems.Clear();
|
||||
|
||||
// TODO: From settings
|
||||
var testInfo = new CultureInfo("en-US");
|
||||
|
||||
switch (StatePersistenceService.CalendarDisplayType)
|
||||
await Dispatcher.ExecuteOnUIThread(() =>
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
case CalendarDisplayType.Week:
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
case CalendarDisplayType.Month:
|
||||
DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames);
|
||||
break;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SetDateNavigationHeaderItems();
|
||||
}
|
||||
|
||||
partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems();
|
||||
|
||||
private void SetDateNavigationHeaderItems()
|
||||
{
|
||||
if (HighlightedDateRange == null) return;
|
||||
|
||||
if (DateNavigationHeaderItems.Count == 0)
|
||||
{
|
||||
UpdateDateNavigationHeaderItems();
|
||||
}
|
||||
|
||||
// TODO: Year view
|
||||
var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex();
|
||||
|
||||
SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1);
|
||||
}
|
||||
|
||||
public async void Receive(CalendarEnableStatusChangedMessage message)
|
||||
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
|
||||
|
||||
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
|
||||
|
||||
public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar));
|
||||
|
||||
public async void Receive(DetailsPageStateChangedMessage message)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
IsEventDetailsPageActive = message.IsActivated;
|
||||
|
||||
// TODO: This is for Wino Mail. Generalize this later on.
|
||||
StatePersistenceService.IsReaderNarrowed = message.IsActivated;
|
||||
StatePersistenceService.IsReadingMail = message.IsActivated;
|
||||
AccountCalendarStateService.AddGroupedAccountCalendar(groupedAccountCalendarViewModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceNavigateCalendarDate()
|
||||
{
|
||||
if (SelectedMenuItemIndex == -1)
|
||||
{
|
||||
var args = new CalendarPageNavigationArgs()
|
||||
{
|
||||
NavigationDate = _navigationDate ?? DateTime.Now.Date
|
||||
};
|
||||
|
||||
// Already on calendar. Just navigate.
|
||||
NavigationService.Navigate(WinoPage.CalendarPage, args);
|
||||
|
||||
_navigationDate = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedMenuItemIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
|
||||
{
|
||||
switch (newValue)
|
||||
{
|
||||
case -1:
|
||||
ForceNavigateCalendarDate();
|
||||
break;
|
||||
case 0:
|
||||
NavigationService.Navigate(WinoPage.ManageAccountsPage);
|
||||
break;
|
||||
case 1:
|
||||
NavigationService.Navigate(WinoPage.SettingsPage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Sync()
|
||||
{
|
||||
// Sync all calendars.
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
}, SynchronizationSource.Client);
|
||||
|
||||
Messenger.Send(t);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When calendar type switches, we need to navigate to the most ideal date.
|
||||
/// This method returns that date.
|
||||
/// </summary>
|
||||
private DateTime GetDisplayTypeSwitchDate()
|
||||
{
|
||||
var settings = PreferencesService.GetCurrentCalendarSettings();
|
||||
switch (StatePersistenceService.CalendarDisplayType)
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
if (HighlightedDateRange.IsInRange(DateTime.Now)) return DateTime.Now.Date;
|
||||
|
||||
return HighlightedDateRange.StartDate;
|
||||
case CalendarDisplayType.Week:
|
||||
if (HighlightedDateRange == null || HighlightedDateRange.IsInRange(DateTime.Now))
|
||||
{
|
||||
return DateTime.Now.Date.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||
}
|
||||
|
||||
return HighlightedDateRange.StartDate.GetWeekStartDateForDate(settings.FirstDayOfWeek);
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
break;
|
||||
case CalendarDisplayType.Month:
|
||||
break;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DateTime.Today.Date;
|
||||
}
|
||||
|
||||
private DateTime? _navigationDate;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly ICalendarService _calendarService;
|
||||
|
||||
#region Commands
|
||||
|
||||
[RelayCommand]
|
||||
private void TodayClicked()
|
||||
{
|
||||
_navigationDate = DateTime.Now.Date;
|
||||
|
||||
ForceNavigateCalendarDate();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ManageAccounts() => NavigationService.Navigate(WinoPage.AccountManagementPage);
|
||||
|
||||
[RelayCommand]
|
||||
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
|
||||
|
||||
[RelayCommand]
|
||||
private void DateClicked(CalendarViewDayClickedEventArgs clickedDateArgs)
|
||||
{
|
||||
_navigationDate = clickedDateArgs.ClickedDate;
|
||||
|
||||
ForceNavigateCalendarDate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Receive(VisibleDateRangeChangedMessage message) => HighlightedDateRange = message.DateRange;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the header navigation items based on visible date range and calendar type.
|
||||
/// </summary>
|
||||
private void UpdateDateNavigationHeaderItems()
|
||||
{
|
||||
DateNavigationHeaderItems.Clear();
|
||||
|
||||
// TODO: From settings
|
||||
var testInfo = new CultureInfo("en-US");
|
||||
|
||||
switch (StatePersistenceService.CalendarDisplayType)
|
||||
{
|
||||
case CalendarDisplayType.Day:
|
||||
case CalendarDisplayType.Week:
|
||||
case CalendarDisplayType.WorkWeek:
|
||||
case CalendarDisplayType.Month:
|
||||
DateNavigationHeaderItems.ReplaceRange(testInfo.DateTimeFormat.MonthNames);
|
||||
break;
|
||||
case CalendarDisplayType.Year:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SetDateNavigationHeaderItems();
|
||||
}
|
||||
|
||||
partial void OnHighlightedDateRangeChanged(DateRange value) => SetDateNavigationHeaderItems();
|
||||
|
||||
private void SetDateNavigationHeaderItems()
|
||||
{
|
||||
if (HighlightedDateRange == null) return;
|
||||
|
||||
if (DateNavigationHeaderItems.Count == 0)
|
||||
{
|
||||
UpdateDateNavigationHeaderItems();
|
||||
}
|
||||
|
||||
// TODO: Year view
|
||||
var monthIndex = HighlightedDateRange.GetMostVisibleMonthIndex();
|
||||
|
||||
SelectedDateNavigationHeaderIndex = Math.Max(monthIndex - 1, -1);
|
||||
}
|
||||
|
||||
public async void Receive(CalendarEnableStatusChangedMessage message)
|
||||
=> await ExecuteUIThread(() => IsCalendarEnabled = message.IsEnabled);
|
||||
|
||||
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
|
||||
|
||||
public void Receive(CalendarDisplayTypeChangedMessage message) => OnPropertyChanged(nameof(IsVerticalCalendar));
|
||||
|
||||
public async void Receive(DetailsPageStateChangedMessage message)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
IsEventDetailsPageActive = message.IsActivated;
|
||||
|
||||
// TODO: This is for Wino Mail. Generalize this later on.
|
||||
StatePersistenceService.IsReaderNarrowed = message.IsActivated;
|
||||
StatePersistenceService.IsReadingMail = message.IsActivated;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,120 +8,119 @@ using Wino.Core.Domain.Translations;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
|
||||
[ObservableProperty]
|
||||
private double _cellHourHeight;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedFirstDayOfWeekIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _is24HourHeaders;
|
||||
|
||||
[ObservableProperty]
|
||||
private TimeSpan _workingHourStart;
|
||||
|
||||
[ObservableProperty]
|
||||
private TimeSpan _workingHourEnd;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<string> _dayNames = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _workingDayStartIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _workingDayEndIndex;
|
||||
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
|
||||
private readonly bool _isLoaded = false;
|
||||
|
||||
public CalendarSettingsPageViewModel(IPreferencesService preferencesService)
|
||||
{
|
||||
[ObservableProperty]
|
||||
private double _cellHourHeight;
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _selectedFirstDayOfWeekIndex;
|
||||
var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage);
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _is24HourHeaders;
|
||||
var cultureInfo = new CultureInfo(currentLanguageLanguageCode);
|
||||
|
||||
[ObservableProperty]
|
||||
private TimeSpan _workingHourStart;
|
||||
|
||||
[ObservableProperty]
|
||||
private TimeSpan _workingHourEnd;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<string> _dayNames = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private int _workingDayStartIndex;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _workingDayEndIndex;
|
||||
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
|
||||
private readonly bool _isLoaded = false;
|
||||
|
||||
public CalendarSettingsPageViewModel(IPreferencesService preferencesService)
|
||||
// Populate the day names list
|
||||
for (var i = 0; i < 7; i++)
|
||||
{
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage);
|
||||
|
||||
var cultureInfo = new CultureInfo(currentLanguageLanguageCode);
|
||||
|
||||
// Populate the day names list
|
||||
for (var i = 0; i < 7; i++)
|
||||
{
|
||||
_dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]);
|
||||
}
|
||||
|
||||
var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek);
|
||||
|
||||
_selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName);
|
||||
_is24HourHeaders = preferencesService.Prefer24HourTimeFormat;
|
||||
_workingHourStart = preferencesService.WorkingHourStart;
|
||||
_workingHourEnd = preferencesService.WorkingHourEnd;
|
||||
_cellHourHeight = preferencesService.HourHeight;
|
||||
|
||||
_workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart));
|
||||
_workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd));
|
||||
|
||||
_isLoaded = true;
|
||||
_dayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]);
|
||||
}
|
||||
|
||||
partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings();
|
||||
partial void OnIs24HourHeadersChanged(bool value) => SaveSettings();
|
||||
partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings();
|
||||
partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings();
|
||||
partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings();
|
||||
partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings();
|
||||
partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings();
|
||||
var cultureFirstDayName = cultureInfo.DateTimeFormat.GetDayName(preferencesService.FirstDayOfWeek);
|
||||
|
||||
public void SaveSettings()
|
||||
_selectedFirstDayOfWeekIndex = _dayNames.IndexOf(cultureFirstDayName);
|
||||
_is24HourHeaders = preferencesService.Prefer24HourTimeFormat;
|
||||
_workingHourStart = preferencesService.WorkingHourStart;
|
||||
_workingHourEnd = preferencesService.WorkingHourEnd;
|
||||
_cellHourHeight = preferencesService.HourHeight;
|
||||
|
||||
_workingDayStartIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart));
|
||||
_workingDayEndIndex = _dayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd));
|
||||
|
||||
_isLoaded = true;
|
||||
}
|
||||
|
||||
partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings();
|
||||
partial void OnIs24HourHeadersChanged(bool value) => SaveSettings();
|
||||
partial void OnSelectedFirstDayOfWeekIndexChanged(int value) => SaveSettings();
|
||||
partial void OnWorkingHourStartChanged(TimeSpan value) => SaveSettings();
|
||||
partial void OnWorkingHourEndChanged(TimeSpan value) => SaveSettings();
|
||||
partial void OnWorkingDayStartIndexChanged(int value) => SaveSettings();
|
||||
partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings();
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
if (!_isLoaded) return;
|
||||
|
||||
PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch
|
||||
{
|
||||
if (!_isLoaded) return;
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
PreferencesService.WorkingDayStart = WorkingDayStartIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
PreferencesService.WorkingDayStart = WorkingDayStartIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
PreferencesService.WorkingDayEnd = WorkingDayEndIndex switch
|
||||
{
|
||||
0 => DayOfWeek.Sunday,
|
||||
1 => DayOfWeek.Monday,
|
||||
2 => DayOfWeek.Tuesday,
|
||||
3 => DayOfWeek.Wednesday,
|
||||
4 => DayOfWeek.Thursday,
|
||||
5 => DayOfWeek.Friday,
|
||||
6 => DayOfWeek.Saturday,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders;
|
||||
PreferencesService.WorkingHourStart = WorkingHourStart;
|
||||
PreferencesService.WorkingHourEnd = WorkingHourEnd;
|
||||
PreferencesService.HourHeight = CellHourHeight;
|
||||
|
||||
PreferencesService.Prefer24HourTimeFormat = Is24HourHeaders;
|
||||
PreferencesService.WorkingHourStart = WorkingHourStart;
|
||||
PreferencesService.WorkingHourEnd = WorkingHourEnd;
|
||||
PreferencesService.HourHeight = CellHourHeight;
|
||||
|
||||
Messenger.Send(new CalendarSettingsUpdatedMessage());
|
||||
}
|
||||
Messenger.Send(new CalendarSettingsUpdatedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Wino.Core;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public static class CalendarViewModelContainerSetup
|
||||
{
|
||||
public static class CalendarViewModelContainerSetup
|
||||
public static void RegisterCalendarViewModelServices(this IServiceCollection services)
|
||||
{
|
||||
public static void RegisterCalendarViewModelServices(this IServiceCollection services)
|
||||
{
|
||||
services.RegisterCoreServices();
|
||||
}
|
||||
services.RegisterCoreServices();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,67 +4,66 @@ using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Data
|
||||
namespace Wino.Calendar.ViewModels.Data;
|
||||
|
||||
public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar
|
||||
{
|
||||
public partial class AccountCalendarViewModel : ObservableObject, IAccountCalendar
|
||||
public MailAccount Account { get; }
|
||||
public AccountCalendar AccountCalendar { get; }
|
||||
|
||||
public AccountCalendarViewModel(MailAccount account, AccountCalendar accountCalendar)
|
||||
{
|
||||
public MailAccount Account { get; }
|
||||
public AccountCalendar AccountCalendar { get; }
|
||||
Account = account;
|
||||
AccountCalendar = accountCalendar;
|
||||
|
||||
public AccountCalendarViewModel(MailAccount account, AccountCalendar accountCalendar)
|
||||
{
|
||||
Account = account;
|
||||
AccountCalendar = accountCalendar;
|
||||
|
||||
IsChecked = accountCalendar.IsExtended;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isChecked;
|
||||
|
||||
partial void OnIsCheckedChanged(bool value) => IsExtended = value;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => AccountCalendar.Name;
|
||||
set => SetProperty(AccountCalendar.Name, value, AccountCalendar, (u, n) => u.Name = n);
|
||||
}
|
||||
|
||||
public string TextColorHex
|
||||
{
|
||||
get => AccountCalendar.TextColorHex;
|
||||
set => SetProperty(AccountCalendar.TextColorHex, value, AccountCalendar, (u, t) => u.TextColorHex = t);
|
||||
}
|
||||
|
||||
public string BackgroundColorHex
|
||||
{
|
||||
get => AccountCalendar.BackgroundColorHex;
|
||||
set => SetProperty(AccountCalendar.BackgroundColorHex, value, AccountCalendar, (u, b) => u.BackgroundColorHex = b);
|
||||
}
|
||||
|
||||
public bool IsExtended
|
||||
{
|
||||
get => AccountCalendar.IsExtended;
|
||||
set => SetProperty(AccountCalendar.IsExtended, value, AccountCalendar, (u, i) => u.IsExtended = i);
|
||||
}
|
||||
|
||||
public bool IsPrimary
|
||||
{
|
||||
get => AccountCalendar.IsPrimary;
|
||||
set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i);
|
||||
}
|
||||
|
||||
public Guid AccountId
|
||||
{
|
||||
get => AccountCalendar.AccountId;
|
||||
set => SetProperty(AccountCalendar.AccountId, value, AccountCalendar, (u, a) => u.AccountId = a);
|
||||
}
|
||||
|
||||
public string RemoteCalendarId
|
||||
{
|
||||
get => AccountCalendar.RemoteCalendarId;
|
||||
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
|
||||
}
|
||||
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
|
||||
IsChecked = accountCalendar.IsExtended;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isChecked;
|
||||
|
||||
partial void OnIsCheckedChanged(bool value) => IsExtended = value;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => AccountCalendar.Name;
|
||||
set => SetProperty(AccountCalendar.Name, value, AccountCalendar, (u, n) => u.Name = n);
|
||||
}
|
||||
|
||||
public string TextColorHex
|
||||
{
|
||||
get => AccountCalendar.TextColorHex;
|
||||
set => SetProperty(AccountCalendar.TextColorHex, value, AccountCalendar, (u, t) => u.TextColorHex = t);
|
||||
}
|
||||
|
||||
public string BackgroundColorHex
|
||||
{
|
||||
get => AccountCalendar.BackgroundColorHex;
|
||||
set => SetProperty(AccountCalendar.BackgroundColorHex, value, AccountCalendar, (u, b) => u.BackgroundColorHex = b);
|
||||
}
|
||||
|
||||
public bool IsExtended
|
||||
{
|
||||
get => AccountCalendar.IsExtended;
|
||||
set => SetProperty(AccountCalendar.IsExtended, value, AccountCalendar, (u, i) => u.IsExtended = i);
|
||||
}
|
||||
|
||||
public bool IsPrimary
|
||||
{
|
||||
get => AccountCalendar.IsPrimary;
|
||||
set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i);
|
||||
}
|
||||
|
||||
public Guid AccountId
|
||||
{
|
||||
get => AccountCalendar.AccountId;
|
||||
set => SetProperty(AccountCalendar.AccountId, value, AccountCalendar, (u, a) => u.AccountId = a);
|
||||
}
|
||||
|
||||
public string RemoteCalendarId
|
||||
{
|
||||
get => AccountCalendar.RemoteCalendarId;
|
||||
set => SetProperty(AccountCalendar.RemoteCalendarId, value, AccountCalendar, (u, r) => u.RemoteCalendarId = r);
|
||||
}
|
||||
public Guid Id { get => ((IAccountCalendar)AccountCalendar).Id; set => ((IAccountCalendar)AccountCalendar).Id = value; }
|
||||
}
|
||||
|
||||
@@ -1,46 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Itenso.TimePeriod;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Data
|
||||
namespace Wino.Calendar.ViewModels.Data;
|
||||
|
||||
public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel
|
||||
{
|
||||
public partial class CalendarItemViewModel : ObservableObject, ICalendarItem, ICalendarItemViewModel
|
||||
public CalendarItem CalendarItem { get; }
|
||||
|
||||
public string Title => CalendarItem.Title;
|
||||
|
||||
public Guid Id => CalendarItem.Id;
|
||||
|
||||
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
||||
|
||||
public DateTime StartDateTime { get => CalendarItem.StartDateTime; set => CalendarItem.StartDateTime = value; }
|
||||
|
||||
public DateTime EndDateTime => CalendarItem.EndDateTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start date and time in the local time zone for display purposes.
|
||||
/// </summary>
|
||||
public DateTime LocalStartDateTime => ConvertToLocalTime();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end date and time in the local time zone for display purposes.
|
||||
/// </summary>
|
||||
public DateTime LocalEndDateTime => ConvertToLocalTime();
|
||||
|
||||
public ITimePeriod Period => CalendarItem.Period;
|
||||
|
||||
public bool IsRecurringEvent => !string.IsNullOrEmpty(CalendarItem.RecurrenceRules) || !string.IsNullOrEmpty(CalendarItem.RecurringEventId);
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isSelected;
|
||||
|
||||
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
|
||||
|
||||
public CalendarItemType ItemType => CalendarItem.ItemType;
|
||||
|
||||
public CalendarItemViewModel(CalendarItem calendarItem)
|
||||
{
|
||||
public CalendarItem CalendarItem { get; }
|
||||
CalendarItem = calendarItem;
|
||||
|
||||
public string Title => CalendarItem.Title;
|
||||
Debug.WriteLine($"{Title} : {ItemType}");
|
||||
}
|
||||
|
||||
public Guid Id => CalendarItem.Id;
|
||||
/// <summary>
|
||||
/// Converts a DateTime to local time based on the provided timezone.
|
||||
/// If timezone is empty or null, assumes the DateTime is in UTC.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The DateTime to convert</param>
|
||||
/// <param name="timeZone">The timezone string. If empty/null, assumes UTC.</param>
|
||||
/// <returns>DateTime converted to local time</returns>
|
||||
private DateTime ConvertToLocalTime()
|
||||
{
|
||||
// All day events ignore time zones and are treated as local time.
|
||||
if (ItemType == CalendarItemType.AllDay || ItemType == CalendarItemType.MultiDayAllDay || ItemType == CalendarItemType.RecurringAllDay)
|
||||
return CalendarItem.StartDateTime;
|
||||
|
||||
public IAccountCalendar AssignedCalendar => CalendarItem.AssignedCalendar;
|
||||
|
||||
public DateTime StartDate { get => CalendarItem.StartDate; set => CalendarItem.StartDate = value; }
|
||||
|
||||
public DateTime EndDate => CalendarItem.EndDate;
|
||||
|
||||
public double DurationInSeconds { get => CalendarItem.DurationInSeconds; set => CalendarItem.DurationInSeconds = value; }
|
||||
|
||||
public ITimePeriod Period => CalendarItem.Period;
|
||||
|
||||
public bool IsAllDayEvent => CalendarItem.IsAllDayEvent;
|
||||
public bool IsMultiDayEvent => CalendarItem.IsMultiDayEvent;
|
||||
public bool IsRecurringEvent => CalendarItem.IsRecurringEvent;
|
||||
public bool IsRecurringChild => CalendarItem.IsRecurringChild;
|
||||
public bool IsRecurringParent => CalendarItem.IsRecurringParent;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isSelected;
|
||||
|
||||
public ObservableCollection<CalendarEventAttendee> Attendees { get; } = new ObservableCollection<CalendarEventAttendee>();
|
||||
|
||||
public CalendarItemViewModel(CalendarItem calendarItem)
|
||||
if (string.IsNullOrEmpty(CalendarItem.TimeZone))
|
||||
{
|
||||
CalendarItem = calendarItem;
|
||||
// If no timezone specified, assume it's UTC and convert to local time
|
||||
return DateTime.SpecifyKind(CalendarItem.StartDateTime, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
|
||||
public override string ToString() => CalendarItem.Title;
|
||||
try
|
||||
{
|
||||
// Parse the timezone and convert to local time
|
||||
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(CalendarItem.TimeZone);
|
||||
return TimeZoneInfo.ConvertTimeToUtc(CalendarItem.StartDateTime, sourceTimeZone).ToLocalTime();
|
||||
}
|
||||
catch (TimeZoneNotFoundException)
|
||||
{
|
||||
// If timezone is not found, fallback to treating as UTC
|
||||
return DateTime.SpecifyKind(CalendarItem.StartDateTime, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
catch (InvalidTimeZoneException)
|
||||
{
|
||||
// If timezone is invalid, fallback to treating as UTC
|
||||
return DateTime.SpecifyKind(CalendarItem.StartDateTime, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => CalendarItem.Title;
|
||||
}
|
||||
|
||||
@@ -6,141 +6,140 @@ using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Data
|
||||
namespace Wino.Calendar.ViewModels.Data;
|
||||
|
||||
public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
{
|
||||
public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
public event EventHandler CollectiveSelectionStateChanged;
|
||||
public event EventHandler<AccountCalendarViewModel> CalendarSelectionStateChanged;
|
||||
|
||||
public MailAccount Account { get; }
|
||||
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
|
||||
|
||||
public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable<AccountCalendarViewModel> calendarViewModels)
|
||||
{
|
||||
public event EventHandler CollectiveSelectionStateChanged;
|
||||
public event EventHandler<AccountCalendarViewModel> CalendarSelectionStateChanged;
|
||||
Account = account;
|
||||
AccountCalendars = new ObservableCollection<AccountCalendarViewModel>(calendarViewModels);
|
||||
|
||||
public MailAccount Account { get; }
|
||||
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
|
||||
ManageIsCheckedState();
|
||||
|
||||
public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable<AccountCalendarViewModel> calendarViewModels)
|
||||
foreach (var calendarViewModel in calendarViewModels)
|
||||
{
|
||||
Account = account;
|
||||
AccountCalendars = new ObservableCollection<AccountCalendarViewModel>(calendarViewModels);
|
||||
|
||||
ManageIsCheckedState();
|
||||
|
||||
foreach (var calendarViewModel in calendarViewModels)
|
||||
{
|
||||
calendarViewModel.PropertyChanged += CalendarPropertyChanged;
|
||||
}
|
||||
|
||||
AccountCalendars.CollectionChanged += CalendarListUpdated;
|
||||
calendarViewModel.PropertyChanged += CalendarPropertyChanged;
|
||||
}
|
||||
|
||||
private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
AccountCalendars.CollectionChanged += CalendarListUpdated;
|
||||
}
|
||||
|
||||
private void CalendarListUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
foreach (AccountCalendarViewModel calendar in e.NewItems)
|
||||
{
|
||||
foreach (AccountCalendarViewModel calendar in e.NewItems)
|
||||
{
|
||||
calendar.PropertyChanged += CalendarPropertyChanged;
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||
{
|
||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||
{
|
||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||
}
|
||||
calendar.PropertyChanged += CalendarPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
if (sender is AccountCalendarViewModel viewModel)
|
||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||
{
|
||||
if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
|
||||
{
|
||||
ManageIsCheckedState();
|
||||
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
|
||||
}
|
||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isExpanded = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool? isCheckedState = true;
|
||||
|
||||
private bool _isExternalPropChangeBlocked = false;
|
||||
|
||||
private void ManageIsCheckedState()
|
||||
else if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
if (_isExternalPropChangeBlocked) return;
|
||||
|
||||
_isExternalPropChangeBlocked = true;
|
||||
|
||||
if (AccountCalendars.All(c => c.IsChecked))
|
||||
foreach (AccountCalendarViewModel calendar in e.OldItems)
|
||||
{
|
||||
IsCheckedState = true;
|
||||
calendar.PropertyChanged -= CalendarPropertyChanged;
|
||||
}
|
||||
else if (AccountCalendars.All(c => !c.IsChecked))
|
||||
{
|
||||
IsCheckedState = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCheckedState = null;
|
||||
}
|
||||
|
||||
_isExternalPropChangeBlocked = false;
|
||||
}
|
||||
|
||||
partial void OnIsCheckedStateChanged(bool? newValue)
|
||||
{
|
||||
if (_isExternalPropChangeBlocked) return;
|
||||
|
||||
// Update is triggered by user on the three-state checkbox.
|
||||
// We should not report all changes one by one.
|
||||
|
||||
_isExternalPropChangeBlocked = true;
|
||||
|
||||
if (newValue == null)
|
||||
{
|
||||
// Only primary calendars must be checked.
|
||||
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
UpdateCalendarCheckedState(calendar, newValue.GetValueOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
_isExternalPropChangeBlocked = false;
|
||||
|
||||
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void UpdateCalendarCheckedState(AccountCalendarViewModel accountCalendarViewModel, bool newValue, bool ignoreValueCheck = false)
|
||||
{
|
||||
var currentValue = accountCalendarViewModel.IsChecked;
|
||||
|
||||
if (currentValue == newValue && !ignoreValueCheck) return;
|
||||
|
||||
accountCalendarViewModel.IsChecked = newValue;
|
||||
|
||||
// No need to report.
|
||||
if (_isExternalPropChangeBlocked == true) return;
|
||||
|
||||
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void CalendarPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is AccountCalendarViewModel viewModel)
|
||||
{
|
||||
if (e.PropertyName == nameof(AccountCalendarViewModel.IsChecked))
|
||||
{
|
||||
ManageIsCheckedState();
|
||||
UpdateCalendarCheckedState(viewModel, viewModel.IsChecked, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isExpanded = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool? isCheckedState = true;
|
||||
|
||||
private bool _isExternalPropChangeBlocked = false;
|
||||
|
||||
private void ManageIsCheckedState()
|
||||
{
|
||||
if (_isExternalPropChangeBlocked) return;
|
||||
|
||||
_isExternalPropChangeBlocked = true;
|
||||
|
||||
if (AccountCalendars.All(c => c.IsChecked))
|
||||
{
|
||||
IsCheckedState = true;
|
||||
}
|
||||
else if (AccountCalendars.All(c => !c.IsChecked))
|
||||
{
|
||||
IsCheckedState = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCheckedState = null;
|
||||
}
|
||||
|
||||
_isExternalPropChangeBlocked = false;
|
||||
}
|
||||
|
||||
partial void OnIsCheckedStateChanged(bool? newValue)
|
||||
{
|
||||
if (_isExternalPropChangeBlocked) return;
|
||||
|
||||
// Update is triggered by user on the three-state checkbox.
|
||||
// We should not report all changes one by one.
|
||||
|
||||
_isExternalPropChangeBlocked = true;
|
||||
|
||||
if (newValue == null)
|
||||
{
|
||||
// Only primary calendars must be checked.
|
||||
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
UpdateCalendarCheckedState(calendar, calendar.IsPrimary);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
UpdateCalendarCheckedState(calendar, newValue.GetValueOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
_isExternalPropChangeBlocked = false;
|
||||
|
||||
CollectiveSelectionStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void UpdateCalendarCheckedState(AccountCalendarViewModel accountCalendarViewModel, bool newValue, bool ignoreValueCheck = false)
|
||||
{
|
||||
var currentValue = accountCalendarViewModel.IsChecked;
|
||||
|
||||
if (currentValue == newValue && !ignoreValueCheck) return;
|
||||
|
||||
accountCalendarViewModel.IsChecked = newValue;
|
||||
|
||||
// No need to report.
|
||||
if (_isExternalPropChangeBlocked == true) return;
|
||||
|
||||
CalendarSelectionStateChanged?.Invoke(this, accountCalendarViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,105 +12,104 @@ using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels
|
||||
namespace Wino.Calendar.ViewModels;
|
||||
|
||||
public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||
{
|
||||
public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||
private readonly ICalendarService _calendarService;
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
|
||||
public CalendarSettings CurrentSettings { get; }
|
||||
|
||||
#region Details
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
||||
private CalendarItemViewModel _currentEvent;
|
||||
|
||||
[ObservableProperty]
|
||||
private CalendarItemViewModel _seriesParent;
|
||||
|
||||
public bool CanViewSeries => false; //CurrentEvent?.IsRecurringChild ?? false; // TODO: Implement this properly
|
||||
|
||||
#endregion
|
||||
|
||||
public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService)
|
||||
{
|
||||
private readonly ICalendarService _calendarService;
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
_calendarService = calendarService;
|
||||
_nativeAppService = nativeAppService;
|
||||
_preferencesService = preferencesService;
|
||||
|
||||
public CalendarSettings CurrentSettings { get; }
|
||||
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
||||
}
|
||||
|
||||
#region Details
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanViewSeries))]
|
||||
private CalendarItemViewModel _currentEvent;
|
||||
Messenger.Send(new DetailsPageStateChangedMessage(true));
|
||||
|
||||
[ObservableProperty]
|
||||
private CalendarItemViewModel _seriesParent;
|
||||
if (parameters == null || parameters is not CalendarItemTarget args)
|
||||
return;
|
||||
|
||||
public bool CanViewSeries => CurrentEvent?.IsRecurringChild ?? false;
|
||||
await LoadCalendarItemTargetAsync(args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public EventDetailsPageViewModel(ICalendarService calendarService, INativeAppService nativeAppService, IPreferencesService preferencesService)
|
||||
private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target)
|
||||
{
|
||||
try
|
||||
{
|
||||
_calendarService = calendarService;
|
||||
_nativeAppService = nativeAppService;
|
||||
_preferencesService = preferencesService;
|
||||
var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target);
|
||||
|
||||
CurrentSettings = _preferencesService.GetCurrentCalendarSettings();
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
Messenger.Send(new DetailsPageStateChangedMessage(true));
|
||||
|
||||
if (parameters == null || parameters is not CalendarItemTarget args)
|
||||
if (currentEventItem == null)
|
||||
return;
|
||||
|
||||
await LoadCalendarItemTargetAsync(args);
|
||||
}
|
||||
CurrentEvent = new CalendarItemViewModel(currentEventItem);
|
||||
|
||||
private async Task LoadCalendarItemTargetAsync(CalendarItemTarget target)
|
||||
{
|
||||
try
|
||||
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.Id);
|
||||
|
||||
foreach (var item in attendees)
|
||||
{
|
||||
var currentEventItem = await _calendarService.GetCalendarItemTargetAsync(target);
|
||||
|
||||
if (currentEventItem == null)
|
||||
return;
|
||||
|
||||
CurrentEvent = new CalendarItemViewModel(currentEventItem);
|
||||
|
||||
var attendees = await _calendarService.GetAttendeesAsync(currentEventItem.EventTrackingId);
|
||||
|
||||
foreach (var item in attendees)
|
||||
{
|
||||
CurrentEvent.Attendees.Add(item);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
CurrentEvent.Attendees.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||
catch (Exception ex)
|
||||
{
|
||||
base.OnNavigatedFrom(mode, parameters);
|
||||
|
||||
Messenger.Send(new DetailsPageStateChangedMessage(false));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task JoinOnline()
|
||||
{
|
||||
if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask;
|
||||
|
||||
return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Respond(CalendarItemStatus status)
|
||||
{
|
||||
if (CurrentEvent == null) return;
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedFrom(mode, parameters);
|
||||
|
||||
Messenger.Send(new DetailsPageStateChangedMessage(false));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task JoinOnline()
|
||||
{
|
||||
if (CurrentEvent == null || string.IsNullOrEmpty(CurrentEvent.CalendarItem.HtmlLink)) return Task.CompletedTask;
|
||||
|
||||
return _nativeAppService.LaunchUriAsync(new Uri(CurrentEvent.CalendarItem.HtmlLink));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Respond(CalendarItemStatus status)
|
||||
{
|
||||
if (CurrentEvent == null) return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,26 +6,25 @@ using System.Linq;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Interfaces
|
||||
namespace Wino.Calendar.ViewModels.Interfaces;
|
||||
|
||||
public interface IAccountCalendarStateService : INotifyPropertyChanged
|
||||
{
|
||||
public interface IAccountCalendarStateService : INotifyPropertyChanged
|
||||
{
|
||||
ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; }
|
||||
ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> GroupedAccountCalendars { get; }
|
||||
|
||||
event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
||||
event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
||||
event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
||||
event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
||||
|
||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||
public void ClearGroupedAccountCalendar();
|
||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar);
|
||||
public void ClearGroupedAccountCalendar();
|
||||
|
||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar);
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of currently selected calendars.
|
||||
/// </summary>
|
||||
IEnumerable<AccountCalendarViewModel> ActiveCalendars { get; }
|
||||
IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Enumeration of currently selected calendars.
|
||||
/// </summary>
|
||||
IEnumerable<AccountCalendarViewModel> ActiveCalendars { get; }
|
||||
IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable { get; }
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Messages
|
||||
{
|
||||
public class CalendarItemDoubleTappedMessage
|
||||
{
|
||||
public CalendarItemDoubleTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
}
|
||||
namespace Wino.Calendar.ViewModels.Messages;
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
public class CalendarItemDoubleTappedMessage
|
||||
{
|
||||
public CalendarItemDoubleTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Messages
|
||||
{
|
||||
public class CalendarItemRightTappedMessage
|
||||
{
|
||||
public CalendarItemRightTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
}
|
||||
namespace Wino.Calendar.ViewModels.Messages;
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
public class CalendarItemRightTappedMessage
|
||||
{
|
||||
public CalendarItemRightTappedMessage(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.ViewModels.Messages
|
||||
{
|
||||
public class CalendarItemTappedMessage
|
||||
{
|
||||
public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
ClickedPeriod = clickedPeriod;
|
||||
}
|
||||
namespace Wino.Calendar.ViewModels.Messages;
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
public CalendarDayModel ClickedPeriod { get; }
|
||||
public class CalendarItemTappedMessage
|
||||
{
|
||||
public CalendarItemTappedMessage(CalendarItemViewModel calendarItemViewModel, CalendarDayModel clickedPeriod)
|
||||
{
|
||||
CalendarItemViewModel = calendarItemViewModel;
|
||||
ClickedPeriod = clickedPeriod;
|
||||
}
|
||||
|
||||
public CalendarItemViewModel CalendarItemViewModel { get; }
|
||||
public CalendarDayModel ClickedPeriod { get; }
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Platforms>x86;x64;arm64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TimePeriodLibrary.NET" Version="2.1.5" />
|
||||
<PackageReference Include="TimePeriodLibrary.NET" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,308 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35424.110
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core.Domain", "Wino.Core.Domain\Wino.Core.Domain.csproj", "{814400B6-5A05-4596-B451-3A116A147DC1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Core.UWP", "Wino.Core.UWP\Wino.Core.UWP.csproj", "{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core.ViewModels", "Wino.Core.ViewModels\Wino.Core.ViewModels.csproj", "{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Messaging", "Wino.Messages\Wino.Messaging.csproj", "{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Server", "Wino.Server\Wino.Server.csproj", "{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Core", "Wino.Core\Wino.Core.csproj", "{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Calendar", "Wino.Calendar\Wino.Calendar.csproj", "{600F4979-DB7E-409D-B7DA-B60BE4C55C35}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.SourceGenerators", "Wino.SourceGenerators\Wino.SourceGenerators.csproj", "{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}"
|
||||
EndProject
|
||||
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Wino.Calendar.Packaging", "Wino.Calendar.Packaging\Wino.Calendar.Packaging.wapproj", "{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wino.Calendar.ViewModels", "Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj", "{CF850F8C-5042-4376-9CBA-C8F2BB554083}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Services", "Wino.Services\Wino.Services.csproj", "{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wino.Authentication", "Wino.Authentication\Wino.Authentication.csproj", "{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|ARM = Debug|ARM
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|ARM = Release|ARM
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x64.Build.0 = Debug|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Debug|x86.Build.0 = Debug|x86
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x64.ActiveCfg = Release|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x64.Build.0 = Release|x64
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x86.ActiveCfg = Release|x86
|
||||
{814400B6-5A05-4596-B451-3A116A147DC1}.Release|x86.Build.0 = Release|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x64.Build.0 = Debug|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Debug|x86.Build.0 = Debug|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.ActiveCfg = Release|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x64.Build.0 = Release|x64
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.ActiveCfg = Release|x86
|
||||
{395F19BA-1E42-495C-9DB5-1A6F537FCCB8}.Release|x86.Build.0 = Release|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x64.Build.0 = Debug|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Debug|x86.Build.0 = Debug|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x64.ActiveCfg = Release|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x64.Build.0 = Release|x64
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x86.ActiveCfg = Release|x86
|
||||
{510CD96C-B3FF-4EC9-A67B-845C842E6BEC}.Release|x86.Build.0 = Release|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x64.Build.0 = Debug|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Debug|x86.Build.0 = Debug|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x64.ActiveCfg = Release|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x64.Build.0 = Release|x64
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x86.ActiveCfg = Release|x86
|
||||
{AB588CFD-4B0C-4A1F-B711-1999E3D092D0}.Release|x86.Build.0 = Release|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM.ActiveCfg = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM.Build.0 = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x64.Build.0 = Debug|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Debug|x86.Build.0 = Debug|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|Any CPU.Build.0 = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM.ActiveCfg = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM.Build.0 = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x64.ActiveCfg = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x64.Build.0 = Release|x64
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x86.ActiveCfg = Release|x86
|
||||
{92DA33FC-9252-40C5-BF71-67ACB0B56F2B}.Release|x86.Build.0 = Release|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x64.Build.0 = Debug|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Debug|x86.Build.0 = Debug|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x64.ActiveCfg = Release|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x64.Build.0 = Release|x64
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x86.ActiveCfg = Release|x86
|
||||
{87FFCBF4-DC17-4F09-90D6-102CF4C72BAF}.Release|x86.Build.0 = Release|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|Any CPU.Deploy.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.Build.0 = Debug|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Build.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Build.0 = Debug|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.Build.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|Any CPU.Deploy.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.ActiveCfg = Release|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.Build.0 = Release|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM.Deploy.0 = Release|ARM
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.ActiveCfg = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Build.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x64.Deploy.0 = Release|x64
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.ActiveCfg = Release|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Build.0 = Release|x86
|
||||
{600F4979-DB7E-409D-B7DA-B60BE4C55C35}.Release|x86.Deploy.0 = Release|x86
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8A7EB697-D722-4E0F-B20E-9FC88373ADB5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.Build.0 = Debug|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Build.0 = Debug|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Build.0 = Debug|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.ActiveCfg = Release|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.Build.0 = Release|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM.Deploy.0 = Release|ARM
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.ActiveCfg = Release|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Build.0 = Release|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x64.Deploy.0 = Release|x64
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.ActiveCfg = Release|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Build.0 = Release|x86
|
||||
{7485B18C-F5AB-4ABE-BA7F-05B6623C67C8}.Release|x86.Deploy.0 = Release|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x64.Build.0 = Debug|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Debug|x86.Build.0 = Debug|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x64.ActiveCfg = Release|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x64.Build.0 = Release|x64
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x86.ActiveCfg = Release|x86
|
||||
{CF850F8C-5042-4376-9CBA-C8F2BB554083}.Release|x86.Build.0 = Release|x86
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BBA49030-7277-48CF-B2FE-3D01CB6B6C81}.Release|x86.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x64.Build.0 = Debug|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Debug|x86.Build.0 = Debug|x86
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x64.ActiveCfg = Release|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x64.Build.0 = Release|x64
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x86.ActiveCfg = Release|x86
|
||||
{16A979C2-F308-464F-9B2A-0AF8ED5EDB43}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -6,19 +6,18 @@ using Windows.UI.Xaml.Media.Animation;
|
||||
using Wino.Activation;
|
||||
using Wino.Calendar.Views;
|
||||
|
||||
namespace Wino.Calendar.Activation
|
||||
namespace Wino.Calendar.Activation;
|
||||
|
||||
public class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
{
|
||||
public class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
protected override Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
||||
(Window.Current.Content as Frame).Navigate(typeof(AppShell), null, new DrillInNavigationTransitionInfo());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Only navigate if Frame content doesn't exist.
|
||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||
=> (Window.Current?.Content as Frame)?.Content == null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Only navigate if Frame content doesn't exist.
|
||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||
=> (Window.Current?.Content as Frame)?.Content == null;
|
||||
}
|
||||
|
||||
@@ -24,141 +24,136 @@ using Wino.Messaging.Client.Connection;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Services;
|
||||
|
||||
namespace Wino.Calendar
|
||||
namespace Wino.Calendar;
|
||||
|
||||
public sealed partial class App : WinoApplication, IRecipient<NewCalendarSynchronizationRequested>
|
||||
{
|
||||
public sealed partial class App : WinoApplication, IRecipient<NewCalendarSynchronizationRequested>
|
||||
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
|
||||
|
||||
public App()
|
||||
{
|
||||
public override string AppCenterKey => "dfdad6ab-95f9-44cc-9112-45ec6730c49e";
|
||||
InitializeComponent();
|
||||
WeakReferenceMessenger.Default.Register<NewCalendarSynchronizationRequested>(this);
|
||||
}
|
||||
|
||||
private BackgroundTaskDeferral connectionBackgroundTaskDeferral;
|
||||
private BackgroundTaskDeferral toastActionBackgroundTaskDeferral;
|
||||
public override IServiceProvider ConfigureServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
public App()
|
||||
services.RegisterSharedServices();
|
||||
services.RegisterCalendarViewModelServices();
|
||||
services.RegisterCoreUWPServices();
|
||||
services.RegisterCoreViewModels();
|
||||
|
||||
RegisterUWPServices(services);
|
||||
RegisterViewModels(services);
|
||||
RegisterActivationHandlers(services);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
#region Dependency Injection
|
||||
|
||||
private void RegisterActivationHandlers(IServiceCollection services)
|
||||
{
|
||||
//services.AddTransient<ProtocolActivationHandler>();
|
||||
//services.AddTransient<ToastNotificationActivationHandler>();
|
||||
//services.AddTransient<FileActivationHandler>();
|
||||
}
|
||||
|
||||
private void RegisterUWPServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<INavigationService, NavigationService>();
|
||||
services.AddSingleton<ICalendarDialogService, DialogService>();
|
||||
services.AddTransient<ISettingsBuilderService, SettingsBuilderService>();
|
||||
services.AddTransient<IProviderService, ProviderService>();
|
||||
services.AddSingleton<IAuthenticatorConfig, CalendarAuthenticatorConfig>();
|
||||
services.AddSingleton<IAccountCalendarStateService, AccountCalendarStateService>();
|
||||
}
|
||||
|
||||
private void RegisterViewModels(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(typeof(AppShellViewModel));
|
||||
services.AddSingleton(typeof(CalendarPageViewModel));
|
||||
services.AddTransient(typeof(CalendarSettingsPageViewModel));
|
||||
services.AddTransient(typeof(AccountManagementViewModel));
|
||||
services.AddTransient(typeof(PersonalizationPageViewModel));
|
||||
services.AddTransient(typeof(AccountDetailsPageViewModel));
|
||||
services.AddTransient(typeof(EventDetailsPageViewModel));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||
{
|
||||
// TODO: Check server running.
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
|
||||
|
||||
if (!args.PrelaunchActivated)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
await ActivateWinoAsync(args);
|
||||
}
|
||||
}
|
||||
|
||||
public override IServiceProvider ConfigureServices()
|
||||
protected override IEnumerable<ActivationHandler> GetActivationHandlers()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
|
||||
=> new DefaultActivationHandler();
|
||||
|
||||
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
||||
{
|
||||
base.OnBackgroundActivated(args);
|
||||
|
||||
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
LogActivation("OnBackgroundActivated -> AppServiceTriggerDetails received.");
|
||||
|
||||
services.RegisterSharedServices();
|
||||
services.RegisterCalendarViewModelServices();
|
||||
services.RegisterCoreUWPServices();
|
||||
services.RegisterCoreViewModels();
|
||||
|
||||
RegisterUWPServices(services);
|
||||
RegisterViewModels(services);
|
||||
RegisterActivationHandlers(services);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
#region Dependency Injection
|
||||
|
||||
private void RegisterActivationHandlers(IServiceCollection services)
|
||||
{
|
||||
//services.AddTransient<ProtocolActivationHandler>();
|
||||
//services.AddTransient<ToastNotificationActivationHandler>();
|
||||
//services.AddTransient<FileActivationHandler>();
|
||||
}
|
||||
|
||||
private void RegisterUWPServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<INavigationService, NavigationService>();
|
||||
services.AddSingleton<ICalendarDialogService, DialogService>();
|
||||
services.AddTransient<ISettingsBuilderService, SettingsBuilderService>();
|
||||
services.AddTransient<IProviderService, ProviderService>();
|
||||
services.AddSingleton<IAuthenticatorConfig, CalendarAuthenticatorConfig>();
|
||||
services.AddSingleton<IAccountCalendarStateService, AccountCalendarStateService>();
|
||||
}
|
||||
|
||||
private void RegisterViewModels(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(typeof(AppShellViewModel));
|
||||
services.AddSingleton(typeof(CalendarPageViewModel));
|
||||
services.AddTransient(typeof(CalendarSettingsPageViewModel));
|
||||
services.AddTransient(typeof(AccountManagementViewModel));
|
||||
services.AddTransient(typeof(PersonalizationPageViewModel));
|
||||
services.AddTransient(typeof(AccountDetailsPageViewModel));
|
||||
services.AddTransient(typeof(EventDetailsPageViewModel));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnApplicationCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
|
||||
{
|
||||
// TODO: Check server running.
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
LogActivation($"OnLaunched -> {args.GetType().Name}, Kind -> {args.Kind}, PreviousExecutionState -> {args.PreviousExecutionState}, IsPrelaunch -> {args.PrelaunchActivated}");
|
||||
|
||||
if (!args.PrelaunchActivated)
|
||||
// Only accept connections from callers in the same package
|
||||
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
|
||||
{
|
||||
await ActivateWinoAsync(args);
|
||||
}
|
||||
}
|
||||
// Connection established from the fulltrust process
|
||||
|
||||
protected override IEnumerable<ActivationHandler> GetActivationHandlers()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
connectionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
|
||||
args.TaskInstance.Canceled += OnConnectionBackgroundTaskCanceled;
|
||||
|
||||
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
|
||||
=> new DefaultActivationHandler();
|
||||
AppServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
|
||||
|
||||
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
||||
{
|
||||
base.OnBackgroundActivated(args);
|
||||
|
||||
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
|
||||
{
|
||||
LogActivation("OnBackgroundActivated -> AppServiceTriggerDetails received.");
|
||||
|
||||
// Only accept connections from callers in the same package
|
||||
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
|
||||
{
|
||||
// Connection established from the fulltrust process
|
||||
|
||||
connectionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
|
||||
args.TaskInstance.Canceled += OnConnectionBackgroundTaskCanceled;
|
||||
|
||||
AppServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstablished());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||
{
|
||||
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
|
||||
|
||||
Log.Information($"Server connection background task was canceled. Reason: {reason}");
|
||||
|
||||
connectionBackgroundTaskDeferral?.Complete();
|
||||
connectionBackgroundTaskDeferral = null;
|
||||
|
||||
AppServiceConnectionManager.Connection = null;
|
||||
}
|
||||
|
||||
public async void Receive(NewCalendarSynchronizationRequested message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(message);
|
||||
synchronizationResultResponse.ThrowIfFailed();
|
||||
}
|
||||
catch (WinoServerException serverException)
|
||||
{
|
||||
var dialogService = Services.GetService<ICalendarDialogService>();
|
||||
|
||||
dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error);
|
||||
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstablished());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||
{
|
||||
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
|
||||
|
||||
Log.Information($"Server connection background task was canceled. Reason: {reason}");
|
||||
|
||||
connectionBackgroundTaskDeferral?.Complete();
|
||||
connectionBackgroundTaskDeferral = null;
|
||||
|
||||
AppServiceConnectionManager.Connection = null;
|
||||
}
|
||||
|
||||
public async void Receive(NewCalendarSynchronizationRequested message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(message);
|
||||
synchronizationResultResponse.ThrowIfFailed();
|
||||
}
|
||||
catch (WinoServerException serverException)
|
||||
{
|
||||
var dialogService = Services.GetService<ICalendarDialogService>();
|
||||
|
||||
dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
using System;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Wino.Calendar.Args
|
||||
namespace Wino.Calendar.Args;
|
||||
|
||||
/// <summary>
|
||||
/// When a new timeline cell is selected.
|
||||
/// </summary>
|
||||
public class TimelineCellSelectedArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// When a new timeline cell is selected.
|
||||
/// </summary>
|
||||
public class TimelineCellSelectedArgs : EventArgs
|
||||
public TimelineCellSelectedArgs(DateTime clickedDate, Point canvasPoint, Point positionerPoint, Size cellSize)
|
||||
{
|
||||
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; }
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Calendar.Args
|
||||
{
|
||||
/// <summary>
|
||||
/// When selected timeline cell is unselected.
|
||||
/// </summary>
|
||||
public class TimelineCellUnselectedArgs : EventArgs { }
|
||||
}
|
||||
namespace Wino.Calendar.Args;
|
||||
|
||||
/// <summary>
|
||||
/// When selected timeline cell is unselected.
|
||||
/// </summary>
|
||||
public class TimelineCellUnselectedArgs : EventArgs { }
|
||||
|
||||
@@ -2,30 +2,29 @@
|
||||
using Windows.UI.Xaml;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class CalendarItemCommandBarFlyout : CommandBarFlyout
|
||||
{
|
||||
public class CalendarItemCommandBarFlyout : CommandBarFlyout
|
||||
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
|
||||
|
||||
public CalendarItemViewModel Item
|
||||
{
|
||||
public static readonly DependencyProperty ItemProperty = DependencyProperty.Register(nameof(Item), typeof(CalendarItemViewModel), typeof(CalendarItemCommandBarFlyout), new PropertyMetadata(null, new PropertyChangedCallback(OnItemChanged)));
|
||||
get { return (CalendarItemViewModel)GetValue(ItemProperty); }
|
||||
set { SetValue(ItemProperty, value); }
|
||||
}
|
||||
|
||||
public CalendarItemViewModel Item
|
||||
|
||||
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemCommandBarFlyout flyout)
|
||||
{
|
||||
get { return (CalendarItemViewModel)GetValue(ItemProperty); }
|
||||
set { SetValue(ItemProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
private static void OnItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemCommandBarFlyout flyout)
|
||||
{
|
||||
flyout.UpdateMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMenuItems()
|
||||
{
|
||||
|
||||
flyout.UpdateMenuItems();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMenuItems()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<controls:WinoFontIcon
|
||||
<!--<controls:WinoFontIcon
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
Icon="CalendarEventRepeat"
|
||||
@@ -79,7 +79,7 @@
|
||||
FontSize="12"
|
||||
Foreground="{x:Bind helpers:XamlHelpers.GetReadableTextColor(CalendarItem.AssignedCalendar.BackgroundColorHex), Mode=OneWay}"
|
||||
Icon="CalendarEventMuiltiDay"
|
||||
Visibility="{x:Bind CalendarItem.IsMultiDayEvent, Mode=OneWay}" />
|
||||
Visibility="{x:Bind CalendarItem.IsMultiDayEvent, Mode=OneWay}" />-->
|
||||
</StackPanel>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
|
||||
@@ -7,192 +7,194 @@ using Windows.UI.Xaml.Input;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Messages;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public sealed partial class CalendarItemControl : UserControl
|
||||
{
|
||||
public sealed partial class CalendarItemControl : UserControl
|
||||
// Single tap has a delay to report double taps properly.
|
||||
private bool isSingleTap = false;
|
||||
|
||||
public static readonly DependencyProperty CalendarItemProperty = DependencyProperty.Register(nameof(CalendarItem), typeof(CalendarItemViewModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarItemChanged)));
|
||||
public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register(nameof(IsDragging), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsCustomEventAreaProperty = DependencyProperty.Register(nameof(IsCustomEventArea), typeof(bool), typeof(CalendarItemControl), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty CalendarItemTitleProperty = DependencyProperty.Register(nameof(CalendarItemTitle), typeof(string), typeof(CalendarItemControl), new PropertyMetadata(string.Empty));
|
||||
public static readonly DependencyProperty DisplayingDateProperty = DependencyProperty.Register(nameof(DisplayingDate), typeof(CalendarDayModel), typeof(CalendarItemControl), new PropertyMetadata(null, new PropertyChangedCallback(OnDisplayDateChanged)));
|
||||
|
||||
/// <summary>
|
||||
/// Whether the control is displaying as regular event or all-multi day area in the day control.
|
||||
/// </summary>
|
||||
public bool IsCustomEventArea
|
||||
{
|
||||
// Single tap has a delay to report double taps properly.
|
||||
private bool isSingleTap = false;
|
||||
get { return (bool)GetValue(IsCustomEventAreaProperty); }
|
||||
set { SetValue(IsCustomEventAreaProperty, value); }
|
||||
}
|
||||
|
||||
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>
|
||||
/// 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); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the control is displaying as regular event or all-multi day area in the day control.
|
||||
/// </summary>
|
||||
public bool IsCustomEventArea
|
||||
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)
|
||||
{
|
||||
get { return (bool)GetValue(IsCustomEventAreaProperty); }
|
||||
set { SetValue(IsCustomEventAreaProperty, value); }
|
||||
control.UpdateControlVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemControl control)
|
||||
{
|
||||
get { return (CalendarDayModel)GetValue(DisplayingDateProperty); }
|
||||
set { SetValue(DisplayingDateProperty, value); }
|
||||
control.UpdateControlVisuals();
|
||||
}
|
||||
}
|
||||
|
||||
public string CalendarItemTitle
|
||||
{
|
||||
get { return (string)GetValue(CalendarItemTitleProperty); }
|
||||
set { SetValue(CalendarItemTitleProperty, value); }
|
||||
}
|
||||
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.
|
||||
|
||||
public CalendarItemViewModel CalendarItem
|
||||
{
|
||||
get { return (CalendarItemViewModel)GetValue(CalendarItemProperty); }
|
||||
set { SetValue(CalendarItemProperty, value); }
|
||||
}
|
||||
if (CalendarItem == null) return;
|
||||
if (DisplayingDate == null) return;
|
||||
|
||||
public bool IsDragging
|
||||
{
|
||||
get { return (bool)GetValue(IsDraggingProperty); }
|
||||
set { SetValue(IsDraggingProperty, value); }
|
||||
}
|
||||
bool isMultiDayEvent = CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDay || CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay;
|
||||
|
||||
public CalendarItemControl()
|
||||
if (isMultiDayEvent)
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
// 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.
|
||||
|
||||
private static void OnDisplayDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemControl control)
|
||||
var periodRelation = CalendarItem.Period.GetRelation(DisplayingDate.Period);
|
||||
|
||||
if (periodRelation == Itenso.TimePeriod.PeriodRelation.StartInside ||
|
||||
periodRelation == PeriodRelation.EnclosingStartTouching)
|
||||
{
|
||||
control.UpdateControlVisuals();
|
||||
// hour -> title
|
||||
CalendarItemTitle = $"{DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.StartDateTime.TimeOfDay)} -> {CalendarItem.Title}";
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCalendarItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CalendarItemControl control)
|
||||
else if (
|
||||
periodRelation == PeriodRelation.EndInside ||
|
||||
periodRelation == PeriodRelation.EnclosingEndTouching)
|
||||
{
|
||||
control.UpdateControlVisuals();
|
||||
// title <- hour
|
||||
CalendarItemTitle = $"{CalendarItem.Title} <- {DisplayingDate.CalendarRenderOptions.CalendarSettings.GetTimeString(CalendarItem.EndDateTime.TimeOfDay)}";
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
else if (periodRelation == PeriodRelation.Enclosing)
|
||||
{
|
||||
// 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.
|
||||
// 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.
|
||||
|
||||
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}");
|
||||
CalendarItemTitle = $"{Translator.CalendarItemAllDay} {CalendarItem.Title}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not expected, but there it is.
|
||||
CalendarItemTitle = CalendarItem.Title;
|
||||
}
|
||||
|
||||
UpdateVisualStates();
|
||||
// Debug.WriteLine($"{CalendarItem.Title} Period relation with {DisplayingDate.Period.ToString()}: {periodRelation}");
|
||||
}
|
||||
else
|
||||
{
|
||||
CalendarItemTitle = CalendarItem.Title;
|
||||
}
|
||||
|
||||
private void UpdateVisualStates()
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
UpdateVisualStates();
|
||||
}
|
||||
|
||||
if (CalendarItem.IsAllDayEvent)
|
||||
private void UpdateVisualStates()
|
||||
{
|
||||
if (CalendarItem == null) return;
|
||||
|
||||
if (CalendarItem.CalendarItem.ItemType == CalendarItemType.AllDay)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
||||
}
|
||||
else if (CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay || CalendarItem.CalendarItem.ItemType == CalendarItemType.MultiDay)
|
||||
{
|
||||
if (IsCustomEventArea)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "AllDayEvent", true);
|
||||
}
|
||||
else if (CalendarItem.IsMultiDayEvent)
|
||||
{
|
||||
if (IsCustomEventArea)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide it.
|
||||
VisualStateManager.GoToState(this, "MultiDayEvent", true);
|
||||
}
|
||||
VisualStateManager.GoToState(this, "CustomAreaMultiDayEvent", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, "RegularEvent", true);
|
||||
// Hide it.
|
||||
VisualStateManager.GoToState(this, "MultiDayEvent", 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)
|
||||
else
|
||||
{
|
||||
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));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,42 @@
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations.
|
||||
/// </summary>
|
||||
public partial class CustomCalendarFlipView : FlipView
|
||||
{
|
||||
/// <summary>
|
||||
/// FlipView that hides the navigation buttons and exposes methods to navigate to the next and previous items with animations.
|
||||
/// </summary>
|
||||
public class CustomCalendarFlipView : FlipView
|
||||
private const string PART_PreviousButton = "PreviousButtonHorizontal";
|
||||
private const string PART_NextButton = "NextButtonHorizontal";
|
||||
|
||||
private Button PreviousButton;
|
||||
private Button NextButton;
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
private const string PART_PreviousButton = "PreviousButtonHorizontal";
|
||||
private const string PART_NextButton = "NextButtonHorizontal";
|
||||
base.OnApplyTemplate();
|
||||
|
||||
private Button PreviousButton;
|
||||
private Button NextButton;
|
||||
PreviousButton = GetTemplateChild(PART_PreviousButton) as Button;
|
||||
NextButton = GetTemplateChild(PART_NextButton) as Button;
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
// Hide navigation buttons
|
||||
PreviousButton.Opacity = NextButton.Opacity = 0;
|
||||
PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false;
|
||||
|
||||
PreviousButton = GetTemplateChild(PART_PreviousButton) as Button;
|
||||
NextButton = GetTemplateChild(PART_NextButton) as Button;
|
||||
var t = FindName("ScrollingHost");
|
||||
}
|
||||
|
||||
// Hide navigation buttons
|
||||
PreviousButton.Opacity = NextButton.Opacity = 0;
|
||||
PreviousButton.IsHitTestVisible = NextButton.IsHitTestVisible = false;
|
||||
public void GoPreviousFlip()
|
||||
{
|
||||
var backPeer = new ButtonAutomationPeer(PreviousButton);
|
||||
backPeer.Invoke();
|
||||
}
|
||||
|
||||
var t = FindName("ScrollingHost");
|
||||
}
|
||||
|
||||
public void GoPreviousFlip()
|
||||
{
|
||||
var backPeer = new ButtonAutomationPeer(PreviousButton);
|
||||
backPeer.Invoke();
|
||||
}
|
||||
|
||||
public void GoNextFlip()
|
||||
{
|
||||
var nextPeer = new ButtonAutomationPeer(NextButton);
|
||||
nextPeer.Invoke();
|
||||
}
|
||||
public void GoNextFlip()
|
||||
{
|
||||
var nextPeer = new ButtonAutomationPeer(NextButton);
|
||||
nextPeer.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,76 +3,75 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class DayColumnControl : Control
|
||||
{
|
||||
public class DayColumnControl : Control
|
||||
private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText);
|
||||
private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder);
|
||||
private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText);
|
||||
|
||||
private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl);
|
||||
|
||||
private const string TodayState = nameof(TodayState);
|
||||
private const string NotTodayState = nameof(NotTodayState);
|
||||
|
||||
private TextBlock HeaderDateDayText;
|
||||
private TextBlock ColumnHeaderText;
|
||||
private Border IsTodayBorder;
|
||||
private ItemsControl AllDayItemsControl;
|
||||
|
||||
public CalendarDayModel DayModel
|
||||
{
|
||||
private const string PART_HeaderDateDayText = nameof(PART_HeaderDateDayText);
|
||||
private const string PART_IsTodayBorder = nameof(PART_IsTodayBorder);
|
||||
private const string PART_ColumnHeaderText = nameof(PART_ColumnHeaderText);
|
||||
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
||||
set { SetValue(DayModelProperty, value); }
|
||||
}
|
||||
|
||||
private const string PART_AllDayItemsControl = nameof(PART_AllDayItemsControl);
|
||||
public static readonly DependencyProperty DayModelProperty = DependencyProperty.Register(nameof(DayModel), typeof(CalendarDayModel), typeof(DayColumnControl), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
|
||||
private const string TodayState = nameof(TodayState);
|
||||
private const string NotTodayState = nameof(NotTodayState);
|
||||
public DayColumnControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(DayColumnControl);
|
||||
}
|
||||
|
||||
private TextBlock HeaderDateDayText;
|
||||
private TextBlock ColumnHeaderText;
|
||||
private Border IsTodayBorder;
|
||||
private ItemsControl AllDayItemsControl;
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
public CalendarDayModel DayModel
|
||||
HeaderDateDayText = GetTemplateChild(PART_HeaderDateDayText) as TextBlock;
|
||||
ColumnHeaderText = GetTemplateChild(PART_ColumnHeaderText) as TextBlock;
|
||||
IsTodayBorder = GetTemplateChild(PART_IsTodayBorder) as Border;
|
||||
AllDayItemsControl = GetTemplateChild(PART_AllDayItemsControl) as ItemsControl;
|
||||
|
||||
UpdateValues();
|
||||
}
|
||||
|
||||
private static void OnRenderingPropertiesChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (control is DayColumnControl columnControl)
|
||||
{
|
||||
get { return (CalendarDayModel)GetValue(DayModelProperty); }
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,55 +3,54 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class DayHeaderControl : Control
|
||||
{
|
||||
public class DayHeaderControl : Control
|
||||
private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock);
|
||||
private TextBlock HeaderTextblock;
|
||||
|
||||
public DayHeaderDisplayType DisplayType
|
||||
{
|
||||
private const string PART_DayHeaderTextBlock = nameof(PART_DayHeaderTextBlock);
|
||||
private TextBlock HeaderTextblock;
|
||||
get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); }
|
||||
set { SetValue(DisplayTypeProperty, value); }
|
||||
}
|
||||
|
||||
public DayHeaderDisplayType DisplayType
|
||||
public DateTime Date
|
||||
{
|
||||
get { return (DateTime)GetValue(DateProperty); }
|
||||
set { SetValue(DateProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DateProperty = DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(DayHeaderControl), new PropertyMetadata(default(DateTime), new PropertyChangedCallback(OnHeaderPropertyChanged)));
|
||||
public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(DayHeaderDisplayType), typeof(DayHeaderControl), new PropertyMetadata(DayHeaderDisplayType.TwentyFourHour, new PropertyChangedCallback(OnHeaderPropertyChanged)));
|
||||
|
||||
public DayHeaderControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(DayHeaderControl);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
HeaderTextblock = GetTemplateChild(PART_DayHeaderTextBlock) as TextBlock;
|
||||
UpdateHeaderText();
|
||||
}
|
||||
|
||||
private static void OnHeaderPropertyChanged(DependencyObject control, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (control is DayHeaderControl headerControl)
|
||||
{
|
||||
get { return (DayHeaderDisplayType)GetValue(DisplayTypeProperty); }
|
||||
set { SetValue(DisplayTypeProperty, value); }
|
||||
headerControl.UpdateHeaderText();
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime Date
|
||||
private void UpdateHeaderText()
|
||||
{
|
||||
if (HeaderTextblock != null)
|
||||
{
|
||||
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");
|
||||
}
|
||||
HeaderTextblock.Text = DisplayType == DayHeaderDisplayType.TwelveHour ? Date.ToString("h tt") : Date.ToString("HH:mm");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,291 +10,290 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarControl : Control
|
||||
{
|
||||
public class WinoCalendarControl : Control
|
||||
private const string PART_WinoFlipView = nameof(PART_WinoFlipView);
|
||||
private const string PART_IdleGrid = nameof(PART_IdleGrid);
|
||||
|
||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||
|
||||
public event EventHandler ScrollPositionChanging;
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection<DayRangeRenderModel>), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty SelectedFlipViewIndexProperty = DependencyProperty.Register(nameof(SelectedFlipViewIndex), typeof(int), typeof(WinoCalendarControl), new PropertyMetadata(-1));
|
||||
public static readonly DependencyProperty SelectedFlipViewDayRangeProperty = DependencyProperty.Register(nameof(SelectedFlipViewDayRange), typeof(DayRangeRenderModel), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveCanvasChanged)));
|
||||
public static readonly DependencyProperty IsFlipIdleProperty = DependencyProperty.Register(nameof(IsFlipIdle), typeof(bool), typeof(WinoCalendarControl), new PropertyMetadata(true, new PropertyChangedCallback(OnIdleStateChanged)));
|
||||
public static readonly DependencyProperty ActiveScrollViewerProperty = DependencyProperty.Register(nameof(ActiveScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnActiveVerticalScrollViewerChanged)));
|
||||
|
||||
public static readonly DependencyProperty VerticalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(VerticalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||
public static readonly DependencyProperty HorizontalItemsPanelTemplateProperty = DependencyProperty.Register(nameof(HorizontalItemsPanelTemplate), typeof(ItemsPanelTemplate), typeof(WinoCalendarControl), new PropertyMetadata(null, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(CalendarOrientation), typeof(WinoCalendarControl), new PropertyMetadata(CalendarOrientation.Horizontal, new PropertyChangedCallback(OnCalendarOrientationPropertiesUpdated)));
|
||||
public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register(nameof(DisplayType), typeof(CalendarDisplayType), typeof(WinoCalendarControl), new PropertyMetadata(CalendarDisplayType.Day));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the day-week-month-year display type.
|
||||
/// Orientation is not determined by this property, but Orientation property.
|
||||
/// This property is used to determine the template to use for the calendar.
|
||||
/// </summary>
|
||||
public CalendarDisplayType DisplayType
|
||||
{
|
||||
private const string PART_WinoFlipView = nameof(PART_WinoFlipView);
|
||||
private const string PART_IdleGrid = nameof(PART_IdleGrid);
|
||||
get { return (CalendarDisplayType)GetValue(DisplayTypeProperty); }
|
||||
set { SetValue(DisplayTypeProperty, value); }
|
||||
}
|
||||
|
||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||
public CalendarOrientation Orientation
|
||||
{
|
||||
get { return (CalendarOrientation)GetValue(OrientationProperty); }
|
||||
set { SetValue(OrientationProperty, value); }
|
||||
}
|
||||
|
||||
public event EventHandler ScrollPositionChanging;
|
||||
public ItemsPanelTemplate VerticalItemsPanelTemplate
|
||||
{
|
||||
get { return (ItemsPanelTemplate)GetValue(VerticalItemsPanelTemplateProperty); }
|
||||
set { SetValue(VerticalItemsPanelTemplateProperty, value); }
|
||||
}
|
||||
|
||||
#region Dependency Properties
|
||||
public ItemsPanelTemplate HorizontalItemsPanelTemplate
|
||||
{
|
||||
get { return (ItemsPanelTemplate)GetValue(HorizontalItemsPanelTemplateProperty); }
|
||||
set { SetValue(HorizontalItemsPanelTemplateProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DayRangesProperty = DependencyProperty.Register(nameof(DayRanges), typeof(ObservableCollection<DayRangeRenderModel>), typeof(WinoCalendarControl), new PropertyMetadata(null));
|
||||
public 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 DayRangeRenderModel SelectedFlipViewDayRange
|
||||
{
|
||||
get { return (DayRangeRenderModel)GetValue(SelectedFlipViewDayRangeProperty); }
|
||||
set { SetValue(SelectedFlipViewDayRangeProperty, value); }
|
||||
}
|
||||
|
||||
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));
|
||||
public ScrollViewer ActiveScrollViewer
|
||||
{
|
||||
get { return (ScrollViewer)GetValue(ActiveScrollViewerProperty); }
|
||||
set { SetValue(ActiveScrollViewerProperty, value); }
|
||||
}
|
||||
|
||||
/// <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
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,179 +8,178 @@ using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Collections;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarFlipView : CustomCalendarFlipView
|
||||
{
|
||||
public class WinoCalendarFlipView : CustomCalendarFlipView
|
||||
public static readonly DependencyProperty IsIdleProperty = DependencyProperty.Register(nameof(IsIdle), typeof(bool), typeof(WinoCalendarFlipView), new PropertyMetadata(true));
|
||||
public static readonly DependencyProperty ActiveCanvasProperty = DependencyProperty.Register(nameof(ActiveCanvas), typeof(WinoDayTimelineCanvas), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty ActiveVerticalScrollViewerProperty = DependencyProperty.Register(nameof(ActiveVerticalScrollViewer), typeof(ScrollViewer), typeof(WinoCalendarFlipView), new PropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the active canvas that is currently displayed in the flip view.
|
||||
/// Each day-range of flip view item has a canvas that displays the day timeline.
|
||||
/// </summary>
|
||||
public WinoDayTimelineCanvas ActiveCanvas
|
||||
{
|
||||
public static readonly DependencyProperty IsIdleProperty = DependencyProperty.Register(nameof(IsIdle), typeof(bool), typeof(WinoCalendarFlipView), new PropertyMetadata(true));
|
||||
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));
|
||||
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
||||
set { SetValue(ActiveCanvasProperty, value); }
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// <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)
|
||||
{
|
||||
get { return (WinoDayTimelineCanvas)GetValue(ActiveCanvasProperty); }
|
||||
set { SetValue(ActiveCanvasProperty, value); }
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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); }
|
||||
}
|
||||
return ContainerFromIndex(SelectedIndex) as FlipViewItem;
|
||||
|
||||
public bool IsIdle
|
||||
{
|
||||
get { return (bool)GetValue(IsIdleProperty); }
|
||||
set { SetValue(IsIdleProperty, value); }
|
||||
}
|
||||
|
||||
public WinoCalendarFlipView()
|
||||
{
|
||||
RegisterPropertyChangedCallback(SelectedIndexProperty, new DependencyPropertyChangedCallback(OnSelectedIndexUpdated));
|
||||
RegisterPropertyChangedCallback(ItemsSourceProperty, new DependencyPropertyChangedCallback(OnItemsSourceChanged));
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnItemsSourceChanged(DependencyObject d, DependencyProperty e)
|
||||
private void UpdateActiveScrollViewer()
|
||||
{
|
||||
if (SelectedIndex < 0)
|
||||
ActiveVerticalScrollViewer = null;
|
||||
else
|
||||
{
|
||||
if (d is WinoCalendarFlipView flipView)
|
||||
GetCurrentFlipViewItem().ContinueWith(task =>
|
||||
{
|
||||
flipView.RegisterItemsSourceChange();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
var flipViewItem = task.Result;
|
||||
|
||||
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
ActiveVerticalScrollViewer = flipViewItem.FindDescendant<ScrollViewer>();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource()
|
||||
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
|
||||
}
|
||||
|
||||
public void UpdateActiveCanvas()
|
||||
{
|
||||
if (SelectedIndex < 0)
|
||||
ActiveCanvas = null;
|
||||
else
|
||||
{
|
||||
GetCurrentFlipViewItem().ContinueWith(task =>
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
{
|
||||
var flipViewItem = task.Result;
|
||||
|
||||
_ = Dispatcher.TryRunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
ActiveCanvas = flipViewItem.FindDescendant<WinoDayTimelineCanvas>();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified date in the calendar.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">Date to navigate.</param>
|
||||
public async void NavigateToDay(DateTime dateTime)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
|
||||
{
|
||||
// Find the day range that contains the date.
|
||||
var dayRange = GetItemsSource()?.FirstOrDefault(a => a.CalendarDays.Any(b => b.RepresentingDate.Date == dateTime.Date));
|
||||
|
||||
if (dayRange != null)
|
||||
{
|
||||
var navigationItemIndex = GetItemsSource().IndexOf(dayRange);
|
||||
|
||||
if (Math.Abs(navigationItemIndex - SelectedIndex) > 4)
|
||||
{
|
||||
// Difference between dates are high.
|
||||
// No need to animate this much, just go without animating.
|
||||
|
||||
SelectedIndex = navigationItemIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Until we reach the day in the flip, simulate next-prev button clicks.
|
||||
// This will make sure the FlipView animations are triggered.
|
||||
// Setting SelectedIndex directly doesn't trigger the animations.
|
||||
|
||||
while (SelectedIndex != navigationItemIndex)
|
||||
{
|
||||
if (SelectedIndex > navigationItemIndex)
|
||||
{
|
||||
GoPreviousFlip();
|
||||
}
|
||||
else
|
||||
{
|
||||
GoNextFlip();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ObservableRangeCollection<DayRangeRenderModel> GetItemsSource()
|
||||
=> ItemsSource as ObservableRangeCollection<DayRangeRenderModel>;
|
||||
}
|
||||
|
||||
@@ -9,286 +9,290 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Calendar.Models;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarPanel : Panel
|
||||
{
|
||||
public class WinoCalendarPanel : Panel
|
||||
private const double LastItemRightExtraMargin = 12d;
|
||||
|
||||
// Store each ICalendarItem measurements by their Id.
|
||||
private readonly Dictionary<ICalendarItem, CalendarItemMeasurement> _measurements = new Dictionary<ICalendarItem, CalendarItemMeasurement>();
|
||||
|
||||
public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0)));
|
||||
public static readonly DependencyProperty HourHeightProperty = DependencyProperty.Register(nameof(HourHeight), typeof(double), typeof(WinoCalendarPanel), new PropertyMetadata(0d));
|
||||
public static readonly DependencyProperty PeriodProperty = DependencyProperty.Register(nameof(Period), typeof(ITimePeriod), typeof(WinoCalendarPanel), new PropertyMetadata(null));
|
||||
|
||||
public ITimePeriod Period
|
||||
{
|
||||
private const double LastItemRightExtraMargin = 12d;
|
||||
get { return (ITimePeriod)GetValue(PeriodProperty); }
|
||||
set { SetValue(PeriodProperty, value); }
|
||||
}
|
||||
|
||||
// Store each ICalendarItem measurements by their Id.
|
||||
private readonly Dictionary<ICalendarItem, CalendarItemMeasurement> _measurements = new Dictionary<ICalendarItem, CalendarItemMeasurement>();
|
||||
public double HourHeight
|
||||
{
|
||||
get { return (double)GetValue(HourHeightProperty); }
|
||||
set { SetValue(HourHeightProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty EventItemMarginProperty = DependencyProperty.Register(nameof(EventItemMargin), typeof(Thickness), typeof(WinoCalendarPanel), new PropertyMetadata(new Thickness(0, 0, 0, 0)));
|
||||
public 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 Thickness EventItemMargin
|
||||
{
|
||||
get { return (Thickness)GetValue(EventItemMarginProperty); }
|
||||
set { SetValue(EventItemMarginProperty, value); }
|
||||
}
|
||||
|
||||
public ITimePeriod Period
|
||||
private void ResetMeasurements() => _measurements.Clear();
|
||||
|
||||
private double GetChildTopMargin(ICalendarItemViewModel calendarItemViewModel, double availableHeight)
|
||||
{
|
||||
var childStart = calendarItemViewModel.LocalStartDateTime;
|
||||
|
||||
if (childStart <= Period.Start)
|
||||
{
|
||||
get { return (ITimePeriod)GetValue(PeriodProperty); }
|
||||
set { SetValue(PeriodProperty, value); }
|
||||
// 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;
|
||||
}
|
||||
|
||||
public double HourHeight
|
||||
double minutesFromStart = (childStart - Period.Start).TotalMinutes;
|
||||
return (minutesFromStart / 1440) * availableHeight;
|
||||
}
|
||||
|
||||
private double GetChildWidth(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
||||
{
|
||||
return (calendarItemMeasurement.Right - calendarItemMeasurement.Left) * availableWidth;
|
||||
}
|
||||
|
||||
private double GetChildLeftMargin(CalendarItemMeasurement calendarItemMeasurement, double availableWidth)
|
||||
=> availableWidth * calendarItemMeasurement.Left;
|
||||
|
||||
private double GetChildHeight(ICalendarItemViewModel child)
|
||||
{
|
||||
// All day events are not measured.
|
||||
if (child.ItemType == CalendarItemType.AllDay) 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.ItemType != CalendarItemType.MultiDay || child.ItemType != CalendarItemType.MultiDayAllDay)
|
||||
{
|
||||
get { return (double)GetValue(HourHeightProperty); }
|
||||
set { SetValue(HourHeightProperty, value); }
|
||||
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;
|
||||
}
|
||||
|
||||
public Thickness EventItemMargin
|
||||
return (childDurationInMinutes / 1440) * availableHeight;
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
ResetMeasurements();
|
||||
return base.MeasureOverride(availableSize);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
if (Period == null || HourHeight == 0d) return finalSize;
|
||||
|
||||
// Measure/arrange each child height and width.
|
||||
// This is a vertical calendar. Therefore the height of each child is the duration of the event.
|
||||
// Children weights for left and right will be saved if they don't exist.
|
||||
// This is important because we don't want to measure the weights again.
|
||||
// They don't change until new event is added or removed.
|
||||
// Width of the each child may depend on the rectangle packing algorithm.
|
||||
// Children are first categorized into columns. Then each column is shifted to the left until
|
||||
// no overlap occurs. The width of each child is calculated based on the number of columns it spans.
|
||||
|
||||
double availableHeight = finalSize.Height;
|
||||
double availableWidth = finalSize.Width;
|
||||
|
||||
var calendarControls = Children.Cast<ContentPresenter>();
|
||||
|
||||
if (!calendarControls.Any()) return base.ArrangeOverride(finalSize);
|
||||
|
||||
var events = calendarControls.Select(a => a.Content as CalendarItemViewModel);
|
||||
|
||||
LayoutEvents(events);
|
||||
|
||||
foreach (var control in calendarControls)
|
||||
{
|
||||
get { return (Thickness)GetValue(EventItemMarginProperty); }
|
||||
set { SetValue(EventItemMarginProperty, value); }
|
||||
}
|
||||
// We can't arrange this child.
|
||||
if (!(control.Content is ICalendarItemViewModel child)) continue;
|
||||
|
||||
private void ResetMeasurements() => _measurements.Clear();
|
||||
bool isHorizontallyLastItem = false;
|
||||
|
||||
private double GetChildTopMargin(ICalendarItem calendarItemViewModel, double availableHeight)
|
||||
{
|
||||
var childStart = calendarItemViewModel.StartDate;
|
||||
double childWidth = 0,
|
||||
childHeight = Math.Max(0, GetChildHeight(child)),
|
||||
childTop = Math.Max(0, GetChildTopMargin(child, availableHeight)),
|
||||
childLeft = 0;
|
||||
|
||||
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.
|
||||
// No need to measure anything here.
|
||||
if (childHeight == 0) continue;
|
||||
|
||||
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
|
||||
if (!_measurements.ContainsKey(child))
|
||||
{
|
||||
// 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;
|
||||
childLeft = 0;
|
||||
childWidth = availableWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
_measurements.Add(calendarItem, measurement);
|
||||
var childMeasurement = _measurements[child];
|
||||
|
||||
childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
|
||||
childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
|
||||
|
||||
isHorizontallyLastItem = childMeasurement.Right == 1;
|
||||
}
|
||||
|
||||
// Add additional right margin to items that falls on the right edge of the panel.
|
||||
double extraRightMargin = 0;
|
||||
|
||||
// Multi-day events don't have any margin and their hit test is disabled.
|
||||
if (child.ItemType != CalendarItemType.MultiDay || child.ItemType != CalendarItemType.MultiDayAllDay)
|
||||
{
|
||||
// Max of 5% of the width or 20px max.
|
||||
extraRightMargin = isHorizontallyLastItem ? Math.Max(LastItemRightExtraMargin, finalSize.Width * 5 / 100) : 0;
|
||||
}
|
||||
|
||||
if (childWidth < 0) childWidth = 1;
|
||||
|
||||
bool isAllOrMultiDayEvent = child.ItemType == CalendarItemType.AllDay || child.ItemType == CalendarItemType.MultiDay || child.ItemType == CalendarItemType.MultiDayAllDay;
|
||||
|
||||
// Regular events must have 2px margin
|
||||
if (!isAllOrMultiDayEvent)
|
||||
{
|
||||
childLeft += 2;
|
||||
childTop += 2;
|
||||
childHeight -= 2;
|
||||
childWidth -= 2;
|
||||
}
|
||||
|
||||
var arrangementRect = new Rect(childLeft + EventItemMargin.Left, childTop + EventItemMargin.Top, Math.Max(childWidth - extraRightMargin, 1), childHeight);
|
||||
|
||||
// Make sure measured size will fit in the arranged box.
|
||||
var measureSize = arrangementRect.ToSize();
|
||||
control.Measure(measureSize);
|
||||
control.Arrange(arrangementRect);
|
||||
|
||||
//Debug.WriteLine($"{child.Title}, Measured: {measureSize}, Arranged: {arrangementRect}");
|
||||
}
|
||||
|
||||
// Pick the left and right positions of each event, such that there are no overlap.
|
||||
private void LayoutEvents(IEnumerable<ICalendarItem> events)
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
#region ColumSpanning and Packing Algorithm
|
||||
|
||||
private void AddOrUpdateMeasurement(ICalendarItem calendarItem, CalendarItemMeasurement measurement)
|
||||
{
|
||||
if (_measurements.ContainsKey(calendarItem))
|
||||
{
|
||||
var columns = new List<List<ICalendarItem>>();
|
||||
DateTime? lastEventEnding = null;
|
||||
_measurements[calendarItem] = measurement;
|
||||
}
|
||||
else
|
||||
{
|
||||
_measurements.Add(calendarItem, measurement);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ev in events.OrderBy(ev => ev.StartDate).ThenBy(ev => ev.EndDate))
|
||||
{
|
||||
// Multi-day events are not measured.
|
||||
if (ev.IsMultiDayEvent) continue;
|
||||
// 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;
|
||||
|
||||
if (ev.Period.Start >= lastEventEnding)
|
||||
{
|
||||
PackEvents(columns);
|
||||
columns.Clear();
|
||||
lastEventEnding = null;
|
||||
}
|
||||
foreach (var ev in events.OrderBy(ev => ev.StartDateTime).ThenBy(ev => ev.EndDateTime))
|
||||
{
|
||||
// Multi-day events are not measured.
|
||||
bool isMultiDayEvent = ev.ItemType == CalendarItemType.MultiDay || ev.ItemType == CalendarItemType.MultiDayAllDay;
|
||||
|
||||
bool placed = false;
|
||||
if (isMultiDayEvent) continue;
|
||||
|
||||
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)
|
||||
if (ev.Period.Start >= lastEventEnding)
|
||||
{
|
||||
PackEvents(columns);
|
||||
columns.Clear();
|
||||
lastEventEnding = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
bool placed = false;
|
||||
|
||||
foreach (var col in columns)
|
||||
{
|
||||
foreach (var ev in col)
|
||||
if (!col.Last().Period.OverlapsWith(ev.Period))
|
||||
{
|
||||
int colSpan = ExpandEvent(ev, iColumn, columns);
|
||||
|
||||
var leftWeight = iColumn / numColumns;
|
||||
var rightWeight = (iColumn + colSpan) / numColumns;
|
||||
|
||||
AddOrUpdateMeasurement(ev, new CalendarItemMeasurement(leftWeight, rightWeight));
|
||||
col.Add(ev);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
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))
|
||||
if (!placed)
|
||||
{
|
||||
foreach (var ev1 in col)
|
||||
{
|
||||
if (ev1.Period.OverlapsWith(ev.Period)) return colSpan;
|
||||
}
|
||||
columns.Add(new List<ICalendarItem> { ev });
|
||||
}
|
||||
if (lastEventEnding == null || ev.Period.End > lastEventEnding.Value)
|
||||
{
|
||||
lastEventEnding = ev.Period.End;
|
||||
}
|
||||
}
|
||||
if (columns.Count > 0)
|
||||
{
|
||||
PackEvents(columns);
|
||||
}
|
||||
}
|
||||
|
||||
colSpan++;
|
||||
// Set the left and right positions for each event in the connected group.
|
||||
private void PackEvents(List<List<ICalendarItem>> columns)
|
||||
{
|
||||
float numColumns = columns.Count;
|
||||
int iColumn = 0;
|
||||
|
||||
foreach (var col in columns)
|
||||
{
|
||||
foreach (var ev in col)
|
||||
{
|
||||
int colSpan = ExpandEvent(ev, iColumn, columns);
|
||||
|
||||
var leftWeight = iColumn / numColumns;
|
||||
var rightWeight = (iColumn + colSpan) / numColumns;
|
||||
|
||||
AddOrUpdateMeasurement(ev, new CalendarItemMeasurement(leftWeight, rightWeight));
|
||||
}
|
||||
|
||||
return colSpan;
|
||||
iColumn++;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks how many columns the event can expand into, without colliding with other events.
|
||||
private int ExpandEvent(ICalendarItem ev, int iColumn, List<List<ICalendarItem>> columns)
|
||||
{
|
||||
int colSpan = 1;
|
||||
|
||||
foreach (var col in columns.Skip(iColumn + 1))
|
||||
{
|
||||
foreach (var ev1 in col)
|
||||
{
|
||||
if (ev1.Period.OverlapsWith(ev.Period)) return colSpan;
|
||||
}
|
||||
|
||||
colSpan++;
|
||||
}
|
||||
|
||||
#endregion
|
||||
return colSpan;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -4,89 +4,88 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarTypeSelectorControl : Control
|
||||
{
|
||||
public class WinoCalendarTypeSelectorControl : Control
|
||||
private const string PART_TodayButton = nameof(PART_TodayButton);
|
||||
private const string PART_DayToggle = nameof(PART_DayToggle);
|
||||
private const string PART_WeekToggle = nameof(PART_WeekToggle);
|
||||
private const string PART_MonthToggle = nameof(PART_MonthToggle);
|
||||
private const string PART_YearToggle = nameof(PART_YearToggle);
|
||||
|
||||
public static readonly DependencyProperty SelectedTypeProperty = DependencyProperty.Register(nameof(SelectedType), typeof(CalendarDisplayType), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(CalendarDisplayType.Week));
|
||||
public static readonly DependencyProperty DisplayDayCountProperty = DependencyProperty.Register(nameof(DisplayDayCount), typeof(int), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(0));
|
||||
public static readonly DependencyProperty TodayClickedCommandProperty = DependencyProperty.Register(nameof(TodayClickedCommand), typeof(ICommand), typeof(WinoCalendarTypeSelectorControl), new PropertyMetadata(null));
|
||||
|
||||
public ICommand TodayClickedCommand
|
||||
{
|
||||
private const string PART_TodayButton = nameof(PART_TodayButton);
|
||||
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);
|
||||
get { return (ICommand)GetValue(TodayClickedCommandProperty); }
|
||||
set { SetValue(TodayClickedCommandProperty, value); }
|
||||
}
|
||||
|
||||
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 CalendarDisplayType SelectedType
|
||||
{
|
||||
get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); }
|
||||
set { SetValue(SelectedTypeProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand TodayClickedCommand
|
||||
{
|
||||
get { return (ICommand)GetValue(TodayClickedCommandProperty); }
|
||||
set { SetValue(TodayClickedCommandProperty, value); }
|
||||
}
|
||||
public int DisplayDayCount
|
||||
{
|
||||
get { return (int)GetValue(DisplayDayCountProperty); }
|
||||
set { SetValue(DisplayDayCountProperty, value); }
|
||||
}
|
||||
|
||||
public CalendarDisplayType SelectedType
|
||||
{
|
||||
get { return (CalendarDisplayType)GetValue(SelectedTypeProperty); }
|
||||
set { SetValue(SelectedTypeProperty, value); }
|
||||
}
|
||||
private AppBarButton _todayButton;
|
||||
private AppBarToggleButton _dayToggle;
|
||||
private AppBarToggleButton _weekToggle;
|
||||
private AppBarToggleButton _monthToggle;
|
||||
private AppBarToggleButton _yearToggle;
|
||||
|
||||
public int DisplayDayCount
|
||||
{
|
||||
get { return (int)GetValue(DisplayDayCountProperty); }
|
||||
set { SetValue(DisplayDayCountProperty, value); }
|
||||
}
|
||||
public WinoCalendarTypeSelectorControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl);
|
||||
}
|
||||
|
||||
private AppBarButton _todayButton;
|
||||
private AppBarToggleButton _dayToggle;
|
||||
private AppBarToggleButton _weekToggle;
|
||||
private AppBarToggleButton _monthToggle;
|
||||
private AppBarToggleButton _yearToggle;
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
public WinoCalendarTypeSelectorControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(WinoCalendarTypeSelectorControl);
|
||||
}
|
||||
_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;
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
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 = 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;
|
||||
_todayButton.Click += TodayClicked;
|
||||
|
||||
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));
|
||||
_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); };
|
||||
|
||||
_todayButton.Click += TodayClicked;
|
||||
UpdateToggleButtonStates();
|
||||
}
|
||||
|
||||
_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); };
|
||||
private void TodayClicked(object sender, RoutedEventArgs e) => TodayClickedCommand?.Execute(null);
|
||||
|
||||
UpdateToggleButtonStates();
|
||||
}
|
||||
private void SetSelectedType(CalendarDisplayType type)
|
||||
{
|
||||
SelectedType = type;
|
||||
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;
|
||||
}
|
||||
private void UpdateToggleButtonStates()
|
||||
{
|
||||
_dayToggle.IsChecked = SelectedType == CalendarDisplayType.Day;
|
||||
_weekToggle.IsChecked = SelectedType == CalendarDisplayType.Week;
|
||||
_monthToggle.IsChecked = SelectedType == CalendarDisplayType.Month;
|
||||
_yearToggle.IsChecked = SelectedType == CalendarDisplayType.Year;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,140 +8,139 @@ using Windows.UI.Xaml.Media;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoCalendarView : Control
|
||||
{
|
||||
public class WinoCalendarView : Control
|
||||
private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder);
|
||||
private const string PART_CalendarView = nameof(PART_CalendarView);
|
||||
|
||||
public static readonly DependencyProperty HighlightedDateRangeProperty = DependencyProperty.Register(nameof(HighlightedDateRange), typeof(DateRange), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnHighlightedDateRangeChanged)));
|
||||
public static readonly DependencyProperty VisibleDateBackgroundProperty = DependencyProperty.Register(nameof(VisibleDateBackground), typeof(Brush), typeof(WinoCalendarView), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertiesChanged)));
|
||||
public static readonly DependencyProperty DateClickedCommandProperty = DependencyProperty.Register(nameof(DateClickedCommand), typeof(ICommand), typeof(WinoCalendarView), new PropertyMetadata(null));
|
||||
public static readonly DependencyProperty TodayBackgroundColorProperty = DependencyProperty.Register(nameof(TodayBackgroundColor), typeof(Color), typeof(WinoCalendarView), new PropertyMetadata(null));
|
||||
|
||||
public Color TodayBackgroundColor
|
||||
{
|
||||
private const string PART_DayViewItemBorder = nameof(PART_DayViewItemBorder);
|
||||
private const string PART_CalendarView = nameof(PART_CalendarView);
|
||||
get { return (Color)GetValue(TodayBackgroundColorProperty); }
|
||||
set { SetValue(TodayBackgroundColorProperty, value); }
|
||||
}
|
||||
|
||||
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));
|
||||
/// <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); }
|
||||
}
|
||||
|
||||
public Color TodayBackgroundColor
|
||||
/// <summary>
|
||||
/// Gets or sets the highlighted range of dates.
|
||||
/// </summary>
|
||||
public DateRange HighlightedDateRange
|
||||
{
|
||||
get { return (DateRange)GetValue(HighlightedDateRangeProperty); }
|
||||
set { SetValue(HighlightedDateRangeProperty, value); }
|
||||
}
|
||||
|
||||
public Brush VisibleDateBackground
|
||||
{
|
||||
get { return (Brush)GetValue(VisibleDateBackgroundProperty); }
|
||||
set { SetValue(VisibleDateBackgroundProperty, value); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private CalendarView CalendarView;
|
||||
|
||||
public WinoCalendarView()
|
||||
{
|
||||
DefaultStyleKey = typeof(WinoCalendarView);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
CalendarView = GetTemplateChild(PART_CalendarView) as CalendarView;
|
||||
|
||||
Guard.IsNotNull(CalendarView, nameof(CalendarView));
|
||||
|
||||
CalendarView.SelectedDatesChanged -= InternalCalendarViewSelectionChanged;
|
||||
CalendarView.SelectedDatesChanged += InternalCalendarViewSelectionChanged;
|
||||
|
||||
// TODO: Should come from settings.
|
||||
CalendarView.FirstDayOfWeek = Windows.Globalization.DayOfWeek.Monday;
|
||||
|
||||
// Everytime display mode changes, update the visible date range backgrounds.
|
||||
// If users go back from year -> month -> day, we need to update the visible date range backgrounds.
|
||||
|
||||
CalendarView.RegisterPropertyChangedCallback(CalendarView.DisplayModeProperty, (s, e) => UpdateVisibleDateRangeBackgrounds());
|
||||
}
|
||||
|
||||
private void InternalCalendarViewSelectionChanged(CalendarView sender, CalendarViewSelectedDatesChangedEventArgs args)
|
||||
{
|
||||
if (args.AddedDates?.Count > 0)
|
||||
{
|
||||
get { return (Color)GetValue(TodayBackgroundColorProperty); }
|
||||
set { SetValue(TodayBackgroundColorProperty, value); }
|
||||
var clickedDate = args.AddedDates[0].Date;
|
||||
SetInnerDisplayDate(clickedDate);
|
||||
|
||||
var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate);
|
||||
DateClickedCommand?.Execute(clickArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the command to execute when a date is picked.
|
||||
/// Unused.
|
||||
/// </summary>
|
||||
public ICommand DateClickedCommand
|
||||
// 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)
|
||||
{
|
||||
get { return (ICommand)GetValue(DateClickedCommandProperty); }
|
||||
set { SetValue(DateClickedCommandProperty, value); }
|
||||
control.UpdateVisibleDateRangeBackgrounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the highlighted range of dates.
|
||||
/// </summary>
|
||||
public DateRange HighlightedDateRange
|
||||
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)
|
||||
{
|
||||
get { return (DateRange)GetValue(HighlightedDateRangeProperty); }
|
||||
set { SetValue(HighlightedDateRangeProperty, value); }
|
||||
control.SetInnerDisplayDate(control.HighlightedDateRange.StartDate);
|
||||
control.UpdateVisibleDateRangeBackgrounds();
|
||||
}
|
||||
}
|
||||
|
||||
public Brush VisibleDateBackground
|
||||
public void UpdateVisibleDateRangeBackgrounds()
|
||||
{
|
||||
if (HighlightedDateRange == null || VisibleDateBackground == null || TodayBackgroundColor == null || CalendarView == null) return;
|
||||
|
||||
var markDateCalendarDayItems = WinoVisualTreeHelper.FindDescendants<CalendarViewDayItem>(CalendarView);
|
||||
|
||||
foreach (var calendarDayItem in markDateCalendarDayItems)
|
||||
{
|
||||
get { return (Brush)GetValue(VisibleDateBackgroundProperty); }
|
||||
set { SetValue(VisibleDateBackgroundProperty, value); }
|
||||
}
|
||||
var border = WinoVisualTreeHelper.GetChildObject<Border>(calendarDayItem, PART_DayViewItemBorder);
|
||||
|
||||
if (border == null) return;
|
||||
|
||||
|
||||
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)
|
||||
if (calendarDayItem.Date.Date == DateTime.Today.Date)
|
||||
{
|
||||
var clickedDate = args.AddedDates[0].Date;
|
||||
SetInnerDisplayDate(clickedDate);
|
||||
|
||||
var clickArgs = new CalendarViewDayClickedEventArgs(clickedDate);
|
||||
DateClickedCommand?.Execute(clickArgs);
|
||||
border.Background = new SolidColorBrush(TodayBackgroundColor);
|
||||
}
|
||||
|
||||
// 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)
|
||||
else if (calendarDayItem.Date.Date >= HighlightedDateRange.StartDate.Date && calendarDayItem.Date.Date < HighlightedDateRange.EndDate.Date)
|
||||
{
|
||||
control.UpdateVisibleDateRangeBackgrounds();
|
||||
border.Background = VisibleDateBackground;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
border.Background = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,269 +10,268 @@ using Windows.UI.Xaml.Media;
|
||||
using Wino.Calendar.Args;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Controls
|
||||
namespace Wino.Calendar.Controls;
|
||||
|
||||
public partial class WinoDayTimelineCanvas : Control, IDisposable
|
||||
{
|
||||
public class WinoDayTimelineCanvas : Control, IDisposable
|
||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||
|
||||
private const string PART_InternalCanvas = nameof(PART_InternalCanvas);
|
||||
private CanvasControl Canvas;
|
||||
|
||||
public static readonly DependencyProperty RenderOptionsProperty = DependencyProperty.Register(nameof(RenderOptions), typeof(CalendarRenderOptions), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
public static readonly DependencyProperty SeperatorColorProperty = DependencyProperty.Register(nameof(SeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
public static readonly DependencyProperty HalfHourSeperatorColorProperty = DependencyProperty.Register(nameof(HalfHourSeperatorColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
public static readonly DependencyProperty SelectedCellBackgroundBrushProperty = DependencyProperty.Register(nameof(SelectedCellBackgroundBrush), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
public static readonly DependencyProperty WorkingHourCellBackgroundColorProperty = DependencyProperty.Register(nameof(WorkingHourCellBackgroundColor), typeof(SolidColorBrush), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
public static readonly DependencyProperty SelectedDateTimeProperty = DependencyProperty.Register(nameof(SelectedDateTime), typeof(DateTime?), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedDateTimeChanged)));
|
||||
public static readonly DependencyProperty PositionerUIElementProperty = DependencyProperty.Register(nameof(PositionerUIElement), typeof(UIElement), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null));
|
||||
|
||||
public UIElement PositionerUIElement
|
||||
{
|
||||
public event EventHandler<TimelineCellSelectedArgs> TimelineCellSelected;
|
||||
public event EventHandler<TimelineCellUnselectedArgs> TimelineCellUnselected;
|
||||
get { return (UIElement)GetValue(PositionerUIElementProperty); }
|
||||
set { SetValue(PositionerUIElementProperty, value); }
|
||||
}
|
||||
|
||||
private const string PART_InternalCanvas = nameof(PART_InternalCanvas);
|
||||
private CanvasControl Canvas;
|
||||
public CalendarRenderOptions RenderOptions
|
||||
{
|
||||
get { return (CalendarRenderOptions)GetValue(RenderOptionsProperty); }
|
||||
set { SetValue(RenderOptionsProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RenderOptionsProperty = DependencyProperty.Register(nameof(RenderOptions), typeof(CalendarRenderOptions), typeof(WinoDayTimelineCanvas), new PropertyMetadata(null, new PropertyChangedCallback(OnRenderingPropertiesChanged)));
|
||||
public 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 SolidColorBrush HalfHourSeperatorColor
|
||||
{
|
||||
get { return (SolidColorBrush)GetValue(HalfHourSeperatorColorProperty); }
|
||||
set { SetValue(HalfHourSeperatorColorProperty, value); }
|
||||
}
|
||||
|
||||
public UIElement PositionerUIElement
|
||||
public SolidColorBrush SeperatorColor
|
||||
{
|
||||
get { return (SolidColorBrush)GetValue(SeperatorColorProperty); }
|
||||
set { SetValue(SeperatorColorProperty, value); }
|
||||
}
|
||||
|
||||
public SolidColorBrush WorkingHourCellBackgroundColor
|
||||
{
|
||||
get { return (SolidColorBrush)GetValue(WorkingHourCellBackgroundColorProperty); }
|
||||
set { SetValue(WorkingHourCellBackgroundColorProperty, value); }
|
||||
}
|
||||
|
||||
public SolidColorBrush SelectedCellBackgroundBrush
|
||||
{
|
||||
get { return (SolidColorBrush)GetValue(SelectedCellBackgroundBrushProperty); }
|
||||
set { SetValue(SelectedCellBackgroundBrushProperty, value); }
|
||||
}
|
||||
|
||||
public DateTime? SelectedDateTime
|
||||
{
|
||||
get { return (DateTime?)GetValue(SelectedDateTimeProperty); }
|
||||
set { SetValue(SelectedDateTimeProperty, value); }
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
Canvas = GetTemplateChild(PART_InternalCanvas) as CanvasControl;
|
||||
|
||||
// TODO: These will leak. Dispose them properly when needed.
|
||||
Canvas.Draw += OnCanvasDraw;
|
||||
Canvas.PointerPressed += OnCanvasPointerPressed;
|
||||
|
||||
ForceDraw();
|
||||
}
|
||||
|
||||
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is WinoDayTimelineCanvas control)
|
||||
{
|
||||
get { return (UIElement)GetValue(PositionerUIElementProperty); }
|
||||
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)
|
||||
{
|
||||
if (e.OldValue != null && e.NewValue == null)
|
||||
{
|
||||
control.RaiseCellUnselected();
|
||||
}
|
||||
|
||||
control.ForceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseCellUnselected()
|
||||
{
|
||||
TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs());
|
||||
}
|
||||
|
||||
private void OnCanvasPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
if (RenderOptions == null) return;
|
||||
|
||||
var hourHeight = RenderOptions.CalendarSettings.HourHeight;
|
||||
|
||||
// When users click to cell we need to find the day, hour and minutes (first 30 minutes or second 30 minutes) that it represents on the timeline.
|
||||
|
||||
PointerPoint positionerRootPoint = e.GetCurrentPoint(PositionerUIElement);
|
||||
PointerPoint canvasPointerPoint = e.GetCurrentPoint(Canvas);
|
||||
|
||||
Point touchPoint = canvasPointerPoint.Position;
|
||||
|
||||
var singleDayWidth = (Canvas.ActualWidth / RenderOptions.TotalDayCount);
|
||||
|
||||
int day = (int)(touchPoint.X / singleDayWidth);
|
||||
int hour = (int)(touchPoint.Y / hourHeight);
|
||||
|
||||
bool isSecondHalf = touchPoint.Y % hourHeight > (hourHeight / 2);
|
||||
|
||||
var diffX = positionerRootPoint.Position.X - touchPoint.X;
|
||||
var diffY = positionerRootPoint.Position.Y - touchPoint.Y;
|
||||
|
||||
var cellStartRelativePositionX = diffX + (day * singleDayWidth);
|
||||
var cellEndRelativePositionX = cellStartRelativePositionX + singleDayWidth;
|
||||
|
||||
var cellStartRelativePositionY = diffY + (hour * hourHeight) + (isSecondHalf ? hourHeight / 2 : 0);
|
||||
var cellEndRelativePositionY = cellStartRelativePositionY + (isSecondHalf ? (hourHeight / 2) : hourHeight);
|
||||
|
||||
var cellSize = new Size(cellEndRelativePositionX - cellStartRelativePositionX, hourHeight / 2);
|
||||
var positionerPoint = new Point(cellStartRelativePositionX, cellStartRelativePositionY);
|
||||
|
||||
var clickedDateTime = RenderOptions.DateRange.StartDate.AddDays(day).AddHours(hour).AddMinutes(isSecondHalf ? 30 : 0);
|
||||
|
||||
// If there is already a selected date, in order to mimic the popup behavior, we need to dismiss the previous selection first.
|
||||
// Next click will be a new selection.
|
||||
|
||||
// Raise the events directly here instead of DP to not lose pointer position.
|
||||
if (clickedDateTime == SelectedDateTime || SelectedDateTime != null)
|
||||
{
|
||||
SelectedDateTime = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedDateTime = clickedDateTime;
|
||||
TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize));
|
||||
control.RaiseCellUnselected();
|
||||
}
|
||||
|
||||
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;
|
||||
control.ForceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseCellUnselected()
|
||||
{
|
||||
TimelineCellUnselected?.Invoke(this, new TimelineCellUnselectedArgs());
|
||||
}
|
||||
|
||||
private void OnCanvasPointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
if (RenderOptions == null) return;
|
||||
|
||||
var hourHeight = RenderOptions.CalendarSettings.HourHeight;
|
||||
|
||||
// When users click to cell we need to find the day, hour and minutes (first 30 minutes or second 30 minutes) that it represents on the timeline.
|
||||
|
||||
PointerPoint positionerRootPoint = e.GetCurrentPoint(PositionerUIElement);
|
||||
PointerPoint canvasPointerPoint = e.GetCurrentPoint(Canvas);
|
||||
|
||||
Point touchPoint = canvasPointerPoint.Position;
|
||||
|
||||
var singleDayWidth = (Canvas.ActualWidth / RenderOptions.TotalDayCount);
|
||||
|
||||
int day = (int)(touchPoint.X / singleDayWidth);
|
||||
int hour = (int)(touchPoint.Y / hourHeight);
|
||||
|
||||
bool isSecondHalf = touchPoint.Y % hourHeight > (hourHeight / 2);
|
||||
|
||||
var diffX = positionerRootPoint.Position.X - touchPoint.X;
|
||||
var diffY = positionerRootPoint.Position.Y - touchPoint.Y;
|
||||
|
||||
var cellStartRelativePositionX = diffX + (day * singleDayWidth);
|
||||
var cellEndRelativePositionX = cellStartRelativePositionX + singleDayWidth;
|
||||
|
||||
var cellStartRelativePositionY = diffY + (hour * hourHeight) + (isSecondHalf ? hourHeight / 2 : 0);
|
||||
var cellEndRelativePositionY = cellStartRelativePositionY + (isSecondHalf ? (hourHeight / 2) : hourHeight);
|
||||
|
||||
var cellSize = new Size(cellEndRelativePositionX - cellStartRelativePositionX, hourHeight / 2);
|
||||
var positionerPoint = new Point(cellStartRelativePositionX, cellStartRelativePositionY);
|
||||
|
||||
var clickedDateTime = RenderOptions.DateRange.StartDate.AddDays(day).AddHours(hour).AddMinutes(isSecondHalf ? 30 : 0);
|
||||
|
||||
// If there is already a selected date, in order to mimic the popup behavior, we need to dismiss the previous selection first.
|
||||
// Next click will be a new selection.
|
||||
|
||||
// Raise the events directly here instead of DP to not lose pointer position.
|
||||
if (clickedDateTime == SelectedDateTime || SelectedDateTime != null)
|
||||
{
|
||||
SelectedDateTime = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedDateTime = clickedDateTime;
|
||||
TimelineCellSelected?.Invoke(this, new TimelineCellSelectedArgs(clickedDateTime, touchPoint, positionerPoint, cellSize));
|
||||
}
|
||||
|
||||
Debug.WriteLine($"Clicked: {clickedDateTime}");
|
||||
}
|
||||
|
||||
public WinoDayTimelineCanvas()
|
||||
{
|
||||
DefaultStyleKey = typeof(WinoDayTimelineCanvas);
|
||||
}
|
||||
|
||||
private static void OnRenderingPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is WinoDayTimelineCanvas control)
|
||||
{
|
||||
control.ForceDraw();
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceDraw() => Canvas?.Invalidate();
|
||||
|
||||
private bool CanDrawTimeline()
|
||||
{
|
||||
return RenderOptions != null
|
||||
&& Canvas != null
|
||||
&& Canvas.ReadyToDraw
|
||||
&& WorkingHourCellBackgroundColor != null
|
||||
&& SeperatorColor != null
|
||||
&& HalfHourSeperatorColor != null
|
||||
&& SelectedCellBackgroundBrush != null;
|
||||
}
|
||||
|
||||
private void OnCanvasDraw(CanvasControl sender, CanvasDrawEventArgs args)
|
||||
{
|
||||
if (!CanDrawTimeline()) return;
|
||||
|
||||
int hours = 24;
|
||||
|
||||
double canvasWidth = Canvas.ActualWidth;
|
||||
double canvasHeight = Canvas.ActualHeight;
|
||||
|
||||
if (canvasWidth == 0 || canvasHeight == 0) return;
|
||||
|
||||
// Calculate the width of each rectangle (1 day column)
|
||||
// Equal distribution of the whole width.
|
||||
double rectWidth = canvasWidth / RenderOptions.TotalDayCount;
|
||||
|
||||
// Calculate the height of each rectangle (1 hour row)
|
||||
double rectHeight = RenderOptions.CalendarSettings.HourHeight;
|
||||
|
||||
// Define stroke and fill colors
|
||||
var strokeColor = SeperatorColor.Color;
|
||||
float strokeThickness = 0.5f;
|
||||
|
||||
for (int day = 0; day < RenderOptions.TotalDayCount; day++)
|
||||
{
|
||||
var currentDay = RenderOptions.DateRange.StartDate.AddDays(day);
|
||||
|
||||
bool isWorkingDay = RenderOptions.CalendarSettings.WorkingDays.Contains(currentDay.DayOfWeek);
|
||||
|
||||
// Loop through each hour (rows)
|
||||
for (int hour = 0; hour < hours; hour++)
|
||||
{
|
||||
var renderTime = TimeSpan.FromHours(hour);
|
||||
|
||||
var representingDateTime = currentDay.AddHours(hour);
|
||||
|
||||
// Calculate the position and size of the rectangle
|
||||
double x = day * rectWidth;
|
||||
double y = hour * rectHeight;
|
||||
|
||||
var rectangle = new Rect(x, y, rectWidth, rectHeight);
|
||||
|
||||
// Draw the rectangle border.
|
||||
// This is the main rectangle.
|
||||
args.DrawingSession.DrawRectangle(rectangle, strokeColor, strokeThickness);
|
||||
|
||||
// Fill another rectangle with the working hour background color
|
||||
// This rectangle must be placed with -1 margin to prevent invisible borders of the main rectangle.
|
||||
if (isWorkingDay && renderTime >= RenderOptions.CalendarSettings.WorkingHourStart && renderTime <= RenderOptions.CalendarSettings.WorkingHourEnd)
|
||||
{
|
||||
var backgroundRectangle = new Rect(x + 1, y + 1, rectWidth - 1, rectHeight - 1);
|
||||
|
||||
args.DrawingSession.DrawRectangle(backgroundRectangle, strokeColor, strokeThickness);
|
||||
args.DrawingSession.FillRectangle(backgroundRectangle, WorkingHourCellBackgroundColor.Color);
|
||||
}
|
||||
|
||||
// Draw a line in the center of the rectangle for representing half hours.
|
||||
double lineY = y + rectHeight / 2;
|
||||
|
||||
args.DrawingSession.DrawLine((float)x, (float)lineY, (float)(x + rectWidth), (float)lineY, HalfHourSeperatorColor.Color, strokeThickness, new CanvasStrokeStyle()
|
||||
{
|
||||
DashStyle = CanvasDashStyle.Dot
|
||||
});
|
||||
}
|
||||
|
||||
// Draw selected item background color for the date if possible.
|
||||
if (SelectedDateTime != null)
|
||||
{
|
||||
var selectedDateTime = SelectedDateTime.Value;
|
||||
if (selectedDateTime.Date == currentDay.Date)
|
||||
{
|
||||
var selectionRectHeight = rectHeight / 2;
|
||||
var selectedY = selectedDateTime.Hour * rectHeight + (selectedDateTime.Minute / 60) * rectHeight;
|
||||
|
||||
// Second half of the hour is selected.
|
||||
if (selectedDateTime.TimeOfDay.Minutes == 30)
|
||||
{
|
||||
selectedY += rectHeight / 2;
|
||||
}
|
||||
|
||||
var selectedRectangle = new Rect(day * rectWidth, selectedY, rectWidth, selectionRectHeight);
|
||||
args.DrawingSession.FillRectangle(selectedRectangle, SelectedCellBackgroundBrush.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Canvas == null) return;
|
||||
|
||||
Canvas.Draw -= OnCanvasDraw;
|
||||
Canvas.PointerPressed -= OnCanvasPointerPressed;
|
||||
Canvas.RemoveFromVisualTree();
|
||||
|
||||
Canvas = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,98 +10,62 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Helpers;
|
||||
|
||||
namespace Wino.Calendar.Helpers
|
||||
namespace Wino.Calendar.Helpers;
|
||||
|
||||
public static class CalendarXamlHelpers
|
||||
{
|
||||
public static class CalendarXamlHelpers
|
||||
public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection)
|
||||
=> (CalendarItemViewModel)collection.AllDayEvents.FirstOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Returns full date + duration info in Event Details page details title.
|
||||
/// </summary>
|
||||
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||
{
|
||||
public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection)
|
||||
=> (CalendarItemViewModel)collection.AllDayEvents.FirstOrDefault();
|
||||
// TODO: This is not correct.
|
||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Returns full date + duration info in Event Details page details title.
|
||||
/// </summary>
|
||||
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||
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.CalendarItem.ItemType == CalendarItemType.MultiDay || calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay)
|
||||
{
|
||||
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)}";
|
||||
}
|
||||
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
||||
}
|
||||
|
||||
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
||||
else
|
||||
{
|
||||
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);
|
||||
return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
// TODO
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||
{
|
||||
// TODO
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
||||
CalendarItemViewModel calendarItemViewModel,
|
||||
CalendarDisplayType calendarDisplayType)
|
||||
{
|
||||
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
||||
|
||||
bool isAllDayOrMultiDay = calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.MultiDay ||
|
||||
calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.AllDay ||
|
||||
calendarItemViewModel.CalendarItem.ItemType == CalendarItemType.MultiDayAllDay;
|
||||
|
||||
// All and/or multi day events always go to the top of the screen.
|
||||
if (isAllDayOrMultiDay) return PopupPlacementMode.Bottom;
|
||||
|
||||
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,15 @@ using Windows.UI.Xaml.Navigation;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
|
||||
|
||||
namespace Wino.Calendar
|
||||
namespace Wino.Calendar;
|
||||
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
public MainPage()
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
namespace Wino.Calendar.Models
|
||||
namespace Wino.Calendar.Models;
|
||||
|
||||
public struct CalendarItemMeasurement
|
||||
{
|
||||
public struct CalendarItemMeasurement
|
||||
// Where to start?
|
||||
public double Left { get; set; }
|
||||
|
||||
// Extend until where?
|
||||
public double Right { get; set; }
|
||||
|
||||
public CalendarItemMeasurement(double left, double right)
|
||||
{
|
||||
// Where to start?
|
||||
public double Left { get; set; }
|
||||
|
||||
// Extend until where?
|
||||
public double Right { get; set; }
|
||||
|
||||
public CalendarItemMeasurement(double left, double right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
IgnorableNamespaces="uap mp">
|
||||
|
||||
<Identity
|
||||
Name="58272BurakKSE.WinoCalendar"
|
||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||
Version="1.0.15.0" />
|
||||
|
||||
<!-- Publisher Cache Folders -->
|
||||
<Extensions>
|
||||
<Extension Category="windows.publisherCacheFolders">
|
||||
@@ -16,13 +21,8 @@
|
||||
</PublisherCacheFolders>
|
||||
</Extension>
|
||||
</Extensions>
|
||||
|
||||
<Identity
|
||||
Name="58272BurakKSE.WinoCalendar"
|
||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||
Version="1.0.15.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="f047b7dd-96ec-4d54-a862-9321e271e449" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
<mp:PhoneIdentity PhoneProductId="f047b7dd-96ec-4d54-a862-9321e271e449" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Wino Calendar</DisplayName>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Wino.Calendar")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Wino.Calendar")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2023")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: ComVisible(false)]
|
||||
@@ -1,44 +0,0 @@
|
||||
<!--
|
||||
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
|
||||
developers. However, you can modify these parameters to modify the behavior of the .NET Native
|
||||
optimizer.
|
||||
|
||||
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
|
||||
|
||||
To fully enable reflection for App1.MyClass and all of its public/private members
|
||||
<Type Name="App1.MyClass" Dynamic="Required All"/>
|
||||
|
||||
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
|
||||
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
|
||||
|
||||
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
|
||||
<Namespace Name="DataClasses.ViewModels" Serialize="All" />
|
||||
-->
|
||||
|
||||
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
|
||||
<Application>
|
||||
<!--
|
||||
An Assembly element with Name="*Application*" applies to all assemblies in
|
||||
the application package. The asterisks are not wildcards.
|
||||
-->
|
||||
<Assembly Name="*Application*" Dynamic="Required All" />
|
||||
|
||||
<!-- Reduce memory footprint when building with Microsoft.Graph -->
|
||||
|
||||
<Assembly Name="Microsoft.Graph" Serialize="Excluded" />
|
||||
|
||||
<Assembly Name="Microsoft.Kiota.Abstractions" Dynamic="Public" />
|
||||
<Assembly Name="Microsoft.Kiota.Authentication.Azure" Dynamic="Public" />
|
||||
<Assembly Name="Microsoft.Kiota.Http.HttpClientLibrary" Dynamic="Public" />
|
||||
<Assembly Name="Microsoft.Kiota.Serialization.Form" Dynamic="Public" />
|
||||
<Assembly Name="Microsoft.Kiota.Serialization.Json" Dynamic="Public" />
|
||||
<Assembly Name="Microsoft.Kiota.Serialization.Multipart" Dynamic="Public" />
|
||||
|
||||
<!-- Add your application specific runtime directives here. -->
|
||||
<Type Name="Windows.Foundation.TypedEventHandler{Microsoft.UI.Xaml.Controls.NavigationView,Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs}" MarshalObject="Public" />
|
||||
|
||||
<Type Name="Microsoft.UI.Xaml.Controls.NavigationView">
|
||||
<Event Name="ItemInvoked" Dynamic="Required"/>
|
||||
</Type>
|
||||
</Application>
|
||||
</Directives>
|
||||
7
Wino.Calendar/Properties/launchSettings.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Wino.Calendar": {
|
||||
"commandName": "MsixPackage"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,21 @@
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
|
||||
namespace Wino.Calendar.Selectors
|
||||
namespace Wino.Calendar.Selectors;
|
||||
|
||||
public partial class CustomAreaCalendarItemSelector : DataTemplateSelector
|
||||
{
|
||||
public class CustomAreaCalendarItemSelector : DataTemplateSelector
|
||||
public DataTemplate AllDayTemplate { get; set; }
|
||||
public DataTemplate MultiDayTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
public DataTemplate AllDayTemplate { get; set; }
|
||||
public DataTemplate MultiDayTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
if (item is CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
if (item is CalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
return calendarItemViewModel.IsMultiDayEvent ? MultiDayTemplate : AllDayTemplate;
|
||||
}
|
||||
|
||||
return base.SelectTemplateCore(item, container);
|
||||
return calendarItemViewModel.CalendarItem.ItemType == Core.Domain.Enums.CalendarItemType.MultiDay ||
|
||||
calendarItemViewModel.CalendarItem.ItemType == Core.Domain.Enums.CalendarItemType.MultiDayAllDay ? MultiDayTemplate : AllDayTemplate;
|
||||
}
|
||||
|
||||
return base.SelectTemplateCore(item, container);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,32 @@
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Calendar.Selectors
|
||||
namespace Wino.Calendar.Selectors;
|
||||
|
||||
public partial class WinoCalendarItemTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public class WinoCalendarItemTemplateSelector : DataTemplateSelector
|
||||
public CalendarDisplayType DisplayType { get; set; }
|
||||
|
||||
public DataTemplate DayWeekWorkWeekTemplate { get; set; }
|
||||
public DataTemplate MonthlyTemplate { get; set; }
|
||||
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
public CalendarDisplayType DisplayType { get; set; }
|
||||
|
||||
public DataTemplate DayWeekWorkWeekTemplate { get; set; }
|
||||
public DataTemplate MonthlyTemplate { get; set; }
|
||||
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
switch (DisplayType)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,108 +7,107 @@ using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Calendar.Services
|
||||
namespace Wino.Calendar.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulated state manager for collectively managing the state of account calendars.
|
||||
/// Callers must react to the events to update their state only from this service.
|
||||
/// </summary>
|
||||
public partial class AccountCalendarStateService : ObservableObject, IAccountCalendarStateService
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
public event EventHandler<GroupedAccountCalendarViewModel> CollectiveAccountGroupSelectionStateChanged;
|
||||
public event EventHandler<AccountCalendarViewModel> AccountCalendarSelectionStateChanged;
|
||||
|
||||
[ObservableProperty]
|
||||
private ReadOnlyObservableCollection<GroupedAccountCalendarViewModel> groupedAccountCalendars;
|
||||
|
||||
private ObservableCollection<GroupedAccountCalendarViewModel> _internalGroupedAccountCalendars = new ObservableCollection<GroupedAccountCalendarViewModel>();
|
||||
|
||||
public IEnumerable<AccountCalendarViewModel> ActiveCalendars
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
return GroupedAccountCalendars
|
||||
.SelectMany(a => a.AccountCalendars)
|
||||
.Where(b => b.IsChecked);
|
||||
}
|
||||
return GroupedAccountCalendars
|
||||
.SelectMany(a => a.AccountCalendars)
|
||||
.Where(b => b.IsChecked);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable
|
||||
public IEnumerable<IGrouping<MailAccount, AccountCalendarViewModel>> GroupedAccountCalendarsEnumerable
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
return GroupedAccountCalendars
|
||||
.Select(a => a.AccountCalendars)
|
||||
.SelectMany(b => b)
|
||||
.GroupBy(c => c.Account);
|
||||
}
|
||||
return GroupedAccountCalendars
|
||||
.Select(a => a.AccountCalendars)
|
||||
.SelectMany(b => b)
|
||||
.GroupBy(c => c.Account);
|
||||
}
|
||||
}
|
||||
|
||||
public AccountCalendarStateService()
|
||||
public AccountCalendarStateService()
|
||||
{
|
||||
GroupedAccountCalendars = new ReadOnlyObservableCollection<GroupedAccountCalendarViewModel>(_internalGroupedAccountCalendars);
|
||||
}
|
||||
|
||||
private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e)
|
||||
=> CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel);
|
||||
|
||||
private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e)
|
||||
=> AccountCalendarSelectionStateChanged?.Invoke(this, e);
|
||||
|
||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||
{
|
||||
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
||||
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
||||
|
||||
_internalGroupedAccountCalendars.Add(groupedAccountCalendar);
|
||||
}
|
||||
|
||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||
{
|
||||
groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged;
|
||||
groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged;
|
||||
|
||||
_internalGroupedAccountCalendars.Remove(groupedAccountCalendar);
|
||||
}
|
||||
|
||||
public void ClearGroupedAccountCalendar()
|
||||
{
|
||||
foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars)
|
||||
{
|
||||
GroupedAccountCalendars = new ReadOnlyObservableCollection<GroupedAccountCalendarViewModel>(_internalGroupedAccountCalendars);
|
||||
RemoveGroupedAccountCalendar(groupedAccountCalendar);
|
||||
}
|
||||
}
|
||||
|
||||
private void SingleGroupCalendarCollectiveStateChanged(object sender, EventArgs e)
|
||||
=> CollectiveAccountGroupSelectionStateChanged?.Invoke(this, sender as GroupedAccountCalendarViewModel);
|
||||
public void AddAccountCalendar(AccountCalendarViewModel accountCalendar)
|
||||
{
|
||||
// Find the group that this calendar belongs to.
|
||||
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
||||
|
||||
private void SingleCalendarSelectionStateChanged(object sender, AccountCalendarViewModel e)
|
||||
=> AccountCalendarSelectionStateChanged?.Invoke(this, e);
|
||||
|
||||
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||
if (group == null)
|
||||
{
|
||||
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
||||
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
||||
|
||||
_internalGroupedAccountCalendars.Add(groupedAccountCalendar);
|
||||
// If the group doesn't exist, create it.
|
||||
group = new GroupedAccountCalendarViewModel(accountCalendar.Account, new[] { accountCalendar });
|
||||
AddGroupedAccountCalendar(group);
|
||||
}
|
||||
|
||||
public void RemoveGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar)
|
||||
else
|
||||
{
|
||||
groupedAccountCalendar.CalendarSelectionStateChanged -= SingleCalendarSelectionStateChanged;
|
||||
groupedAccountCalendar.CollectiveSelectionStateChanged -= SingleGroupCalendarCollectiveStateChanged;
|
||||
|
||||
_internalGroupedAccountCalendars.Remove(groupedAccountCalendar);
|
||||
group.AccountCalendars.Add(accountCalendar);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearGroupedAccountCalendar()
|
||||
public void RemoveAccountCalendar(AccountCalendarViewModel accountCalendar)
|
||||
{
|
||||
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
||||
|
||||
// We don't expect but just in case.
|
||||
if (group == null) return;
|
||||
|
||||
group.AccountCalendars.Remove(accountCalendar);
|
||||
|
||||
if (group.AccountCalendars.Count == 0)
|
||||
{
|
||||
foreach (var groupedAccountCalendar in _internalGroupedAccountCalendars)
|
||||
{
|
||||
RemoveGroupedAccountCalendar(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);
|
||||
}
|
||||
RemoveGroupedAccountCalendar(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Calendar.Services
|
||||
namespace Wino.Calendar.Services;
|
||||
|
||||
public class CalendarAuthenticatorConfig : IAuthenticatorConfig
|
||||
{
|
||||
public class CalendarAuthenticatorConfig : IAuthenticatorConfig
|
||||
public string OutlookAuthenticatorClientId => "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||
|
||||
public string[] OutlookScope => new string[]
|
||||
{
|
||||
public string OutlookAuthenticatorClientId => "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||
"Calendars.Read",
|
||||
"Calendars.Read.Shared",
|
||||
"offline_access",
|
||||
"Calendars.ReadBasic",
|
||||
"Calendars.ReadWrite",
|
||||
"Calendars.ReadWrite.Shared",
|
||||
"User.Read"
|
||||
};
|
||||
|
||||
public string[] OutlookScope => new string[]
|
||||
{
|
||||
"Calendars.Read",
|
||||
"Calendars.Read.Shared",
|
||||
"offline_access",
|
||||
"Calendars.ReadBasic",
|
||||
"Calendars.ReadWrite",
|
||||
"Calendars.ReadWrite.Shared",
|
||||
"User.Read"
|
||||
};
|
||||
public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
||||
|
||||
public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
||||
public string[] GmailScope => new string[]
|
||||
{
|
||||
"https://www.googleapis.com/auth/calendar",
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.settings.readonly",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/userinfo.email"
|
||||
};
|
||||
|
||||
public string[] GmailScope => new string[]
|
||||
{
|
||||
"https://www.googleapis.com/auth/calendar",
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.settings.readonly",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/userinfo.email"
|
||||
};
|
||||
|
||||
public string GmailTokenStoreIdentifier => "WinoCalendarGmailTokenStore";
|
||||
}
|
||||
public string GmailTokenStoreIdentifier => "WinoCalendarGmailTokenStore";
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.UWP.Services;
|
||||
|
||||
namespace Wino.Calendar.Services
|
||||
namespace Wino.Calendar.Services;
|
||||
|
||||
public class DialogService : DialogServiceBase, ICalendarDialogService
|
||||
{
|
||||
public class DialogService : DialogServiceBase, ICalendarDialogService
|
||||
public DialogService(IThemeService themeService,
|
||||
IConfigurationService configurationService,
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||
{
|
||||
public DialogService(IThemeService themeService,
|
||||
IConfigurationService configurationService,
|
||||
IApplicationResourceManager<ResourceDictionary> applicationResourceManager) : base(themeService, configurationService, applicationResourceManager)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,54 +10,53 @@ using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.UWP.Services;
|
||||
using Wino.Views;
|
||||
|
||||
namespace Wino.Calendar.Services
|
||||
namespace Wino.Calendar.Services;
|
||||
|
||||
public class NavigationService : NavigationServiceBase, INavigationService
|
||||
{
|
||||
public class NavigationService : NavigationServiceBase, INavigationService
|
||||
public Type GetPageType(WinoPage winoPage)
|
||||
{
|
||||
public Type GetPageType(WinoPage winoPage)
|
||||
return winoPage switch
|
||||
{
|
||||
return winoPage switch
|
||||
{
|
||||
WinoPage.CalendarPage => typeof(CalendarPage),
|
||||
WinoPage.SettingsPage => typeof(SettingsPage),
|
||||
WinoPage.CalendarSettingsPage => typeof(CalendarSettingsPage),
|
||||
WinoPage.AccountManagementPage => typeof(AccountManagementPage),
|
||||
WinoPage.ManageAccountsPage => typeof(ManageAccountsPage),
|
||||
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
|
||||
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
|
||||
WinoPage.EventDetailsPage => typeof(EventDetailsPage),
|
||||
_ => throw new Exception("Page is not implemented yet."),
|
||||
};
|
||||
}
|
||||
WinoPage.CalendarPage => typeof(CalendarPage),
|
||||
WinoPage.SettingsPage => typeof(SettingsPage),
|
||||
WinoPage.CalendarSettingsPage => typeof(CalendarSettingsPage),
|
||||
WinoPage.AccountManagementPage => typeof(AccountManagementPage),
|
||||
WinoPage.ManageAccountsPage => typeof(ManageAccountsPage),
|
||||
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
|
||||
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
|
||||
WinoPage.EventDetailsPage => typeof(EventDetailsPage),
|
||||
_ => throw new Exception("Page is not implemented yet."),
|
||||
};
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
public void GoBack()
|
||||
{
|
||||
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
||||
{
|
||||
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
||||
{
|
||||
var shellFrame = shellPage.GetShellFrame();
|
||||
var shellFrame = shellPage.GetShellFrame();
|
||||
|
||||
if (shellFrame.CanGoBack)
|
||||
{
|
||||
shellFrame.GoBack();
|
||||
}
|
||||
if (shellFrame.CanGoBack)
|
||||
{
|
||||
shellFrame.GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Navigate(WinoPage page, object parameter = null, NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame, NavigationTransitionType transition = NavigationTransitionType.None)
|
||||
{
|
||||
// All navigations are performed on shell frame for calendar.
|
||||
|
||||
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
||||
{
|
||||
var shellFrame = shellPage.GetShellFrame();
|
||||
|
||||
var pageType = GetPageType(page);
|
||||
|
||||
shellFrame.Navigate(pageType, parameter);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Navigate(WinoPage page, object parameter = null, NavigationReferenceFrame frame = NavigationReferenceFrame.ShellFrame, NavigationTransitionType transition = NavigationTransitionType.None)
|
||||
{
|
||||
// All navigations are performed on shell frame for calendar.
|
||||
|
||||
if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage)
|
||||
{
|
||||
var shellFrame = shellPage.GetShellFrame();
|
||||
|
||||
var pageType = GetPageType(page);
|
||||
|
||||
shellFrame.Navigate(pageType, parameter);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,33 +4,32 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
|
||||
namespace Wino.Calendar.Services
|
||||
namespace Wino.Calendar.Services;
|
||||
|
||||
public class ProviderService : IProviderService
|
||||
{
|
||||
public class ProviderService : IProviderService
|
||||
public IProviderDetail GetProviderDetail(MailProviderType type)
|
||||
{
|
||||
public IProviderDetail GetProviderDetail(MailProviderType type)
|
||||
{
|
||||
var details = GetAvailableProviders();
|
||||
var details = GetAvailableProviders();
|
||||
|
||||
return details.FirstOrDefault(a => a.Type == type);
|
||||
return details.FirstOrDefault(a => a.Type == type);
|
||||
}
|
||||
|
||||
public List<IProviderDetail> GetAvailableProviders()
|
||||
{
|
||||
var providerList = new List<IProviderDetail>();
|
||||
|
||||
var providers = new MailProviderType[]
|
||||
{
|
||||
MailProviderType.Outlook,
|
||||
MailProviderType.Gmail
|
||||
};
|
||||
|
||||
foreach (var type in providers)
|
||||
{
|
||||
providerList.Add(new ProviderDetail(type, SpecialImapProvider.None));
|
||||
}
|
||||
|
||||
public List<IProviderDetail> GetAvailableProviders()
|
||||
{
|
||||
var providerList = new List<IProviderDetail>();
|
||||
|
||||
var providers = new MailProviderType[]
|
||||
{
|
||||
MailProviderType.Outlook,
|
||||
MailProviderType.Gmail
|
||||
};
|
||||
|
||||
foreach (var type in providers)
|
||||
{
|
||||
providerList.Add(new ProviderDetail(type, SpecialImapProvider.None));
|
||||
}
|
||||
|
||||
return providerList;
|
||||
}
|
||||
return providerList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace Wino.Calendar.Styles
|
||||
namespace Wino.Calendar.Styles;
|
||||
|
||||
public sealed partial class WinoCalendarResources : ResourceDictionary
|
||||
{
|
||||
public sealed partial class WinoCalendarResources : ResourceDictionary
|
||||
public WinoCalendarResources()
|
||||
{
|
||||
public WinoCalendarResources()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Core.UWP;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract
|
||||
{
|
||||
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel> { }
|
||||
}
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public abstract class AccountDetailsPageAbstract : BasePage<AccountDetailsPageViewModel> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Core.UWP;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract
|
||||
{
|
||||
public class AccountManagementPageAbstract : BasePage<AccountManagementViewModel> { }
|
||||
}
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public partial class AccountManagementPageAbstract : BasePage<AccountManagementViewModel> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Core.UWP;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract
|
||||
{
|
||||
public abstract class AppShellAbstract : BasePage<AppShellViewModel> { }
|
||||
}
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public abstract class AppShellAbstract : BasePage<AppShellViewModel> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Core.UWP;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract
|
||||
{
|
||||
public abstract class CalendarPageAbstract : BasePage<CalendarPageViewModel> { }
|
||||
}
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public abstract class CalendarPageAbstract : BasePage<CalendarPageViewModel> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Core.UWP;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract
|
||||
{
|
||||
public abstract class CalendarSettingsPageAbstract : BasePage<CalendarSettingsPageViewModel> { }
|
||||
}
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public abstract class CalendarSettingsPageAbstract : BasePage<CalendarSettingsPageViewModel> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Wino.Calendar.ViewModels;
|
||||
using Wino.Core.UWP;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract
|
||||
{
|
||||
public abstract class EventDetailsPageAbstract : BasePage<EventDetailsPageViewModel> { }
|
||||
}
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public abstract class EventDetailsPageAbstract : BasePage<EventDetailsPageViewModel> { }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Core.ViewModels;
|
||||
|
||||
namespace Wino.Calendar.Views.Abstract
|
||||
{
|
||||
public class PersonalizationPageAbstract : BasePage<PersonalizationPageViewModel> { }
|
||||
}
|
||||
namespace Wino.Calendar.Views.Abstract;
|
||||
|
||||
public partial class PersonalizationPageAbstract : BasePage<PersonalizationPageViewModel> { }
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Calendar.Views.Abstract;
|
||||
|
||||
namespace Wino.Calendar.Views.Account
|
||||
namespace Wino.Calendar.Views.Account;
|
||||
|
||||
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
|
||||
{
|
||||
public sealed partial class AccountManagementPage : AccountManagementPageAbstract
|
||||
public AccountManagementPage()
|
||||
{
|
||||
public AccountManagementPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="Transparent"
|
||||
IsTabStop="False"
|
||||
PlaceholderText="Search" />
|
||||
|
||||
<StackPanel
|
||||
|
||||
@@ -5,50 +5,49 @@ using Wino.Calendar.Views.Abstract;
|
||||
using Wino.Core.UWP;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Views
|
||||
namespace Wino.Calendar.Views;
|
||||
|
||||
public sealed partial class AppShell : AppShellAbstract,
|
||||
IRecipient<CalendarDisplayTypeChangedMessage>
|
||||
{
|
||||
public sealed partial class AppShell : AppShellAbstract,
|
||||
IRecipient<CalendarDisplayTypeChangedMessage>
|
||||
private const string STATE_HorizontalCalendar = "HorizontalCalendar";
|
||||
private const string STATE_VerticalCalendar = "VerticalCalendar";
|
||||
|
||||
public Frame GetShellFrame() => ShellFrame;
|
||||
|
||||
public AppShell()
|
||||
{
|
||||
private const string STATE_HorizontalCalendar = "HorizontalCalendar";
|
||||
private const string STATE_VerticalCalendar = "VerticalCalendar";
|
||||
InitializeComponent();
|
||||
|
||||
public Frame GetShellFrame() => ShellFrame;
|
||||
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Window.Current.SetTitleBar(DragArea);
|
||||
ManageCalendarDisplayType();
|
||||
}
|
||||
|
||||
private void ManageCalendarDisplayType()
|
||||
{
|
||||
// Go to different states based on the display type.
|
||||
if (ViewModel.IsVerticalCalendar)
|
||||
{
|
||||
VisualStateManager.GoToState(this, STATE_VerticalCalendar, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
|
||||
|
||||
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
|
||||
|
||||
public void Receive(CalendarDisplayTypeChangedMessage message)
|
||||
{
|
||||
ManageCalendarDisplayType();
|
||||
}
|
||||
|
||||
private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||
=> RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
|
||||
|
||||
private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args)
|
||||
=> ViewModel.NavigationService.GoBack();
|
||||
Window.Current.SetTitleBar(DragArea);
|
||||
ManageCalendarDisplayType();
|
||||
}
|
||||
|
||||
private void ManageCalendarDisplayType()
|
||||
{
|
||||
// Go to different states based on the display type.
|
||||
if (ViewModel.IsVerticalCalendar)
|
||||
{
|
||||
VisualStateManager.GoToState(this, STATE_VerticalCalendar, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, STATE_HorizontalCalendar, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
|
||||
|
||||
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
|
||||
|
||||
public void Receive(CalendarDisplayTypeChangedMessage message)
|
||||
{
|
||||
ManageCalendarDisplayType();
|
||||
}
|
||||
|
||||
private void ShellFrameContentNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||
=> RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
|
||||
|
||||
private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args)
|
||||
=> ViewModel.NavigationService.GoBack();
|
||||
}
|
||||
|
||||
@@ -9,153 +9,152 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
namespace Wino.Calendar.Views
|
||||
namespace Wino.Calendar.Views;
|
||||
|
||||
public sealed partial class CalendarPage : CalendarPageAbstract,
|
||||
IRecipient<ScrollToDateMessage>,
|
||||
IRecipient<ScrollToHourMessage>,
|
||||
IRecipient<GoNextDateRequestedMessage>,
|
||||
IRecipient<GoPreviousDateRequestedMessage>
|
||||
{
|
||||
public sealed partial class CalendarPage : CalendarPageAbstract,
|
||||
IRecipient<ScrollToDateMessage>,
|
||||
IRecipient<ScrollToHourMessage>,
|
||||
IRecipient<GoNextDateRequestedMessage>,
|
||||
IRecipient<GoPreviousDateRequestedMessage>
|
||||
private const int PopupDialogOffset = 12;
|
||||
|
||||
public CalendarPage()
|
||||
{
|
||||
private const int PopupDialogOffset = 12;
|
||||
InitializeComponent();
|
||||
NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
|
||||
public CalendarPage()
|
||||
ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged;
|
||||
}
|
||||
|
||||
private void CalendarItemDetailContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (ViewModel.DisplayDetailsCalendarItemViewModel != null)
|
||||
{
|
||||
InitializeComponent();
|
||||
NavigationCacheMode = NavigationCacheMode.Enabled;
|
||||
var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel);
|
||||
|
||||
ViewModel.DetailsShowCalendarItemChanged += CalendarItemDetailContextChanged;
|
||||
}
|
||||
|
||||
private void CalendarItemDetailContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (ViewModel.DisplayDetailsCalendarItemViewModel != null)
|
||||
if (control != null)
|
||||
{
|
||||
var control = CalendarControl.GetCalendarItemControl(ViewModel.DisplayDetailsCalendarItemViewModel);
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
EventDetailsPopup.PlacementTarget = control;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
<PersonPicture
|
||||
Width="40"
|
||||
Height="40"
|
||||
DisplayName="{x:Bind Name}" />
|
||||
DisplayName="{x:Bind DisplayName}" />
|
||||
|
||||
<!-- TODO: Organizer -->
|
||||
<Grid Grid.Column="1">
|
||||
@@ -265,7 +265,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind DisplayName}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
FontSize="13"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
|
||||
using Wino.Calendar.Views.Abstract;
|
||||
|
||||
namespace Wino.Calendar.Views
|
||||
namespace Wino.Calendar.Views;
|
||||
|
||||
public sealed partial class EventDetailsPage : EventDetailsPageAbstract
|
||||
{
|
||||
public sealed partial class EventDetailsPage : EventDetailsPageAbstract
|
||||
public EventDetailsPage()
|
||||
{
|
||||
public EventDetailsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<PathIcon Data="F1 M 10 0.625 C 10 0.45573 10.061849 0.309246 10.185547 0.185547 C 10.309244 0.06185 10.455729 0 10.625 0 L 15.625 0 C 15.79427 0 15.940754 0.06185 16.064453 0.185547 C 16.18815 0.309246 16.25 0.45573 16.25 0.625 C 16.25 0.794271 16.18815 0.940756 16.064453 1.064453 C 15.940754 1.188152 15.79427 1.25 15.625 1.25 L 13.75 1.25 L 13.75 18.75 L 15.625 18.75 C 15.79427 18.75 15.940754 18.81185 16.064453 18.935547 C 16.18815 19.059244 16.25 19.205729 16.25 19.375 C 16.25 19.544271 16.18815 19.690756 16.064453 19.814453 C 15.940754 19.93815 15.79427 20 15.625 20 L 10.625 20 C 10.455729 20 10.309244 19.93815 10.185547 19.814453 C 10.061849 19.690756 10 19.544271 10 19.375 C 10 19.205729 10.061849 19.059244 10.185547 18.935547 C 10.309244 18.81185 10.455729 18.75 10.625 18.75 L 12.5 18.75 L 12.5 1.25 L 10.625 1.25 C 10.455729 1.25 10.309244 1.188152 10.185547 1.064453 C 10.061849 0.940756 10 0.794271 10 0.625 Z M 0 6.25 C 0 5.735678 0.097656 5.250651 0.292969 4.794922 C 0.488281 4.339193 0.756836 3.94043 1.098633 3.598633 C 1.44043 3.256836 1.837565 2.988281 2.290039 2.792969 C 2.742513 2.597656 3.229167 2.5 3.75 2.5 L 11.25 2.5 L 11.25 3.75 L 3.75 3.75 C 3.404948 3.75 3.081055 3.815105 2.77832 3.945312 C 2.475586 4.075521 2.210286 4.254558 1.982422 4.482422 C 1.754557 4.710287 1.575521 4.975587 1.445312 5.27832 C 1.315104 5.581056 1.25 5.904949 1.25 6.25 L 1.25 13.75 C 1.25 14.095053 1.315104 14.418945 1.445312 14.72168 C 1.575521 15.024414 1.754557 15.289714 1.982422 15.517578 C 2.210286 15.745443 2.475586 15.924479 2.77832 16.054688 C 3.081055 16.184896 3.404948 16.25 3.75 16.25 L 11.25 16.25 L 11.25 17.5 L 3.75 17.5 C 3.229167 17.5 2.742513 17.402344 2.290039 17.207031 C 1.837565 17.011719 1.44043 16.743164 1.098633 16.401367 C 0.756836 16.05957 0.488281 15.662436 0.292969 15.209961 C 0.097656 14.757487 0 14.270834 0 13.75 Z M 15 3.75 L 15 2.5 L 16.25 2.5 C 16.764322 2.5 17.249348 2.597656 17.705078 2.792969 C 18.160807 2.988281 18.55957 3.256836 18.901367 3.598633 C 19.243164 3.94043 19.511719 4.339193 19.707031 4.794922 C 19.902344 5.250651 20 5.735678 20 6.25 L 20 13.75 C 20 14.270834 19.902344 14.757487 19.707031 15.209961 C 19.511719 15.662436 19.243164 16.05957 18.901367 16.401367 C 18.55957 16.743164 18.160807 17.011719 17.705078 17.207031 C 17.249348 17.402344 16.764322 17.5 16.25 17.5 L 15 17.5 L 15 16.25 L 16.25 16.25 C 16.595051 16.25 16.918945 16.184896 17.22168 16.054688 C 17.524414 15.924479 17.789713 15.745443 18.017578 15.517578 C 18.245441 15.289714 18.424479 15.024414 18.554688 14.72168 C 18.684895 14.418945 18.75 14.095053 18.75 13.75 L 18.75 6.25 C 18.75 5.904949 18.684895 5.581056 18.554688 5.27832 C 18.424479 4.975587 18.245441 4.710287 18.017578 4.482422 C 17.789713 4.254558 17.524414 4.075521 17.22168 3.945312 C 16.918945 3.815105 16.595051 3.75 16.25 3.75 Z M 7.441406 5.361328 C 7.389323 5.250651 7.312825 5.162761 7.211914 5.097656 C 7.111002 5.032553 6.998697 5.000001 6.875 5 C 6.751302 5.000001 6.638997 5.032553 6.538086 5.097656 C 6.437174 5.162761 6.360677 5.250651 6.308594 5.361328 L 2.871094 12.861328 C 2.799479 13.017578 2.792969 13.177084 2.851562 13.339844 C 2.910156 13.502604 3.017578 13.619792 3.173828 13.691406 C 3.330078 13.763021 3.489583 13.769531 3.652344 13.710938 C 3.815104 13.652344 3.932292 13.544922 4.003906 13.388672 L 4.84375 11.5625 L 8.896484 11.5625 L 8.90625 11.5625 L 9.746094 13.388672 C 9.817708 13.544922 9.934896 13.652344 10.097656 13.710938 C 10.260416 13.769531 10.419922 13.763021 10.576172 13.691406 C 10.732422 13.619792 10.839844 13.502604 10.898438 13.339844 C 10.957031 13.177084 10.950521 13.017578 10.878906 12.861328 Z M 5.410156 10.3125 L 6.875 7.128906 L 8.339844 10.3125 Z " />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
<Button Command="{x:Bind ViewModel.RenameAccountCommand}" Content="{x:Bind domain:Translator.FolderOperation_Rename}" />
|
||||
<Button Command="{x:Bind ViewModel.EditAccountDetailsCommand}" Content="{x:Bind domain:Translator.FolderOperation_Rename}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- TODO -->
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Calendar.Views.Abstract;
|
||||
|
||||
namespace Wino.Calendar.Views.Settings
|
||||
namespace Wino.Calendar.Views.Settings;
|
||||
|
||||
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
|
||||
{
|
||||
public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
|
||||
public AccountDetailsPage()
|
||||
{
|
||||
public AccountDetailsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Wino.Calendar.Views.Abstract;
|
||||
|
||||
|
||||
namespace Wino.Calendar.Views.Settings
|
||||
namespace Wino.Calendar.Views.Settings;
|
||||
|
||||
public sealed partial class CalendarSettingsPage : CalendarSettingsPageAbstract
|
||||
{
|
||||
public sealed partial class CalendarSettingsPage : CalendarSettingsPageAbstract
|
||||
public CalendarSettingsPage()
|
||||
{
|
||||
public CalendarSettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Wino.Calendar.Views.Abstract;
|
||||
|
||||
namespace Wino.Calendar.Views.Settings
|
||||
namespace Wino.Calendar.Views.Settings;
|
||||
|
||||
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
|
||||
{
|
||||
public sealed partial class PersonalizationPage : PersonalizationPageAbstract
|
||||
public PersonalizationPage()
|
||||
{
|
||||
public PersonalizationPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,184 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
<!-- UWP WAM Authentication on Xbox needs this. -->
|
||||
<UseDotNetNativeSharedAssemblyFrameworkPackage>false</UseDotNetNativeSharedAssemblyFrameworkPackage>
|
||||
<PackageCertificateThumbprint>
|
||||
</PackageCertificateThumbprint>
|
||||
<PackageCertificateKeyFile>Wino.Mail_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
|
||||
<AppxSymbolPackageEnabled>False</AppxSymbolPackageEnabled>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<AppxBundlePlatforms>x64</AppxBundlePlatforms>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProjectGuid>{600F4979-DB7E-409D-B7DA-B60BE4C55C35}</ProjectGuid>
|
||||
<OutputType>AppContainerExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Wino.Calendar</RootNamespace>
|
||||
<AssemblyName>Wino.Calendar</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.22621.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\ARM64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
||||
<OutputPath>bin\ARM64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<NoWarn>;2008</NoWarn>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<!-- .NET Native Shit -->
|
||||
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
|
||||
<UseDotNetNativeSharedAssemblyFrameworkPackage>false</UseDotNetNativeSharedAssemblyFrameworkPackage>
|
||||
<Use64BitCompiler>true</Use64BitCompiler>
|
||||
<OutOfProcPDB>true</OutOfProcPDB>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Activation\DefaultActivationHandler.cs" />
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Args\TimelineCellSelectedArgs.cs" />
|
||||
<Compile Include="Args\TimelineCellUnselectedArgs.cs" />
|
||||
<Compile Include="Controls\CalendarItemCommandBarFlyout.cs" />
|
||||
<Compile Include="Controls\CalendarItemControl.xaml.cs">
|
||||
<DependentUpon>CalendarItemControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\CustomCalendarFlipView.cs" />
|
||||
<Compile Include="Controls\DayColumnControl.cs" />
|
||||
<Compile Include="Controls\DayHeaderControl.cs" />
|
||||
<Compile Include="Controls\WinoCalendarControl.cs" />
|
||||
<Compile Include="Controls\WinoCalendarFlipView.cs" />
|
||||
<Compile Include="Controls\WinoCalendarPanel.cs" />
|
||||
<Compile Include="Controls\WinoCalendarTypeSelectorControl.cs" />
|
||||
<Compile Include="Controls\WinoCalendarView.cs" />
|
||||
<Compile Include="Controls\WinoDayTimelineCanvas.cs" />
|
||||
<Compile Include="Helpers\CalendarXamlHelpers.cs" />
|
||||
<Compile Include="MainPage.xaml.cs">
|
||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Models\CalendarItemMeasurement.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Selectors\CustomAreaCalendarItemSelector.cs" />
|
||||
<Compile Include="Selectors\WinoCalendarItemTemplateSelector.cs" />
|
||||
<Compile Include="Services\AccountCalendarStateService.cs" />
|
||||
<Compile Include="Services\CalendarAuthenticatorConfig.cs" />
|
||||
<Compile Include="Services\DialogService.cs" />
|
||||
<Compile Include="Services\NavigationService.cs" />
|
||||
<Compile Include="Services\ProviderService.cs" />
|
||||
<Compile Include="Services\SettingsBuilderService.cs" />
|
||||
<Compile Include="Styles\WinoCalendarResources.xaml.cs" />
|
||||
<Compile Include="Views\Abstract\AccountDetailsPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\AccountManagementPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\AppShellAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\CalendarPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\CalendarSettingsPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\EventDetailsPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\PersonalizationPageAbstract.cs" />
|
||||
<Compile Include="Views\Account\AccountManagementPage.xaml.cs">
|
||||
<DependentUpon>AccountManagementPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\AppShell.xaml.cs">
|
||||
<DependentUpon>AppShell.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\CalendarPage.xaml.cs">
|
||||
<DependentUpon>CalendarPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\EventDetailsPage.xaml.cs">
|
||||
<DependentUpon>EventDetailsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Settings\AccountDetailsPage.xaml.cs">
|
||||
<DependentUpon>AccountDetailsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Settings\CalendarSettingsPage.xaml.cs">
|
||||
<DependentUpon>CalendarSettingsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Settings\PersonalizationPage.xaml.cs">
|
||||
<DependentUpon>PersonalizationPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
|
||||
<UseUwp>true</UseUwp>
|
||||
<Platforms>x86;x64;arm64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<!--<PublishAot>true</PublishAot>-->
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateAppInstallerFile>True</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="BundleArtifacts\**" />
|
||||
<EmbeddedResource Remove="BundleArtifacts\**" />
|
||||
<None Remove="BundleArtifacts\**" />
|
||||
<Page Remove="BundleArtifacts\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Remove="BundleArtifacts\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
<Content Include="Assets\LargeTile.scale-100.png" />
|
||||
@@ -226,7 +73,6 @@
|
||||
<Content Include="Assets\Wide310x150Logo.scale-125.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-150.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-400.png" />
|
||||
<Content Include="Properties\Default.rd.xml" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
@@ -235,120 +81,18 @@
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Page Include="Controls\CalendarItemControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="MainPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Styles\CalendarThemeResources.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Styles\DayHeaderControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Styles\WinoCalendarResources.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Styles\WinoCalendarTypeSelectorControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Styles\WinoCalendarView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Styles\WinoDayTimelineCanvas.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Views\Account\AccountManagementPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\AppShell.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\CalendarPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\EventDetailsPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\Settings\AccountDetailsPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\Settings\CalendarSettingsPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\Settings\PersonalizationPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<PackageReference Include="CommunityToolkit.Uwp.Controls.Primitives" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" />
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" />
|
||||
<PackageReference Include="Win2D.uwp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Uwp.Controls.Primitives">
|
||||
<Version>8.1.240916</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Identity.Client">
|
||||
<Version>4.66.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.14</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Win2D.uwp">
|
||||
<Version>1.28.1</Version>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\Wino.Authentication\Wino.Authentication.csproj" />
|
||||
<ProjectReference Include="..\Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core.UWP\Wino.Core.UWP.csproj" />
|
||||
<ProjectReference Include="..\Wino.Core.ViewModels\Wino.Core.ViewModels.csproj" />
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
|
||||
<ProjectReference Include="..\Wino.Services\Wino.Services.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Calendar.ViewModels\Wino.Calendar.ViewModels.csproj">
|
||||
<Project>{039affa8-c1cc-4e3b-8a31-6814d7557f74}</Project>
|
||||
<Name>Wino.Calendar.ViewModels</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj">
|
||||
<Project>{cf3312e5-5da0-4867-9945-49ea7598af1f}</Project>
|
||||
<Name>Wino.Core.Domain</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wino.Core.UWP\Wino.Core.UWP.csproj">
|
||||
<Project>{395f19ba-1e42-495c-9db5-1a6f537fccb8}</Project>
|
||||
<Name>Wino.Core.UWP</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wino.Core.ViewModels\Wino.Core.ViewModels.csproj">
|
||||
<Project>{53723ae8-7e7e-4d54-adab-0a6033255cc8}</Project>
|
||||
<Name>Wino.Core.ViewModels</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj">
|
||||
<Project>{0c307d7e-256f-448c-8265-5622a812fbcc}</Project>
|
||||
<Name>Wino.Messaging</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Wino.Services\Wino.Services.csproj">
|
||||
<Project>{bba49030-7277-48cf-b2fe-3d01cb6b6c81}</Project>
|
||||
<Name>Wino.Services</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -7,150 +7,149 @@ using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Collections
|
||||
namespace Wino.Core.Domain.Collections;
|
||||
|
||||
public class CalendarEventCollection
|
||||
{
|
||||
public class CalendarEventCollection
|
||||
public event EventHandler<ICalendarItem> CalendarItemAdded;
|
||||
public event EventHandler<ICalendarItem> CalendarItemRemoved;
|
||||
|
||||
public event EventHandler CalendarItemsCleared;
|
||||
|
||||
private ObservableRangeCollection<ICalendarItem> _internalRegularEvents = [];
|
||||
private ObservableRangeCollection<ICalendarItem> _internalAllDayEvents = [];
|
||||
|
||||
public ReadOnlyObservableCollection<ICalendarItem> RegularEvents { get; }
|
||||
public ReadOnlyObservableCollection<ICalendarItem> AllDayEvents { get; } // TODO: Rename this to include multi-day events.
|
||||
public ITimePeriod Period { get; }
|
||||
public CalendarSettings Settings { get; }
|
||||
|
||||
private readonly List<ICalendarItem> _allItems = new List<ICalendarItem>();
|
||||
|
||||
public CalendarEventCollection(ITimePeriod period, CalendarSettings settings)
|
||||
{
|
||||
public event EventHandler<ICalendarItem> CalendarItemAdded;
|
||||
public event EventHandler<ICalendarItem> CalendarItemRemoved;
|
||||
Period = period;
|
||||
Settings = settings;
|
||||
|
||||
public event EventHandler CalendarItemsCleared;
|
||||
RegularEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalRegularEvents);
|
||||
AllDayEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalAllDayEvents);
|
||||
}
|
||||
|
||||
private ObservableRangeCollection<ICalendarItem> _internalRegularEvents = [];
|
||||
private ObservableRangeCollection<ICalendarItem> _internalAllDayEvents = [];
|
||||
public bool HasCalendarEvent(AccountCalendar accountCalendar)
|
||||
=> _allItems.Any(x => x.AssignedCalendar.Id == accountCalendar.Id);
|
||||
|
||||
public ReadOnlyObservableCollection<ICalendarItem> RegularEvents { get; }
|
||||
public ReadOnlyObservableCollection<ICalendarItem> AllDayEvents { get; } // TODO: Rename this to include multi-day events.
|
||||
public ITimePeriod Period { get; }
|
||||
public CalendarSettings Settings { get; }
|
||||
public ICalendarItem GetCalendarItem(Guid calendarItemId)
|
||||
{
|
||||
return _allItems.FirstOrDefault(x => x.Id == calendarItemId);
|
||||
}
|
||||
|
||||
private readonly List<ICalendarItem> _allItems = new List<ICalendarItem>();
|
||||
|
||||
public CalendarEventCollection(ITimePeriod period, CalendarSettings settings)
|
||||
public void ClearSelectionStates()
|
||||
{
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
Period = period;
|
||||
Settings = settings;
|
||||
|
||||
RegularEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalRegularEvents);
|
||||
AllDayEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalAllDayEvents);
|
||||
}
|
||||
|
||||
public bool HasCalendarEvent(AccountCalendar accountCalendar)
|
||||
=> _allItems.Any(x => x.AssignedCalendar.Id == accountCalendar.Id);
|
||||
|
||||
public ICalendarItem GetCalendarItem(Guid calendarItemId)
|
||||
{
|
||||
return _allItems.FirstOrDefault(x => x.Id == calendarItemId);
|
||||
}
|
||||
|
||||
public void ClearSelectionStates()
|
||||
{
|
||||
foreach (var item in _allItems)
|
||||
if (item is ICalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
if (item is ICalendarItemViewModel calendarItemViewModel)
|
||||
{
|
||||
calendarItemViewModel.IsSelected = false;
|
||||
}
|
||||
calendarItemViewModel.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void FilterByCalendars(IEnumerable<Guid> visibleCalendarIds)
|
||||
{
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
var collections = GetProperCollectionsForCalendarItem(item);
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
if (!visibleCalendarIds.Contains(item.AssignedCalendar.Id) && collection.Contains(item))
|
||||
{
|
||||
RemoveCalendarItemInternal(collection, item, false);
|
||||
}
|
||||
else if (visibleCalendarIds.Contains(item.AssignedCalendar.Id) && !collection.Contains(item))
|
||||
{
|
||||
AddCalendarItemInternal(collection, item, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ObservableRangeCollection<ICalendarItem>> GetProperCollectionsForCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
// All-day events go to all days.
|
||||
// Multi-day events go to both.
|
||||
// Anything else goes to regular.
|
||||
|
||||
if (calendarItem.IsAllDayEvent)
|
||||
{
|
||||
return [_internalAllDayEvents];
|
||||
}
|
||||
else if (calendarItem.IsMultiDayEvent)
|
||||
{
|
||||
return [_internalRegularEvents, _internalAllDayEvents];
|
||||
}
|
||||
else
|
||||
{
|
||||
return [_internalRegularEvents];
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
var collections = GetProperCollectionsForCalendarItem(calendarItem);
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
AddCalendarItemInternal(collection, calendarItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
var collections = GetProperCollectionsForCalendarItem(calendarItem);
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
RemoveCalendarItemInternal(collection, calendarItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
collection.Add(calendarItem);
|
||||
|
||||
if (create)
|
||||
{
|
||||
_allItems.Add(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemAdded?.Invoke(this, calendarItem);
|
||||
}
|
||||
|
||||
private void RemoveCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool destroy = true)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
collection.Remove(calendarItem);
|
||||
|
||||
if (destroy)
|
||||
{
|
||||
_allItems.Remove(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemRemoved?.Invoke(this, calendarItem);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_internalAllDayEvents.Clear();
|
||||
_internalRegularEvents.Clear();
|
||||
_allItems.Clear();
|
||||
|
||||
CalendarItemsCleared?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void FilterByCalendars(IEnumerable<Guid> visibleCalendarIds)
|
||||
{
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
var collections = GetProperCollectionsForCalendarItem(item);
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
if (!visibleCalendarIds.Contains(item.AssignedCalendar.Id) && collection.Contains(item))
|
||||
{
|
||||
RemoveCalendarItemInternal(collection, item, false);
|
||||
}
|
||||
else if (visibleCalendarIds.Contains(item.AssignedCalendar.Id) && !collection.Contains(item))
|
||||
{
|
||||
AddCalendarItemInternal(collection, item, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ObservableRangeCollection<ICalendarItem>> GetProperCollectionsForCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
// All-day events go to all days.
|
||||
// Multi-day events go to both.
|
||||
// Anything else goes to regular.
|
||||
|
||||
if (calendarItem.ItemType == Enums.CalendarItemType.AllDay || calendarItem.ItemType == Enums.CalendarItemType.MultiDayAllDay || calendarItem.ItemType == Enums.CalendarItemType.RecurringAllDay)
|
||||
{
|
||||
return [_internalAllDayEvents];
|
||||
}
|
||||
else if (calendarItem.ItemType == Enums.CalendarItemType.MultiDay || calendarItem.ItemType == Enums.CalendarItemType.MultiDayAllDay)
|
||||
{
|
||||
return [_internalRegularEvents, _internalAllDayEvents];
|
||||
}
|
||||
else
|
||||
{
|
||||
return [_internalRegularEvents];
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
var collections = GetProperCollectionsForCalendarItem(calendarItem);
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
AddCalendarItemInternal(collection, calendarItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
var collections = GetProperCollectionsForCalendarItem(calendarItem);
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
RemoveCalendarItemInternal(collection, calendarItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
collection.Add(calendarItem);
|
||||
|
||||
if (create)
|
||||
{
|
||||
_allItems.Add(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemAdded?.Invoke(this, calendarItem);
|
||||
}
|
||||
|
||||
private void RemoveCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool destroy = true)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
collection.Remove(calendarItem);
|
||||
|
||||
if (destroy)
|
||||
{
|
||||
_allItems.Remove(calendarItem);
|
||||
}
|
||||
|
||||
CalendarItemRemoved?.Invoke(this, calendarItem);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_internalAllDayEvents.Clear();
|
||||
_internalRegularEvents.Clear();
|
||||
_allItems.Clear();
|
||||
|
||||
CalendarItemsCleared?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,41 +2,40 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Collections
|
||||
namespace Wino.Core.Domain.Collections;
|
||||
|
||||
public class DayRangeCollection : ObservableRangeCollection<DayRangeRenderModel>
|
||||
{
|
||||
public class DayRangeCollection : ObservableRangeCollection<DayRangeRenderModel>
|
||||
/// <summary>
|
||||
/// Gets the range of dates that are currently displayed in the collection.
|
||||
/// </summary>
|
||||
public DateRange DisplayRange
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the range of dates that are currently displayed in the collection.
|
||||
/// </summary>
|
||||
public DateRange DisplayRange
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Count == 0) return null;
|
||||
if (Count == 0) return null;
|
||||
|
||||
var minimumLoadedDate = this[0].CalendarRenderOptions.DateRange.StartDate;
|
||||
var maximumLoadedDate = this[Count - 1].CalendarRenderOptions.DateRange.EndDate;
|
||||
var minimumLoadedDate = this[0].CalendarRenderOptions.DateRange.StartDate;
|
||||
var maximumLoadedDate = this[Count - 1].CalendarRenderOptions.DateRange.EndDate;
|
||||
|
||||
return new DateRange(minimumLoadedDate, maximumLoadedDate);
|
||||
}
|
||||
return new DateRange(minimumLoadedDate, maximumLoadedDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCalendarItem(ICalendarItem calendarItem)
|
||||
public void RemoveCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
foreach (var dayRange in this)
|
||||
{
|
||||
foreach (var dayRange in this)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCalendarItem(ICalendarItem calendarItem)
|
||||
public void AddCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
foreach (var dayRange in this)
|
||||
{
|
||||
foreach (var dayRange in this)
|
||||
{
|
||||
var calendarDayModel = dayRange.CalendarDays.FirstOrDefault(x => x.Period.HasInside(calendarItem.Period.Start));
|
||||
calendarDayModel?.EventsCollection.AddCalendarItem(calendarItem);
|
||||
}
|
||||
var calendarDayModel = dayRange.CalendarDays.FirstOrDefault(x => x.Period.HasInside(calendarItem.Period.Start));
|
||||
calendarDayModel?.EventsCollection.AddCalendarItem(calendarItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,171 +4,170 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Wino.Core.Domain.Collections
|
||||
namespace Wino.Core.Domain.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ObservableRangeCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
|
||||
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ObservableRangeCollection<T> : ObservableCollection<T>
|
||||
public ObservableRangeCollection()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
|
||||
/// </summary>
|
||||
public ObservableRangeCollection()
|
||||
: base()
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
|
||||
/// </summary>
|
||||
/// <param name="collection">collection: The collection from which the elements are copied.</param>
|
||||
/// <exception cref="ArgumentNullException">The collection parameter cannot be null.</exception>
|
||||
public ObservableRangeCollection(IEnumerable<T> collection)
|
||||
: base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
|
||||
/// </summary>
|
||||
public void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
if (notificationMode != NotifyCollectionChangedAction.Add && notificationMode != NotifyCollectionChangedAction.Reset)
|
||||
throw new ArgumentException("Mode must be either Add or Reset for AddRange.", nameof(notificationMode));
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
CheckReentrancy();
|
||||
|
||||
var startIndex = Count;
|
||||
|
||||
var itemsAdded = AddArrangeCore(collection);
|
||||
|
||||
if (!itemsAdded)
|
||||
return;
|
||||
|
||||
if (notificationMode == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
|
||||
/// </summary>
|
||||
/// <param name="collection">collection: The collection from which the elements are copied.</param>
|
||||
/// <exception cref="ArgumentNullException">The collection parameter cannot be null.</exception>
|
||||
public ObservableRangeCollection(IEnumerable<T> collection)
|
||||
: base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
|
||||
/// </summary>
|
||||
public void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
if (notificationMode != NotifyCollectionChangedAction.Add && notificationMode != NotifyCollectionChangedAction.Reset)
|
||||
throw new ArgumentException("Mode must be either Add or Reset for AddRange.", nameof(notificationMode));
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
CheckReentrancy();
|
||||
|
||||
var startIndex = Count;
|
||||
|
||||
var itemsAdded = AddArrangeCore(collection);
|
||||
|
||||
if (!itemsAdded)
|
||||
return;
|
||||
|
||||
if (notificationMode == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var changedItems = collection is List<T> ? (List<T>)collection : new List<T>(collection);
|
||||
|
||||
RaiseChangeNotificationEvents(
|
||||
action: NotifyCollectionChangedAction.Add,
|
||||
changedItems: changedItems,
|
||||
startingIndex: startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). NOTE: with notificationMode = Remove, removed items starting index is not set because items are not guaranteed to be consecutive.
|
||||
/// </summary>
|
||||
public void RemoveRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
if (notificationMode != NotifyCollectionChangedAction.Remove && notificationMode != NotifyCollectionChangedAction.Reset)
|
||||
throw new ArgumentException("Mode must be either Remove or Reset for RemoveRange.", nameof(notificationMode));
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
CheckReentrancy();
|
||||
|
||||
if (notificationMode == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
var raiseEvents = false;
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Items.Remove(item);
|
||||
raiseEvents = true;
|
||||
}
|
||||
|
||||
if (raiseEvents)
|
||||
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var changedItems = new List<T>(collection);
|
||||
for (var i = 0; i < changedItems.Count; i++)
|
||||
{
|
||||
if (!Items.Remove(changedItems[i]))
|
||||
{
|
||||
changedItems.RemoveAt(i); //Can't use a foreach because changedItems is intended to be (carefully) modified
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedItems.Count == 0)
|
||||
return;
|
||||
|
||||
RaiseChangeNotificationEvents(
|
||||
action: NotifyCollectionChangedAction.Remove,
|
||||
changedItems: changedItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current collection and replaces it with the specified item.
|
||||
/// </summary>
|
||||
public void Replace(T item) => ReplaceRange(new T[] { item });
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current collection and replaces it with the specified collection.
|
||||
/// </summary>
|
||||
public void ReplaceRange(IEnumerable<T> collection)
|
||||
{
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
CheckReentrancy();
|
||||
|
||||
var previouslyEmpty = Items.Count == 0;
|
||||
|
||||
Items.Clear();
|
||||
|
||||
AddArrangeCore(collection);
|
||||
|
||||
var currentlyEmpty = Items.Count == 0;
|
||||
|
||||
if (previouslyEmpty && currentlyEmpty)
|
||||
return;
|
||||
|
||||
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public void InsertRange(IEnumerable<T> items)
|
||||
var changedItems = collection is List<T> ? (List<T>)collection : new List<T>(collection);
|
||||
|
||||
RaiseChangeNotificationEvents(
|
||||
action: NotifyCollectionChangedAction.Add,
|
||||
changedItems: changedItems,
|
||||
startingIndex: startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). NOTE: with notificationMode = Remove, removed items starting index is not set because items are not guaranteed to be consecutive.
|
||||
/// </summary>
|
||||
public void RemoveRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
if (notificationMode != NotifyCollectionChangedAction.Remove && notificationMode != NotifyCollectionChangedAction.Reset)
|
||||
throw new ArgumentException("Mode must be either Remove or Reset for RemoveRange.", nameof(notificationMode));
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
CheckReentrancy();
|
||||
|
||||
if (notificationMode == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
CheckReentrancy();
|
||||
|
||||
foreach (var item in items)
|
||||
Items.Insert(0, item);
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
private bool AddArrangeCore(IEnumerable<T> collection)
|
||||
{
|
||||
var itemAdded = false;
|
||||
var raiseEvents = false;
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Items.Add(item);
|
||||
itemAdded = true;
|
||||
Items.Remove(item);
|
||||
raiseEvents = true;
|
||||
}
|
||||
return itemAdded;
|
||||
|
||||
if (raiseEvents)
|
||||
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void RaiseChangeNotificationEvents(NotifyCollectionChangedAction action, List<T> changedItems = null, int startingIndex = -1)
|
||||
var changedItems = new List<T>(collection);
|
||||
for (var i = 0; i < changedItems.Count; i++)
|
||||
{
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
|
||||
if (changedItems is null)
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action));
|
||||
else
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, changedItems: changedItems, startingIndex: startingIndex));
|
||||
if (!Items.Remove(changedItems[i]))
|
||||
{
|
||||
changedItems.RemoveAt(i); //Can't use a foreach because changedItems is intended to be (carefully) modified
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedItems.Count == 0)
|
||||
return;
|
||||
|
||||
RaiseChangeNotificationEvents(
|
||||
action: NotifyCollectionChangedAction.Remove,
|
||||
changedItems: changedItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current collection and replaces it with the specified item.
|
||||
/// </summary>
|
||||
public void Replace(T item) => ReplaceRange(new T[] { item });
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current collection and replaces it with the specified collection.
|
||||
/// </summary>
|
||||
public void ReplaceRange(IEnumerable<T> collection)
|
||||
{
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException(nameof(collection));
|
||||
|
||||
CheckReentrancy();
|
||||
|
||||
var previouslyEmpty = Items.Count == 0;
|
||||
|
||||
Items.Clear();
|
||||
|
||||
AddArrangeCore(collection);
|
||||
|
||||
var currentlyEmpty = Items.Count == 0;
|
||||
|
||||
if (previouslyEmpty && currentlyEmpty)
|
||||
return;
|
||||
|
||||
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
|
||||
}
|
||||
|
||||
public void InsertRange(IEnumerable<T> items)
|
||||
{
|
||||
CheckReentrancy();
|
||||
|
||||
foreach (var item in items)
|
||||
Items.Insert(0, item);
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
private bool AddArrangeCore(IEnumerable<T> collection)
|
||||
{
|
||||
var itemAdded = false;
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Items.Add(item);
|
||||
itemAdded = true;
|
||||
}
|
||||
return itemAdded;
|
||||
}
|
||||
|
||||
private void RaiseChangeNotificationEvents(NotifyCollectionChangedAction action, List<T> changedItems = null, int startingIndex = -1)
|
||||
{
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
|
||||
if (changedItems is null)
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action));
|
||||
else
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, changedItems: changedItems, startingIndex: startingIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
namespace Wino.Core.Domain
|
||||
namespace Wino.Core.Domain;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// MIME header that exists in all the drafts created from Wino.
|
||||
/// </summary>
|
||||
public const string WinoLocalDraftHeader = "X-Wino-Draft-Id";
|
||||
public const string LocalDraftStartPrefix = "localDraft_";
|
||||
/// <summary>
|
||||
/// MIME header that exists in all the drafts created from Wino.
|
||||
/// </summary>
|
||||
public const string WinoLocalDraftHeader = "X-Wino-Draft-Id";
|
||||
public const string LocalDraftStartPrefix = "localDraft_";
|
||||
|
||||
public const string CalendarEventRecurrenceRuleSeperator = "___";
|
||||
public const string ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey);
|
||||
public const string ToastActionKey = nameof(ToastActionKey);
|
||||
|
||||
public const string ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey);
|
||||
public const string ToastActionKey = nameof(ToastActionKey);
|
||||
public const string ClientLogFile = "Client_.log";
|
||||
public const string ServerLogFile = "Server_.log";
|
||||
public const string LogArchiveFileName = "WinoLogs.zip";
|
||||
|
||||
public const string ClientLogFile = "Client_.log";
|
||||
public const string ServerLogFile = "Server_.log";
|
||||
public const string LogArchiveFileName = "WinoLogs.zip";
|
||||
|
||||
public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer);
|
||||
public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier);
|
||||
}
|
||||
public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer);
|
||||
public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier);
|
||||
}
|
||||
|
||||
@@ -2,24 +2,50 @@
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar
|
||||
{
|
||||
public class AccountCalendar : IAccountCalendar
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public string RemoteCalendarId { get; set; }
|
||||
public string SynchronizationDeltaToken { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool IsPrimary { get; set; }
|
||||
public bool IsExtended { get; set; } = true;
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
/// <summary>
|
||||
/// Unused for now.
|
||||
/// </summary>
|
||||
public string TextColorHex { get; set; }
|
||||
public string BackgroundColorHex { get; set; }
|
||||
public string TimeZone { get; set; }
|
||||
}
|
||||
public class AccountCalendar : IAccountCalendar
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[NotNull]
|
||||
public string RemoteCalendarId { get; set; } = string.Empty;
|
||||
|
||||
[NotNull]
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
[NotNull]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public string? Location { get; set; }
|
||||
|
||||
public string? TimeZone { get; set; }
|
||||
|
||||
public string? AccessRole { get; set; }
|
||||
|
||||
public bool IsPrimary { get; set; } = false;
|
||||
|
||||
public string? BackgroundColor { get; set; }
|
||||
|
||||
public string? ForegroundColor { get; set; }
|
||||
|
||||
public DateTime CreatedDate { get; set; }
|
||||
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
public DateTime? LastSyncTime { get; set; }
|
||||
|
||||
public string? SynchronizationDeltaToken { get; set; }
|
||||
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
public bool IsExtended { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Unused for now.
|
||||
/// </summary>
|
||||
public string TextColorHex { get; set; }
|
||||
public string BackgroundColorHex { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,19 +2,35 @@
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
// TODO: Connect to Contact store with Wino People.
|
||||
public class CalendarEventAttendee
|
||||
{
|
||||
// TODO: Connect to Contact store with Wino People.
|
||||
public class CalendarEventAttendee
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
public Guid CalendarItemId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public AttendeeStatus AttendenceStatus { get; set; }
|
||||
public bool IsOrganizer { get; set; }
|
||||
public bool IsOptionalAttendee { get; set; }
|
||||
public string Comment { get; set; }
|
||||
}
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[NotNull]
|
||||
public Guid EventId { get; set; }
|
||||
|
||||
[NotNull]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
public AttendeeResponseStatus ResponseStatus { get; set; } = AttendeeResponseStatus.NeedsAction;
|
||||
|
||||
public bool IsOptional { get; set; } = false;
|
||||
|
||||
public bool IsOrganizer { get; set; } = false;
|
||||
|
||||
public bool IsSelf { get; set; } = false;
|
||||
|
||||
public string? Comment { get; set; }
|
||||
|
||||
public int? AdditionalGuests { get; set; }
|
||||
|
||||
public DateTime CreatedDate { get; set; }
|
||||
|
||||
public DateTime LastModified { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,178 +3,90 @@ using System.Diagnostics;
|
||||
using Itenso.TimePeriod;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Helpers;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
[DebuggerDisplay("{Title} ({StartDate} - {EndDate})")]
|
||||
public class CalendarItem : ICalendarItem
|
||||
{
|
||||
[DebuggerDisplay("{Title} ({StartDate} - {EndDate})")]
|
||||
public class CalendarItem : ICalendarItem
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[NotNull]
|
||||
public string RemoteEventId { get; set; } = string.Empty;
|
||||
|
||||
[NotNull]
|
||||
public Guid CalendarId { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public IAccountCalendar AssignedCalendar { get; set; }
|
||||
|
||||
[NotNull]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public string? Location { get; set; }
|
||||
public string? HtmlLink { get; set; }
|
||||
|
||||
public DateTime StartDateTime { get; set; }
|
||||
|
||||
public DateTime EndDateTime { get; set; }
|
||||
|
||||
private ITimePeriod _period;
|
||||
public ITimePeriod Period
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
public string RemoteEventId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Location { get; set; }
|
||||
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
public DateTime EndDate
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
return StartDate.AddSeconds(DurationInSeconds);
|
||||
}
|
||||
}
|
||||
_period ??= new TimeRange(StartDateTime, EndDateTime);
|
||||
|
||||
public TimeSpan StartDateOffset { get; set; }
|
||||
public TimeSpan EndDateOffset { get; set; }
|
||||
|
||||
private ITimePeriod _period;
|
||||
public ITimePeriod Period
|
||||
{
|
||||
get
|
||||
{
|
||||
_period ??= new TimeRange(StartDate, EndDate);
|
||||
|
||||
return _period;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events that starts at midnight and ends at midnight are considered all-day events.
|
||||
/// </summary>
|
||||
public bool IsAllDayEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
StartDate.TimeOfDay == TimeSpan.Zero &&
|
||||
EndDate.TimeOfDay == TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events that are either an exceptional instance of a recurring event or occurrences.
|
||||
/// IsOccurrence is used to display occurrence instances of parent recurring events.
|
||||
/// IsOccurrence == false && IsRecurringChild == true => exceptional single instance.
|
||||
/// </summary>
|
||||
public bool IsRecurringChild
|
||||
{
|
||||
get
|
||||
{
|
||||
return RecurringCalendarItemId != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events that are either an exceptional instance of a recurring event or occurrences.
|
||||
/// </summary>
|
||||
public bool IsRecurringEvent => IsRecurringChild || IsRecurringParent;
|
||||
|
||||
/// <summary>
|
||||
/// Events that are the master event definition of recurrence events.
|
||||
/// </summary>
|
||||
public bool IsRecurringParent
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(Recurrence) && RecurringCalendarItemId == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events that are not all-day events and last more than one day are considered multi-day events.
|
||||
/// </summary>
|
||||
public bool IsMultiDayEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
return Period.Duration.TotalDays >= 1 && !IsAllDayEvent;
|
||||
}
|
||||
}
|
||||
|
||||
public double DurationInSeconds { get; set; }
|
||||
public string Recurrence { get; set; }
|
||||
|
||||
public string OrganizerDisplayName { get; set; }
|
||||
public string OrganizerEmail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The id of the parent calendar item of the recurring event.
|
||||
/// Exceptional instances are stored as a separate calendar item.
|
||||
/// This makes the calendar item a child of the recurring event.
|
||||
/// </summary>
|
||||
public Guid? RecurringCalendarItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates read-only events. Default is false.
|
||||
/// </summary>
|
||||
public bool IsLocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hidden events must not be displayed to the user.
|
||||
/// This usually happens when a child instance of recurring parent is cancelled after creation.
|
||||
/// </summary>
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
// TODO
|
||||
public string CustomEventColorHex { get; set; }
|
||||
public string HtmlLink { get; set; }
|
||||
public CalendarItemStatus Status { get; set; }
|
||||
public CalendarItemVisibility Visibility { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
public Guid CalendarId { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public IAccountCalendar AssignedCalendar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this item does not really exist in the database or not.
|
||||
/// These are used to display occurrence instances of parent recurring events.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public bool IsOccurrence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id to load information related to this event.
|
||||
/// Occurrences tracked by the parent recurring event if they are not exceptional instances.
|
||||
/// Recurring children here are exceptional instances. They have their own info in the database including Id.
|
||||
/// </summary>
|
||||
public Guid EventTrackingId => IsOccurrence ? RecurringCalendarItemId.Value : Id;
|
||||
|
||||
public CalendarItem CreateRecurrence(DateTime startDate, double durationInSeconds)
|
||||
{
|
||||
// Create a copy with the new start date and duration
|
||||
|
||||
return new CalendarItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Title = Title,
|
||||
Description = Description,
|
||||
Location = Location,
|
||||
StartDate = startDate,
|
||||
DurationInSeconds = durationInSeconds,
|
||||
Recurrence = Recurrence,
|
||||
OrganizerDisplayName = OrganizerDisplayName,
|
||||
OrganizerEmail = OrganizerEmail,
|
||||
RecurringCalendarItemId = Id,
|
||||
AssignedCalendar = AssignedCalendar,
|
||||
CalendarId = CalendarId,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
Visibility = Visibility,
|
||||
Status = Status,
|
||||
CustomEventColorHex = CustomEventColorHex,
|
||||
HtmlLink = HtmlLink,
|
||||
StartDateOffset = StartDateOffset,
|
||||
EndDateOffset = EndDateOffset,
|
||||
RemoteEventId = RemoteEventId,
|
||||
IsHidden = IsHidden,
|
||||
IsLocked = IsLocked,
|
||||
IsOccurrence = true
|
||||
};
|
||||
return _period;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAllDay { get; set; }
|
||||
|
||||
public string? TimeZone { get; set; }
|
||||
|
||||
public string? RecurrenceRules { get; set; }
|
||||
|
||||
public string? Status { get; set; }
|
||||
|
||||
public string? OrganizerDisplayName { get; set; }
|
||||
|
||||
public string? OrganizerEmail { get; set; }
|
||||
|
||||
public DateTime CreatedDate { get; set; }
|
||||
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
public bool IsDeleted { get; set; }
|
||||
|
||||
public string? RecurringEventId { get; set; }
|
||||
|
||||
public string? OriginalStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of calendar item (Timed, AllDay, MultiDay, etc.)
|
||||
/// </summary>
|
||||
public CalendarItemType ItemType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Automatically determines and sets the ItemType based on event properties
|
||||
/// </summary>
|
||||
public void DetermineItemType()
|
||||
{
|
||||
var hasRecurrence = !string.IsNullOrEmpty(RecurrenceRules);
|
||||
var isCancelled = Status?.ToLowerInvariant() == "cancelled" || IsDeleted;
|
||||
|
||||
ItemType = CalendarItemTypeHelper.DetermineItemType(
|
||||
StartDateTime,
|
||||
EndDateTime,
|
||||
IsAllDay,
|
||||
hasRecurrence,
|
||||
isCancelled,
|
||||
Status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar
|
||||
{
|
||||
public class Reminder
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
public Guid CalendarItemId { get; set; }
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
public DateTimeOffset ReminderTime { get; set; }
|
||||
public CalendarItemReminderType ReminderType { get; set; }
|
||||
}
|
||||
public class Reminder
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
public Guid CalendarItemId { get; set; }
|
||||
|
||||
public DateTimeOffset ReminderTime { get; set; }
|
||||
public CalendarItemReminderType ReminderType { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
public class AccountSignature
|
||||
{
|
||||
public class AccountSignature
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public string HtmlBody { get; set; }
|
||||
public string HtmlBody { get; set; }
|
||||
|
||||
public Guid MailAccountId { get; set; }
|
||||
}
|
||||
public Guid MailAccountId { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,63 +1,62 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
public class RemoteAccountAlias
|
||||
{
|
||||
public class RemoteAccountAlias
|
||||
{
|
||||
/// <summary>
|
||||
/// Display address of the alias.
|
||||
/// </summary>
|
||||
public string AliasAddress { get; set; }
|
||||
/// <summary>
|
||||
/// Display address of the alias.
|
||||
/// </summary>
|
||||
public string AliasAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Address to be included in Reply-To header when alias is used for sending messages.
|
||||
/// </summary>
|
||||
public string ReplyToAddress { get; set; }
|
||||
/// <summary>
|
||||
/// Address to be included in Reply-To header when alias is used for sending messages.
|
||||
/// </summary>
|
||||
public string ReplyToAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this alias is the primary alias for the account.
|
||||
/// </summary>
|
||||
public bool IsPrimary { get; set; }
|
||||
/// <summary>
|
||||
/// Whether this alias is the primary alias for the account.
|
||||
/// </summary>
|
||||
public bool IsPrimary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the alias is verified by the server.
|
||||
/// Only Gmail aliases are verified for now.
|
||||
/// Non-verified alias messages might be rejected by SMTP server.
|
||||
/// </summary>
|
||||
public bool IsVerified { get; set; }
|
||||
/// <summary>
|
||||
/// Whether the alias is verified by the server.
|
||||
/// Only Gmail aliases are verified for now.
|
||||
/// Non-verified alias messages might be rejected by SMTP server.
|
||||
/// </summary>
|
||||
public bool IsVerified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this alias is the root alias for the account.
|
||||
/// Root alias means the first alias that was created for the account.
|
||||
/// It can't be deleted or changed.
|
||||
/// </summary>
|
||||
public bool IsRootAlias { get; set; }
|
||||
/// <summary>
|
||||
/// Whether this alias is the root alias for the account.
|
||||
/// Root alias means the first alias that was created for the account.
|
||||
/// It can't be deleted or changed.
|
||||
/// </summary>
|
||||
public bool IsRootAlias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional sender name for the alias.
|
||||
/// Falls back to account's sender name if not set when preparing messages.
|
||||
/// Used for Gmail only.
|
||||
/// </summary>
|
||||
public string AliasSenderName { get; set; }
|
||||
}
|
||||
|
||||
public class MailAccountAlias : RemoteAccountAlias
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique Id for the alias.
|
||||
/// </summary>
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Account id that this alias is attached to.
|
||||
/// </summary>
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Root aliases can't be deleted.
|
||||
/// </summary>
|
||||
public bool CanDelete => !IsRootAlias;
|
||||
}
|
||||
/// <summary>
|
||||
/// Optional sender name for the alias.
|
||||
/// Falls back to account's sender name if not set when preparing messages.
|
||||
/// Used for Gmail only.
|
||||
/// </summary>
|
||||
public string AliasSenderName { get; set; }
|
||||
}
|
||||
|
||||
public class MailAccountAlias : RemoteAccountAlias
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique Id for the alias.
|
||||
/// </summary>
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Account id that this alias is attached to.
|
||||
/// </summary>
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Root aliases can't be deleted.
|
||||
/// </summary>
|
||||
public bool CanDelete => !IsRootAlias;
|
||||
}
|
||||
|
||||
@@ -5,153 +5,152 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
/// <summary>
|
||||
/// Summary of the parsed MIME messages.
|
||||
/// Wino will do non-network operations on this table and others from the original MIME.
|
||||
/// </summary>
|
||||
public class MailCopy : IMailItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary of the parsed MIME messages.
|
||||
/// Wino will do non-network operations on this table and others from the original MIME.
|
||||
/// Unique Id of the mail.
|
||||
/// </summary>
|
||||
public class MailCopy : IMailItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique Id of the mail.
|
||||
/// </summary>
|
||||
[PrimaryKey]
|
||||
public Guid UniqueId { get; set; }
|
||||
[PrimaryKey]
|
||||
public Guid UniqueId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Not unique id of the item. Some operations held on this Id, some on the UniqueId.
|
||||
/// Same message can be in different folder. In that case UniqueId is used.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
/// <summary>
|
||||
/// Not unique id of the item. Some operations held on this Id, some on the UniqueId.
|
||||
/// Same message can be in different folder. In that case UniqueId is used.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Folder that this mail belongs to.
|
||||
/// </summary>
|
||||
public Guid FolderId { get; set; }
|
||||
/// <summary>
|
||||
/// Folder that this mail belongs to.
|
||||
/// </summary>
|
||||
public Guid FolderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Conversation id for the mail.
|
||||
/// </summary>
|
||||
public string ThreadId { get; set; }
|
||||
/// <summary>
|
||||
/// Conversation id for the mail.
|
||||
/// </summary>
|
||||
public string ThreadId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MIME MessageId if exists.
|
||||
/// </summary>
|
||||
public string MessageId { get; set; }
|
||||
/// <summary>
|
||||
/// MIME MessageId if exists.
|
||||
/// </summary>
|
||||
public string MessageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// References header from MIME
|
||||
/// </summary>
|
||||
public string References { get; set; }
|
||||
/// <summary>
|
||||
/// References header from MIME
|
||||
/// </summary>
|
||||
public string References { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In-Reply-To header from MIME
|
||||
/// </summary>
|
||||
public string InReplyTo { get; set; }
|
||||
/// <summary>
|
||||
/// In-Reply-To header from MIME
|
||||
/// </summary>
|
||||
public string InReplyTo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name for the sender.
|
||||
/// </summary>
|
||||
public string FromName { get; set; }
|
||||
/// <summary>
|
||||
/// Name for the sender.
|
||||
/// </summary>
|
||||
public string FromName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Address of the sender.
|
||||
/// </summary>
|
||||
public string FromAddress { get; set; }
|
||||
/// <summary>
|
||||
/// Address of the sender.
|
||||
/// </summary>
|
||||
public string FromAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Subject of the mail.
|
||||
/// </summary>
|
||||
public string Subject { get; set; }
|
||||
/// <summary>
|
||||
/// Subject of the mail.
|
||||
/// </summary>
|
||||
public string Subject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Short preview of the content.
|
||||
/// </summary>
|
||||
public string PreviewText { get; set; }
|
||||
/// <summary>
|
||||
/// Short preview of the content.
|
||||
/// </summary>
|
||||
public string PreviewText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Date that represents this mail has been created in provider servers.
|
||||
/// Stored always in UTC.
|
||||
/// </summary>
|
||||
public DateTime CreationDate { get; set; }
|
||||
/// <summary>
|
||||
/// Date that represents this mail has been created in provider servers.
|
||||
/// Stored always in UTC.
|
||||
/// </summary>
|
||||
public DateTime CreationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importance of the mail.
|
||||
/// </summary>
|
||||
public MailImportance Importance { get; set; }
|
||||
/// <summary>
|
||||
/// Importance of the mail.
|
||||
/// </summary>
|
||||
public MailImportance Importance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read status for the mail.
|
||||
/// </summary>
|
||||
public bool IsRead { get; set; }
|
||||
/// <summary>
|
||||
/// Read status for the mail.
|
||||
/// </summary>
|
||||
public bool IsRead { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag status.
|
||||
/// Flagged for Outlook.
|
||||
/// Important for Gmail.
|
||||
/// </summary>
|
||||
public bool IsFlagged { get; set; }
|
||||
/// <summary>
|
||||
/// Flag status.
|
||||
/// Flagged for Outlook.
|
||||
/// Important for Gmail.
|
||||
/// </summary>
|
||||
public bool IsFlagged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// To support Outlook.
|
||||
/// Gmail doesn't use it.
|
||||
/// </summary>
|
||||
public bool IsFocused { get; set; }
|
||||
/// <summary>
|
||||
/// To support Outlook.
|
||||
/// Gmail doesn't use it.
|
||||
/// </summary>
|
||||
public bool IsFocused { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether mail has attachments included or not.
|
||||
/// </summary>
|
||||
public bool HasAttachments { get; set; }
|
||||
/// <summary>
|
||||
/// Whether mail has attachments included or not.
|
||||
/// </summary>
|
||||
public bool HasAttachments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Assigned draft id.
|
||||
/// </summary>
|
||||
public string DraftId { get; set; }
|
||||
/// <summary>
|
||||
/// Assigned draft id.
|
||||
/// </summary>
|
||||
public string DraftId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mail is only created locally.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public bool IsLocalDraft => !string.IsNullOrEmpty(DraftId) && DraftId.StartsWith(Constants.LocalDraftStartPrefix);
|
||||
/// <summary>
|
||||
/// Whether this mail is only created locally.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public bool IsLocalDraft => !string.IsNullOrEmpty(DraftId) && DraftId.StartsWith(Constants.LocalDraftStartPrefix);
|
||||
|
||||
/// <summary>
|
||||
/// Whether this copy is draft or not.
|
||||
/// </summary>
|
||||
public bool IsDraft { get; set; }
|
||||
/// <summary>
|
||||
/// Whether this copy is draft or not.
|
||||
/// </summary>
|
||||
public bool IsDraft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File id that this mail is assigned to.
|
||||
/// This Id is immutable. It's used to find the file in the file system.
|
||||
/// Even after mapping local draft to remote draft, it will not change.
|
||||
/// </summary>
|
||||
public Guid FileId { get; set; }
|
||||
/// <summary>
|
||||
/// File id that this mail is assigned to.
|
||||
/// This Id is immutable. It's used to find the file in the file system.
|
||||
/// Even after mapping local draft to remote draft, it will not change.
|
||||
/// </summary>
|
||||
public Guid FileId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Folder that this mail is assigned to.
|
||||
/// Warning: This field is not populated by queries.
|
||||
/// Services or View Models are responsible for populating this field.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MailItemFolder AssignedFolder { get; set; }
|
||||
/// <summary>
|
||||
/// Folder that this mail is assigned to.
|
||||
/// Warning: This field is not populated by queries.
|
||||
/// Services or View Models are responsible for populating this field.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MailItemFolder AssignedFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Account that this mail is assigned to.
|
||||
/// Warning: This field is not populated by queries.
|
||||
/// Services or View Models are responsible for populating this field.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MailAccount AssignedAccount { get; set; }
|
||||
/// <summary>
|
||||
/// Account that this mail is assigned to.
|
||||
/// Warning: This field is not populated by queries.
|
||||
/// Services or View Models are responsible for populating this field.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MailAccount AssignedAccount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contact information of the sender if exists.
|
||||
/// Warning: This field is not populated by queries.
|
||||
/// Services or View Models are responsible for populating this field.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public AccountContact SenderContact { get; set; }
|
||||
/// <summary>
|
||||
/// Contact information of the sender if exists.
|
||||
/// Warning: This field is not populated by queries.
|
||||
/// Services or View Models are responsible for populating this field.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public AccountContact SenderContact { get; set; }
|
||||
|
||||
public IEnumerable<Guid> GetContainingIds() => [UniqueId];
|
||||
public override string ToString() => $"{Subject} <-> {Id}";
|
||||
}
|
||||
public IEnumerable<Guid> GetContainingIds() => [UniqueId];
|
||||
public override string ToString() => $"{Subject} <-> {Id}";
|
||||
}
|
||||
|
||||
@@ -5,71 +5,70 @@ using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
[DebuggerDisplay("{FolderName} - {SpecialFolderType}")]
|
||||
public class MailItemFolder : IMailItemFolder
|
||||
{
|
||||
[DebuggerDisplay("{FolderName} - {SpecialFolderType}")]
|
||||
public class MailItemFolder : IMailItemFolder
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string RemoteFolderId { get; set; }
|
||||
public string ParentRemoteFolderId { get; set; }
|
||||
|
||||
public Guid MailAccountId { get; set; }
|
||||
public string FolderName { get; set; }
|
||||
public SpecialFolderType SpecialFolderType { get; set; }
|
||||
public bool IsSystemFolder { get; set; }
|
||||
public bool IsSticky { get; set; }
|
||||
public bool IsSynchronizationEnabled { get; set; }
|
||||
public bool IsHidden { get; set; }
|
||||
public bool ShowUnreadCount { get; set; }
|
||||
public DateTime? LastSynchronizedDate { get; set; }
|
||||
|
||||
// For IMAP
|
||||
public uint UidValidity { get; set; }
|
||||
public long HighestModeSeq { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Outlook shares delta changes per-folder. Gmail is for per-account.
|
||||
/// This is only used for Outlook provider.
|
||||
/// </summary>
|
||||
public string DeltaToken { get; set; }
|
||||
|
||||
// For GMail Labels
|
||||
public string TextColorHex { get; set; }
|
||||
public string BackgroundColorHex { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public List<IMailItemFolder> ChildFolders { get; set; } = [];
|
||||
|
||||
// Category and Move type folders are not valid move targets.
|
||||
// These folders are virtual. They don't exist on the server.
|
||||
public bool IsMoveTarget => !(SpecialFolderType == SpecialFolderType.More || SpecialFolderType == SpecialFolderType.Category);
|
||||
|
||||
public bool ContainsSpecialFolderType(SpecialFolderType type)
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
if (SpecialFolderType == type)
|
||||
return true;
|
||||
|
||||
public string RemoteFolderId { get; set; }
|
||||
public string ParentRemoteFolderId { get; set; }
|
||||
|
||||
public Guid MailAccountId { get; set; }
|
||||
public string FolderName { get; set; }
|
||||
public SpecialFolderType SpecialFolderType { get; set; }
|
||||
public bool IsSystemFolder { get; set; }
|
||||
public bool IsSticky { get; set; }
|
||||
public bool IsSynchronizationEnabled { get; set; }
|
||||
public bool IsHidden { get; set; }
|
||||
public bool ShowUnreadCount { get; set; }
|
||||
public DateTime? LastSynchronizedDate { get; set; }
|
||||
|
||||
// For IMAP
|
||||
public uint UidValidity { get; set; }
|
||||
public long HighestModeSeq { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Outlook shares delta changes per-folder. Gmail is for per-account.
|
||||
/// This is only used for Outlook provider.
|
||||
/// </summary>
|
||||
public string DeltaToken { get; set; }
|
||||
|
||||
// For GMail Labels
|
||||
public string TextColorHex { get; set; }
|
||||
public string BackgroundColorHex { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public List<IMailItemFolder> ChildFolders { get; set; } = [];
|
||||
|
||||
// Category and Move type folders are not valid move targets.
|
||||
// These folders are virtual. They don't exist on the server.
|
||||
public bool IsMoveTarget => !(SpecialFolderType == SpecialFolderType.More || SpecialFolderType == SpecialFolderType.Category);
|
||||
|
||||
public bool ContainsSpecialFolderType(SpecialFolderType type)
|
||||
foreach (var child in ChildFolders)
|
||||
{
|
||||
if (SpecialFolderType == type)
|
||||
return true;
|
||||
|
||||
foreach (var child in ChildFolders)
|
||||
if (child.SpecialFolderType == type)
|
||||
{
|
||||
if (child.SpecialFolderType == type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return child.ContainsSpecialFolderType(type);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return child.ContainsSpecialFolderType(type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static MailItemFolder CreateMoreFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.More, FolderName = Translator.MoreFolderNameOverride };
|
||||
public static MailItemFolder CreateCategoriesFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.Category, FolderName = Translator.CategoriesFolderNameOverride };
|
||||
|
||||
public override string ToString() => FolderName;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static MailItemFolder CreateMoreFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.More, FolderName = Translator.MoreFolderNameOverride };
|
||||
public static MailItemFolder CreateCategoriesFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.Category, FolderName = Translator.CategoriesFolderNameOverride };
|
||||
|
||||
public override string ToString() => FolderName;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail
|
||||
{
|
||||
public class MergedInbox
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
public class MergedInbox
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,76 +2,64 @@
|
||||
using System.Collections.Generic;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Back storage for simple name-address book.
|
||||
/// These values will be inserted during MIME fetch.
|
||||
/// </summary>
|
||||
|
||||
// TODO: This can easily evolve to Contact store, just like People app in Windows 10/11.
|
||||
// Do it.
|
||||
public class AccountContact : IEquatable<AccountContact>
|
||||
{
|
||||
/// <summary>
|
||||
/// Back storage for simple name-address book.
|
||||
/// These values will be inserted during MIME fetch.
|
||||
/// E-mail address of the contact.
|
||||
/// </summary>
|
||||
[PrimaryKey]
|
||||
public string Address { get; set; }
|
||||
|
||||
// TODO: This can easily evolve to Contact store, just like People app in Windows 10/11.
|
||||
// Do it.
|
||||
public class AccountContact : IEquatable<AccountContact>
|
||||
/// <summary>
|
||||
/// Display name of the contact.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Base64 encoded profile image of the contact.
|
||||
/// </summary>
|
||||
public string Base64ContactPicture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All registered accounts have their contacts registered as root.
|
||||
/// Root contacts must not be overridden by any configuration.
|
||||
/// They are created on account creation.
|
||||
/// </summary>
|
||||
public bool IsRootContact { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
/// <summary>
|
||||
/// E-mail address of the contact.
|
||||
/// </summary>
|
||||
[PrimaryKey]
|
||||
public string Address { get; set; }
|
||||
return Equals(obj as AccountContact);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display name of the contact.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
public bool Equals(AccountContact other)
|
||||
{
|
||||
return other is not null &&
|
||||
Address == other.Address &&
|
||||
Name == other.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64 encoded profile image of the contact.
|
||||
/// </summary>
|
||||
public string Base64ContactPicture { get; set; }
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Address, Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All registered accounts have their contacts registered as root.
|
||||
/// Root contacts must not be overridden by any configuration.
|
||||
/// They are created on account creation.
|
||||
/// </summary>
|
||||
public bool IsRootContact { get; set; }
|
||||
public static bool operator ==(AccountContact left, AccountContact right)
|
||||
{
|
||||
return EqualityComparer<AccountContact>.Default.Equals(left, right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Short display name of the contact.
|
||||
/// Eather Name or Address.
|
||||
/// </summary>
|
||||
public string ShortDisplayName => Address == Name || string.IsNullOrWhiteSpace(Name) ? $"{Address.ToLowerInvariant()};" : $"{Name};";
|
||||
|
||||
public string DisplayName => Address == Name || string.IsNullOrWhiteSpace(Name) ? Address.ToLowerInvariant() : $"{Name} <{Address.ToLowerInvariant()}>";
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as AccountContact);
|
||||
}
|
||||
|
||||
public bool Equals(AccountContact other)
|
||||
{
|
||||
return other is not null &&
|
||||
Address == other.Address &&
|
||||
Name == other.Name;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = -1717786383;
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Address);
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public static bool operator ==(AccountContact left, AccountContact right)
|
||||
{
|
||||
return EqualityComparer<AccountContact>.Default.Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(AccountContact left, AccountContact right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
public static bool operator !=(AccountContact left, AccountContact right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
public class CustomServerInformation
|
||||
{
|
||||
public class CustomServerInformation
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This field is ignored. DisplayName is stored in MailAccount as SenderName from now.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public string DisplayName { get; set; }
|
||||
public string Address { get; set; }
|
||||
public string IncomingServer { get; set; }
|
||||
public string IncomingServerUsername { get; set; }
|
||||
public string IncomingServerPassword { get; set; }
|
||||
public string IncomingServerPort { get; set; }
|
||||
|
||||
public CustomIncomingServerType IncomingServerType { get; set; }
|
||||
|
||||
public string OutgoingServer { get; set; }
|
||||
public string OutgoingServerPort { get; set; }
|
||||
public string OutgoingServerUsername { get; set; }
|
||||
public string OutgoingServerPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// useSSL True: SslOnConnect
|
||||
/// useSSL False: StartTlsWhenAvailable
|
||||
/// </summary>
|
||||
|
||||
public ImapConnectionSecurity IncomingServerSocketOption { get; set; }
|
||||
public ImapAuthenticationMethod IncomingAuthenticationMethod { get; set; }
|
||||
|
||||
|
||||
public ImapConnectionSecurity OutgoingServerSocketOption { get; set; }
|
||||
public ImapAuthenticationMethod OutgoingAuthenticationMethod { get; set; }
|
||||
|
||||
public string ProxyServer { get; set; }
|
||||
public string ProxyServerPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of concurrent clients that can connect to the server.
|
||||
/// Default is 5.
|
||||
/// </summary>
|
||||
public int MaxConcurrentClients { get; set; }
|
||||
|
||||
public Dictionary<string, string> GetConnectionProperties()
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
// Printout the public connection properties.
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
var connectionProperties = new Dictionary<string, string>
|
||||
{
|
||||
{ "IncomingServer", IncomingServer },
|
||||
{ "IncomingServerPort", IncomingServerPort },
|
||||
{ "IncomingServerSocketOption", IncomingServerSocketOption.ToString() },
|
||||
{ "IncomingAuthenticationMethod", IncomingAuthenticationMethod.ToString() },
|
||||
{ "OutgoingServer", OutgoingServer },
|
||||
{ "OutgoingServerPort", OutgoingServerPort },
|
||||
{ "OutgoingServerSocketOption", OutgoingServerSocketOption.ToString() },
|
||||
{ "OutgoingAuthenticationMethod", OutgoingAuthenticationMethod.ToString() },
|
||||
{ "ProxyServer", ProxyServer },
|
||||
{ "ProxyServerPort", ProxyServerPort }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// This field is ignored. DisplayName is stored in MailAccount as SenderName from now.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public string DisplayName { get; set; }
|
||||
public string Address { get; set; }
|
||||
public string IncomingServer { get; set; }
|
||||
public string IncomingServerUsername { get; set; }
|
||||
public string IncomingServerPassword { get; set; }
|
||||
public string IncomingServerPort { get; set; }
|
||||
|
||||
public CustomIncomingServerType IncomingServerType { get; set; }
|
||||
|
||||
public string OutgoingServer { get; set; }
|
||||
public string OutgoingServerPort { get; set; }
|
||||
public string OutgoingServerUsername { get; set; }
|
||||
public string OutgoingServerPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// useSSL True: SslOnConnect
|
||||
/// useSSL False: StartTlsWhenAvailable
|
||||
/// </summary>
|
||||
|
||||
public ImapConnectionSecurity IncomingServerSocketOption { get; set; }
|
||||
public ImapAuthenticationMethod IncomingAuthenticationMethod { get; set; }
|
||||
|
||||
|
||||
public ImapConnectionSecurity OutgoingServerSocketOption { get; set; }
|
||||
public ImapAuthenticationMethod OutgoingAuthenticationMethod { get; set; }
|
||||
|
||||
public string ProxyServer { get; set; }
|
||||
public string ProxyServerPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of concurrent clients that can connect to the server.
|
||||
/// Default is 5.
|
||||
/// </summary>
|
||||
public int MaxConcurrentClients { get; set; }
|
||||
return connectionProperties;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,109 +3,108 @@ using SQLite;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
public class MailAccount
|
||||
{
|
||||
public class MailAccount
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Given name of the account in Wino.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Given name of the account in Wino.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Display name of the authenticated user/account.
|
||||
/// API integrations will query this value from the API.
|
||||
/// IMAP is populated by user on setup dialog.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// TODO: Display name of the authenticated user/account.
|
||||
/// API integrations will query this value from the API.
|
||||
/// IMAP is populated by user on setup dialog.
|
||||
/// </summary>
|
||||
|
||||
public string SenderName { get; set; }
|
||||
public string SenderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Account e-mail address.
|
||||
/// </summary>
|
||||
public string Address { get; set; }
|
||||
/// <summary>
|
||||
/// Account e-mail address.
|
||||
/// </summary>
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provider type of the account. Outlook,Gmail etc...
|
||||
/// </summary>
|
||||
public MailProviderType ProviderType { get; set; }
|
||||
/// <summary>
|
||||
/// Provider type of the account. Outlook,Gmail etc...
|
||||
/// </summary>
|
||||
public MailProviderType ProviderType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For tracking mail change delta.
|
||||
/// Gmail : historyId
|
||||
/// Outlook: deltaToken
|
||||
/// </summary>
|
||||
public string SynchronizationDeltaIdentifier { get; set; }
|
||||
/// <summary>
|
||||
/// For tracking mail change delta.
|
||||
/// Gmail : historyId
|
||||
/// Outlook: deltaToken
|
||||
/// </summary>
|
||||
public string SynchronizationDeltaIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// For tracking calendar change delta.
|
||||
/// Gmail: It's per-calendar, so unused.
|
||||
/// Outlook: deltaLink
|
||||
/// </summary>
|
||||
public string CalendarSynchronizationDeltaIdentifier { get; set; }
|
||||
/// <summary>
|
||||
/// For tracking calendar change delta.
|
||||
/// Gmail: It's per-calendar, so unused.
|
||||
/// Outlook: deltaLink
|
||||
/// </summary>
|
||||
public string CalendarSynchronizationDeltaIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Gets or sets the custom account identifier color in hex.
|
||||
/// </summary>
|
||||
public string AccountColorHex { get; set; }
|
||||
/// <summary>
|
||||
/// TODO: Gets or sets the custom account identifier color in hex.
|
||||
/// </summary>
|
||||
public string AccountColorHex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Base64 encoded profile picture of the account.
|
||||
/// </summary>
|
||||
public string Base64ProfilePictureData { get; set; }
|
||||
/// <summary>
|
||||
/// Base64 encoded profile picture of the account.
|
||||
/// </summary>
|
||||
public string Base64ProfilePictureData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the listing order of the account in the accounts list.
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the listing order of the account in the accounts list.
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the account has any reason for an interactive user action to fix continue operating.
|
||||
/// </summary>
|
||||
public AccountAttentionReason AttentionReason { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets whether the account has any reason for an interactive user action to fix continue operating.
|
||||
/// </summary>
|
||||
public AccountAttentionReason AttentionReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the id of the merged inbox this account belongs to.
|
||||
/// </summary>
|
||||
public Guid? MergedInboxId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the id of the merged inbox this account belongs to.
|
||||
/// </summary>
|
||||
public Guid? MergedInboxId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the additional IMAP provider assignment for the account.
|
||||
/// Providers that use IMAP as a synchronizer but have special requirements.
|
||||
/// </summary>
|
||||
public SpecialImapProvider SpecialImapProvider { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the additional IMAP provider assignment for the account.
|
||||
/// Providers that use IMAP as a synchronizer but have special requirements.
|
||||
/// </summary>
|
||||
public SpecialImapProvider SpecialImapProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains the merged inbox this account belongs to.
|
||||
/// Ignored for all SQLite operations.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MergedInbox MergedInbox { get; set; }
|
||||
/// <summary>
|
||||
/// Contains the merged inbox this account belongs to.
|
||||
/// Ignored for all SQLite operations.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MergedInbox MergedInbox { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Populated only when account has custom server information.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Populated only when account has custom server information.
|
||||
/// </summary>
|
||||
|
||||
[Ignore]
|
||||
public CustomServerInformation ServerInformation { get; set; }
|
||||
[Ignore]
|
||||
public CustomServerInformation ServerInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Account preferences.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MailAccountPreferences Preferences { get; set; }
|
||||
/// <summary>
|
||||
/// Account preferences.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public MailAccountPreferences Preferences { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the account can perform ProfileInformation sync type.
|
||||
/// </summary>
|
||||
public bool IsProfileInfoSyncSupported => ProviderType == MailProviderType.Outlook || ProviderType == MailProviderType.Gmail;
|
||||
/// <summary>
|
||||
/// Gets whether the account can perform ProfileInformation sync type.
|
||||
/// </summary>
|
||||
public bool IsProfileInfoSyncSupported => ProviderType == MailProviderType.Outlook || ProviderType == MailProviderType.Gmail;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the account can perform AliasInformation sync type.
|
||||
/// </summary>
|
||||
public bool IsAliasSyncSupported => ProviderType == MailProviderType.Gmail;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets whether the account can perform AliasInformation sync type.
|
||||
/// </summary>
|
||||
public bool IsAliasSyncSupported => ProviderType == MailProviderType.Gmail;
|
||||
}
|
||||
|
||||
@@ -1,54 +1,53 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
public class MailAccountPreferences
|
||||
{
|
||||
public class MailAccountPreferences
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id of the account in MailAccount table.
|
||||
/// </summary>
|
||||
public Guid AccountId { get; set; }
|
||||
/// <summary>
|
||||
/// Id of the account in MailAccount table.
|
||||
/// </summary>
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether sent draft messages should be appended to the sent folder.
|
||||
/// Some IMAP servers do this automatically, some don't.
|
||||
/// It's disabled by default.
|
||||
/// </summary>
|
||||
public bool ShouldAppendMessagesToSentFolder { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets whether sent draft messages should be appended to the sent folder.
|
||||
/// Some IMAP servers do this automatically, some don't.
|
||||
/// It's disabled by default.
|
||||
/// </summary>
|
||||
public bool ShouldAppendMessagesToSentFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the notifications are enabled for the account.
|
||||
/// </summary>
|
||||
public bool IsNotificationsEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets whether the notifications are enabled for the account.
|
||||
/// </summary>
|
||||
public bool IsNotificationsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the account has Focused inbox support.
|
||||
/// Null if the account provider type doesn't support Focused inbox.
|
||||
/// </summary>
|
||||
public bool? IsFocusedInboxEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets whether the account has Focused inbox support.
|
||||
/// Null if the account provider type doesn't support Focused inbox.
|
||||
/// </summary>
|
||||
public bool? IsFocusedInboxEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether signature should be appended automatically.
|
||||
/// </summary>
|
||||
public bool IsSignatureEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets whether signature should be appended automatically.
|
||||
/// </summary>
|
||||
public bool IsSignatureEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this account's unread items should be included in taskbar badge.
|
||||
/// </summary>
|
||||
public bool IsTaskbarBadgeEnabled { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Gets or sets whether this account's unread items should be included in taskbar badge.
|
||||
/// </summary>
|
||||
public bool IsTaskbarBadgeEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets signature for new messages. Null if signature is not needed.
|
||||
/// </summary>
|
||||
public Guid? SignatureIdForNewMessages { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets signature for new messages. Null if signature is not needed.
|
||||
/// </summary>
|
||||
public Guid? SignatureIdForNewMessages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets signature for following messages. Null if signature is not needed.
|
||||
/// </summary>
|
||||
public Guid? SignatureIdForFollowingMessages { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets signature for following messages. Null if signature is not needed.
|
||||
/// </summary>
|
||||
public Guid? SignatureIdForFollowingMessages { get; set; }
|
||||
}
|
||||
|
||||
14
Wino.Core.Domain/Entities/Shared/Thumbnail.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
public class Thumbnail
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Domain { get; set; }
|
||||
|
||||
public string Gravatar { get; set; }
|
||||
public string Favicon { get; set; }
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Wino.Core.Domain.Enums
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum AccountAttentionReason
|
||||
{
|
||||
public enum AccountAttentionReason
|
||||
{
|
||||
None,
|
||||
InvalidCredentials,
|
||||
MissingSystemFolderConfiguration
|
||||
}
|
||||
None,
|
||||
InvalidCredentials,
|
||||
MissingSystemFolderConfiguration
|
||||
}
|
||||
|
||||
7
Wino.Core.Domain/Enums/AccountCacheResetReason.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum AccountCacheResetReason
|
||||
{
|
||||
AccountRemoval,
|
||||
ExpiredCache
|
||||
}
|
||||