22 Commits

Author SHA1 Message Date
Burak Kaan Köse
4f09345769 Added simple validations for advanced imap setup dialog to prevent users from making mistakes. 2025-02-22 01:43:09 +01:00
Aleh Khantsevich
5073ead8fe Extract webvieweditor to share between compose page and signature editor (#578)
* initial work for webview editor control

* moved more stuff to editor itself

* revert packages.props indention changes

* move alignment logic

* Migrate signature editor to new control

* move background to editor control

* Some polishing

* Fixed the corner glitch issue with dark theme.

---------

Co-authored-by: Burak Kaan Köse <bkaankose@outlook.com>
2025-02-22 00:43:39 +01:00
Burak Kaan Köse
f61bcb621b Online Search (#576)
* Very basic online search for gmail.

* Server side of handling offline search and listing part in listing page.

* Default search mode implementation and search UI improvements.

* Online search for Outlook.

* Very basic online search for gmail.

* Server side of handling offline search and listing part in listing page.

* Default search mode implementation and search UI improvements.

* Online search for Outlook.

* Online search for imap without downloading the messages yet. TODO

* Completing imap search.
2025-02-22 00:22:00 +01:00
Burak Kaan Köse
42b695854b Merge branch 'main' of https://github.com/bkaankose/Wino-Mail 2025-02-20 00:54:46 +01:00
Burak Kaan Köse
496ae8b1b2 Download imap messages in ascending order. 2025-02-20 00:54:41 +01:00
Aleh Khantsevich
4215a2592f Remove last simicolon in to/cc/bcc (#574) 2025-02-18 20:51:02 +01:00
Sean Chen
bca62033a1 Log unexpected exceptions on sync failure (#569) 2025-02-16 21:15:31 +01:00
Burak Kaan Köse
18a91f9223 Fix condstore synchronization. 2025-02-16 20:40:53 +01:00
Burak Kaan Köse
474d7c7a26 New Crowdin updates (#568)
* New translations resources.json (Romanian)

* New translations resources.json (French)

* New translations resources.json (Spanish)

* New translations resources.json (Catalan)

* New translations resources.json (Czech)

* New translations resources.json (Danish)

* New translations resources.json (German)

* New translations resources.json (Greek)

* New translations resources.json (Finnish)

* New translations resources.json (Italian)

* New translations resources.json (Japanese)

* New translations resources.json (Dutch)

* New translations resources.json (Polish)

* New translations resources.json (Russian)

* New translations resources.json (Turkish)

* New translations resources.json (Ukrainian)

* New translations resources.json (Chinese Simplified)

* New translations resources.json (Galician)

* New translations resources.json (Portuguese, Brazilian)

* New translations resources.json (Indonesian)

* New translations resources.json (Lithuanian)
2025-02-16 18:13:32 +01:00
Burak Kaan Köse
3f9a51ff46 Fix portuguese - brazil typo. 2025-02-16 17:06:07 +01:00
Burak Kaan Köse
df3b5c41f9 Clicking on loaded account menu item will automatically go to Inbox. 2025-02-16 16:56:59 +01:00
Burak Kaan Köse
8800d11ab0 Lower the amount of text needed to start auto-complete in composer page to 2. 2025-02-16 16:56:42 +01:00
Burak Kaan Köse
f021834ceb Fixing diagnostic id being not saved properly. 2025-02-16 16:42:48 +01:00
Burak Kaan Köse
f54a39a549 Fix missing ; for 'you' 2025-02-16 16:33:02 +01:00
Burak Kaan Köse
c312ff3faf Ignore folders that can't be opened for IMAP. 2025-02-16 16:17:41 +01:00
Burak Kaan Köse
db833594f4 Make sure idle disconnects are not logged to app insights. 2025-02-16 16:14:50 +01:00
Burak Kaan Köse
d36cf59829 Translated dates based on display language. (#567)
* Updating the app's culture based on the display language and making sure that dates/times are properly translated.
2025-02-16 14:46:34 +01:00
Aleh Khantsevich
caae751698 Show "You" for active account in mail rendering page (#566)
* Added account contact view model to handle "You" case.

* fix namespaces again
2025-02-16 14:38:53 +01:00
Burak Kaan Köse
f7836eedce Tracking failed imap setup steps for app insights. 2025-02-16 13:23:45 +01:00
Aleh Khantsevich
3ddc1a6229 file scoped namespaces (#565) 2025-02-16 11:54:23 +01:00
Burak Kaan Köse
cf9869b71e Revert "File scoped namespaces"
This reverts commit d31d8f574e.
2025-02-16 11:43:30 +01:00
Aleh Khantsevich
d31d8f574e File scoped namespaces 2025-02-16 11:35:43 +01:00
661 changed files with 34329 additions and 33496 deletions

View File

@@ -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

View File

@@ -1,64 +1,65 @@
<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>
<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.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>

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -11,116 +11,115 @@ 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 => a.Username == account.Address);
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.");
}
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));
}
}

View File

@@ -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";
}

View File

@@ -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.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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -1,23 +1,22 @@
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 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);
}

View File

@@ -2,24 +2,23 @@
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; }
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;
/// <summary>
/// Unused for now.
/// </summary>
public string TextColorHex { get; set; }
public string BackgroundColorHex { get; set; }
public string TimeZone { get; set; }
}

View File

@@ -2,19 +2,18 @@
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; }
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; }
}

View File

@@ -5,176 +5,175 @@ using SQLite;
using Wino.Core.Domain.Enums;
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; }
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
{
[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);
}
}
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 StartDate.AddSeconds(DurationInSeconds);
}
}
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
};
}
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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;
}

View File

@@ -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}";
}

View File

@@ -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;
}

View File

@@ -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; }
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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; }
}

View File

@@ -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
}

View File

@@ -1,17 +1,16 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum AccountCreationDialogState
{
public enum AccountCreationDialogState
{
Idle,
SigningIn,
PreparingFolders,
Completed,
ManuelSetupWaiting,
TestingConnection,
AutoDiscoverySetup,
AutoDiscoveryInProgress,
FetchingProfileInformation,
Canceled,
FetchingEvents
}
Idle,
SigningIn,
PreparingFolders,
Completed,
ManuelSetupWaiting,
TestingConnection,
AutoDiscoverySetup,
AutoDiscoveryInProgress,
FetchingProfileInformation,
Canceled,
FetchingEvents
}

View File

@@ -1,12 +1,11 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
/// <summary>
/// Indicates the state of synchronizer.
/// </summary>
public enum AccountSynchronizerState
{
/// <summary>
/// Indicates the state of synchronizer.
/// </summary>
public enum AccountSynchronizerState
{
Idle,
ExecutingRequests,
Synchronizing
}
Idle,
ExecutingRequests,
Synchronizing
}

View File

@@ -1,21 +1,20 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum AppLanguage
{
public enum AppLanguage
{
None,
English,
Deutsch,
Russian,
Turkish,
Polish,
Czech,
Chinese,
Spanish,
French,
Indonesian,
Greek,
PortugeseBrazil,
Italian,
Romanian
}
None,
English,
Deutsch,
Russian,
Turkish,
Polish,
Czech,
Chinese,
Spanish,
French,
Indonesian,
Greek,
PortugeseBrazil,
Italian,
Romanian
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum AppThemeType
{
public enum AppThemeType
{
System,
PreDefined,
Custom,
}
System,
PreDefined,
Custom,
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum ApplicationElementTheme
{
public enum ApplicationElementTheme
{
Default,
Light,
Dark
}
Default,
Light,
Dark
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum AttendeeStatus
{
public enum AttendeeStatus
{
NeedsAction,
Accepted,
Tentative,
Declined
}
NeedsAction,
Accepted,
Tentative,
Declined
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum BackgroundSynchronizationReason
{
public enum BackgroundSynchronizationReason
{
SessionConnected,
Timer
}
SessionConnected,
Timer
}

View File

@@ -1,11 +1,10 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarDisplayType
{
public enum CalendarDisplayType
{
Day,
Week,
WorkWeek,
Month,
Year
}
Day,
Week,
WorkWeek,
Month,
Year
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarEventTargetType
{
public enum CalendarEventTargetType
{
Single, // Show details for a single event.
Series // Show the series event. Parent of all recurring events.
}
Single, // Show details for a single event.
Series // Show the series event. Parent of all recurring events.
}

View File

@@ -1,11 +1,10 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
/// <summary>
/// Trigger to load more data.
/// </summary>
public enum CalendarInitInitiative
{
/// <summary>
/// Trigger to load more data.
/// </summary>
public enum CalendarInitInitiative
{
User,
App
}
User,
App
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarItemRecurrenceFrequency
{
public enum CalendarItemRecurrenceFrequency
{
Daily,
Weekly,
Monthly,
Yearly
}
Daily,
Weekly,
Monthly,
Yearly
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarItemReminderType
{
public enum CalendarItemReminderType
{
Popup,
Email
}
Popup,
Email
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarItemStatus
{
public enum CalendarItemStatus
{
NotResponded,
Confirmed,
Tentative,
Cancelled,
}
NotResponded,
Confirmed,
Tentative,
Cancelled,
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarItemVisibility
{
public enum CalendarItemVisibility
{
Default,
Public,
Private,
Confidential
}
Default,
Public,
Private,
Confidential
}

View File

@@ -1,12 +1,11 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
/// <summary>
/// Which way in time to load more data for calendar.
/// </summary>
public enum CalendarLoadDirection
{
/// <summary>
/// Which way in time to load more data for calendar.
/// </summary>
public enum CalendarLoadDirection
{
Replace,
Previous,
Next
}
Replace,
Previous,
Next
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarOrientation
{
public enum CalendarOrientation
{
Horizontal,
Vertical
}
Horizontal,
Vertical
}

View File

@@ -1,11 +1,10 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CalendarSynchronizationType
{
public enum CalendarSynchronizationType
{
ExecuteRequests, // Execute all requests in the queue.
CalendarMetadata, // Sync calendar metadata.
CalendarEvents, // Sync all events for all calendars.
SingleCalendar, // Sync events for only specified calendars.
UpdateProfile // Update profile information only.
}
ExecuteRequests, // Execute all requests in the queue.
CalendarMetadata, // Sync calendar metadata.
CalendarEvents, // Sync all events for all calendars.
SingleCalendar, // Sync events for only specified calendars.
UpdateProfile // Update profile information only.
}

View File

@@ -1,24 +1,23 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum ChangeRequestType
{
public enum ChangeRequestType
{
MailMarkAs,
MailChangeFlag,
MailHardDelete,
MailMove,
MailAlwaysMoveTo,
MailChangeFocused,
MailArchive,
MailUnarchive,
FolderMarkAsRead,
FolderDelete,
FolderEmpty,
FolderRename,
CreateNewDraft,
CreateReplyDraft,
CreateForwardDraft,
DiscardDraft,
SendDraft,
FetchSingleItem
}
MailMarkAs,
MailChangeFlag,
MailHardDelete,
MailMove,
MailAlwaysMoveTo,
MailChangeFocused,
MailArchive,
MailUnarchive,
FolderMarkAsRead,
FolderDelete,
FolderEmpty,
FolderRename,
CreateNewDraft,
CreateReplyDraft,
CreateForwardDraft,
DiscardDraft,
SendDraft,
FetchSingleItem
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum CustomIncomingServerType
{
public enum CustomIncomingServerType
{
POP3,
IMAP4
}
POP3,
IMAP4
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum DayHeaderDisplayType
{
public enum DayHeaderDisplayType
{
TwelveHour,
TwentyFourHour,
}
TwelveHour,
TwentyFourHour,
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum DraftCreationReason
{
public enum DraftCreationReason
{
Empty,
Reply,
ReplyAll,
Forward
}
Empty,
Reply,
ReplyAll,
Forward
}

View File

@@ -1,11 +1,10 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum FilterOptionType
{
public enum FilterOptionType
{
All,
Unread,
Flagged,
Mentions,
Files
}
All,
Unread,
Flagged,
Mentions,
Files
}

View File

@@ -1,23 +1,22 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
/// <summary>
/// Defines all possible folder operations that can be done.
/// Available values for each folder is returned by IContextMenuProvider
/// that integrators hold.
/// </summary>
public enum FolderOperation
{
/// <summary>
/// Defines all possible folder operations that can be done.
/// Available values for each folder is returned by IContextMenuProvider
/// that integrators hold.
/// </summary>
public enum FolderOperation
{
None,
Pin,
Unpin,
MarkAllAsRead,
DontSync,
Empty,
Rename,
Delete,
Move,
TurnOffNotifications,
CreateSubFolder,
Seperator
}
None,
Pin,
Unpin,
MarkAllAsRead,
DontSync,
Empty,
Rename,
Delete,
Move,
TurnOffNotifications,
CreateSubFolder,
Seperator
}

View File

@@ -1,13 +1,12 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum ImapAuthenticationMethod
{
public enum ImapAuthenticationMethod
{
Auto,
None,
NormalPassword,
EncryptedPassword,
Ntlm,
CramMd5,
DigestMd5
}
Auto,
None,
NormalPassword,
EncryptedPassword,
Ntlm,
CramMd5,
DigestMd5
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum ImapConnectionSecurity
{
public enum ImapConnectionSecurity
{
Auto,
None,
StartTls,
SslTls
}
Auto,
None,
StartTls,
SslTls
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum InfoBarAnimationType
{
public enum InfoBarAnimationType
{
SlideFromRightToLeft,
SlideFromBottomToTop
}
SlideFromRightToLeft,
SlideFromBottomToTop
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum InfoBarMessageType
{
public enum InfoBarMessageType
{
Information,
Success,
Warning,
Error
}
Information,
Success,
Warning,
Error
}

View File

@@ -1,16 +1,15 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum MailAttachmentType
{
public enum MailAttachmentType
{
None,
Executable,
Image,
Audio,
Video,
PDF,
HTML,
RarArchive,
Archive,
Other
}
None,
Executable,
Image,
Audio,
Video,
PDF,
HTML,
RarArchive,
Archive,
Other
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum MailImportance
{
public enum MailImportance
{
Low,
Normal,
High
}
Low,
Normal,
High
}

View File

@@ -1,9 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum MailListDisplayMode
{
public enum MailListDisplayMode
{
Spacious,
Medium,
Compact,
}
Spacious,
Medium,
Compact,
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum MailMarkAsOption
{
public enum MailMarkAsOption
{
WhenSelected,
DontMark,
AfterDelay
}
WhenSelected,
DontMark,
AfterDelay
}

View File

@@ -1,58 +1,57 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
// Synchronizer requests.
public enum MailSynchronizerOperation
{
// Synchronizer requests.
public enum MailSynchronizerOperation
{
MarkRead,
Move,
Delete, // Hard delete.
CreateDraft,
Send,
ChangeFlag,
AlwaysMoveTo,
MoveToFocused,
Archive,
}
public enum FolderSynchronizerOperation
{
RenameFolder,
EmptyFolder,
MarkFolderRead,
}
// UI requests
public enum MailOperation
{
None,
Archive,
UnArchive,
SoftDelete,
HardDelete,
Move,
MoveToJunk,
MoveToFocused,
MoveToOther,
AlwaysMoveToOther,
AlwaysMoveToFocused,
SetFlag,
ClearFlag,
MarkAsRead,
MarkAsUnread,
MarkAsNotJunk,
Seperator,
Ignore,
Reply,
ReplyAll,
Zoom,
SaveAs,
Find,
Forward,
DarkEditor,
LightEditor,
Print,
ViewMessageSource,
DiscardLocalDraft,
Navigate // For toast activation
}
MarkRead,
Move,
Delete, // Hard delete.
CreateDraft,
Send,
ChangeFlag,
AlwaysMoveTo,
MoveToFocused,
Archive,
}
public enum FolderSynchronizerOperation
{
RenameFolder,
EmptyFolder,
MarkFolderRead,
}
// UI requests
public enum MailOperation
{
None,
Archive,
UnArchive,
SoftDelete,
HardDelete,
Move,
MoveToJunk,
MoveToFocused,
MoveToOther,
AlwaysMoveToOther,
AlwaysMoveToFocused,
SetFlag,
ClearFlag,
MarkAsRead,
MarkAsUnread,
MarkAsNotJunk,
Seperator,
Ignore,
Reply,
ReplyAll,
Zoom,
SaveAs,
Find,
Forward,
DarkEditor,
LightEditor,
Print,
ViewMessageSource,
DiscardLocalDraft,
Navigate // For toast activation
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum MailProviderType
{
public enum MailProviderType
{
Outlook,
Gmail,
IMAP4 = 4 // 2-3 were removed after release. Don't change for backward compatibility.
}
Outlook,
Gmail,
IMAP4 = 4 // 2-3 were removed after release. Don't change for backward compatibility.
}

View File

@@ -1,14 +1,13 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum MailSynchronizationType
{
public enum MailSynchronizationType
{
UpdateProfile, // Only update profile information
ExecuteRequests, // Run the queued requests, and then synchronize if needed.
FoldersOnly, // Only synchronize folder metadata.
InboxOnly, // Only Inbox, Sent, Draft and Deleted folders.
CustomFolders, // Only sync folders that are specified in the options.
FullFolders, // Synchronize all folders. This won't update profile or alias information.
Alias, // Only update alias information
IMAPIdle // Idle client triggered synchronization.
}
UpdateProfile, // Only update profile information
ExecuteRequests, // Run the queued requests, and then synchronize if needed.
FoldersOnly, // Only synchronize folder metadata.
InboxOnly, // Only Inbox, Sent, Draft and Deleted folders.
CustomFolders, // Only sync folders that are specified in the options.
FullFolders, // Synchronize all folders. This won't update profile or alias information.
Alias, // Only update alias information
IMAPIdle // Idle client triggered synchronization.
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum NavigationReferenceFrame
{
public enum NavigationReferenceFrame
{
ShellFrame,
RenderingFrame
}
ShellFrame,
RenderingFrame
}

View File

@@ -1,12 +1,11 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
/// <summary>
/// Defines the potential reasons for picking folder in the folder picking dialog.
/// </summary>
public enum PickFolderReason
{
/// <summary>
/// Defines the potential reasons for picking folder in the folder picking dialog.
/// </summary>
public enum PickFolderReason
{
Move,
SpecialFolder,
Any
}
Move,
SpecialFolder,
Any
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum PrintingResult
{
public enum PrintingResult
{
Abandoned,
Canceled,
Failed,
Submitted
}
Abandoned,
Canceled,
Failed,
Submitted
}

View File

@@ -0,0 +1,6 @@
namespace Wino.Core.Domain.Enums;
public enum SearchMode
{
Local,
Online
}

View File

@@ -1,12 +1,11 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
/// <summary>
/// What should happen to server app when the client is terminated.
/// </summary>
public enum ServerBackgroundMode
{
/// <summary>
/// What should happen to server app when the client is terminated.
/// </summary>
public enum ServerBackgroundMode
{
MinimizedTray, // Still runs, tray icon is visible.
Invisible, // Still runs, tray icon is invisible.
Terminate // Server is terminated as Wino terminates.
}
MinimizedTray, // Still runs, tray icon is visible.
Invisible, // Still runs, tray icon is invisible.
Terminate // Server is terminated as Wino terminates.
}

View File

@@ -1,8 +1,7 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum SortingOptionType
{
public enum SortingOptionType
{
ReceiveDate,
Sender
}
ReceiveDate,
Sender
}

View File

@@ -1,24 +1,23 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum SpecialFolderType
{
public enum SpecialFolderType
{
Inbox,
Starred,
Important,
Sent,
Draft,
Archive,
Deleted,
Junk,
Chat,
Category,
Unread,
Forums,
Updates,
Personal,
Promotions,
Social,
Other,
More
}
Inbox,
Starred,
Important,
Sent,
Draft,
Archive,
Deleted,
Junk,
Chat,
Category,
Unread,
Forums,
Updates,
Personal,
Promotions,
Social,
Other,
More
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum SpecialImapProvider
{
public enum SpecialImapProvider
{
None,
iCloud,
Yahoo
}
None,
iCloud,
Yahoo
}

View File

@@ -1,11 +1,10 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum StartupBehaviorResult
{
public enum StartupBehaviorResult
{
Enabled,
Disabled,
DisabledByUser,
DisabledByPolicy,
Fatal
}
Enabled,
Disabled,
DisabledByUser,
DisabledByPolicy,
Fatal
}

View File

@@ -1,19 +1,18 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
// From the SDK.
public enum StorePurchaseResult
{
// From the SDK.
public enum StorePurchaseResult
{
//
// Summary:
// The purchase request succeeded.
Succeeded,
//
// Summary:
// The current user has already purchased the specified app or add-on.
AlreadyPurchased,
//
// Summary:
// The purchase request did not succeed.
NotPurchased,
}
//
// Summary:
// The purchase request succeeded.
Succeeded,
//
// Summary:
// The current user has already purchased the specified app or add-on.
AlreadyPurchased,
//
// Summary:
// The purchase request did not succeed.
NotPurchased,
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum SynchronizationCompletedState
{
public enum SynchronizationCompletedState
{
Success, // All succeeded.
Canceled, // Canceled by user or HTTP call.
Failed // Exception.
}
Success, // All succeeded.
Canceled, // Canceled by user or HTTP call.
Failed // Exception.
}

View File

@@ -1,12 +1,11 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
/// <summary>
/// Enumeration for the source of synchronization.
/// Right now it can either be from the client or the server.
/// </summary>
public enum SynchronizationSource
{
/// <summary>
/// Enumeration for the source of synchronization.
/// Right now it can either be from the client or the server.
/// </summary>
public enum SynchronizationSource
{
Client,
Server
}
Client,
Server
}

View File

@@ -1,9 +1,8 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum WinoAppType
{
public enum WinoAppType
{
Unknown,
Mail,
Calendar
}
Unknown,
Mail,
Calendar
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum WinoCustomMessageDialogIcon
{
public enum WinoCustomMessageDialogIcon
{
Information,
Warning,
Error,
Question
}
Information,
Warning,
Error,
Question
}

View File

@@ -1,34 +1,33 @@
namespace Wino.Core.Domain.Enums
{
/// <summary>
/// All registered views.
/// </summary>
public enum WinoPage
{
None,
IdlePage,
ComposePage,
SettingsPage,
MailRenderingPage,
WelcomePage,
AccountDetailsPage,
MergedAccountDetailsPage,
ManageAccountsPage,
AccountManagementPage,
SignatureManagementPage,
AboutPage,
PersonalizationPage,
MessageListPage,
MailListPage,
ReadComposePanePage,
LanguageTimePage,
AppPreferencesPage,
SettingOptionsPage,
AliasManagementPage,
namespace Wino.Core.Domain.Enums;
// Calendar
CalendarPage,
CalendarSettingsPage,
EventDetailsPage
}
/// <summary>
/// All registered views.
/// </summary>
public enum WinoPage
{
None,
IdlePage,
ComposePage,
SettingsPage,
MailRenderingPage,
WelcomePage,
AccountDetailsPage,
MergedAccountDetailsPage,
ManageAccountsPage,
AccountManagementPage,
SignatureManagementPage,
AboutPage,
PersonalizationPage,
MessageListPage,
MailListPage,
ReadComposePanePage,
LanguageTimePage,
AppPreferencesPage,
SettingOptionsPage,
AliasManagementPage,
// Calendar
CalendarPage,
CalendarSettingsPage,
EventDetailsPage
}

View File

@@ -1,11 +1,10 @@
namespace Wino.Core.Domain.Enums
namespace Wino.Core.Domain.Enums;
public enum WinoServerConnectionStatus
{
public enum WinoServerConnectionStatus
{
None,
Connecting,
Connected,
Disconnected,
Failed
}
None,
Connecting,
Connected,
Disconnected,
Failed
}

View File

@@ -1,7 +1,6 @@
namespace Wino.Core.Domain.Exceptions
{
public class AccountSetupCanceledException : System.Exception
{
namespace Wino.Core.Domain.Exceptions;
public class AccountSetupCanceledException : System.Exception
{
}
}

View File

@@ -1,19 +1,18 @@
using System;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Exceptions
{
/// <summary>
/// Thrown when IAuthenticator requires user interaction to fix authentication issues.
/// It can be expired and can't restorable token, or some stuff that requires re-authentication.
/// </summary>
public class AuthenticationAttentionException : Exception
{
public AuthenticationAttentionException(MailAccount account)
{
Account = account;
}
namespace Wino.Core.Domain.Exceptions;
public MailAccount Account { get; }
/// <summary>
/// Thrown when IAuthenticator requires user interaction to fix authentication issues.
/// It can be expired and can't restorable token, or some stuff that requires re-authentication.
/// </summary>
public class AuthenticationAttentionException : Exception
{
public AuthenticationAttentionException(MailAccount account)
{
Account = account;
}
public MailAccount Account { get; }
}

View File

@@ -1,18 +1,17 @@
using System;
namespace Wino.Core.Domain.Exceptions
{
/// <summary>
/// All exceptions related to authentication.
/// </summary>
public class AuthenticationException : Exception
{
public AuthenticationException(string message) : base(message)
{
}
namespace Wino.Core.Domain.Exceptions;
public AuthenticationException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// All exceptions related to authentication.
/// </summary>
public class AuthenticationException : Exception
{
public AuthenticationException(string message) : base(message)
{
}
public AuthenticationException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -1,9 +1,8 @@
using System;
namespace Wino.Core.Domain.Exceptions
{
/// <summary>
/// An exception thrown when the background task registration is failed.
/// </summary>
public class BackgroundTaskRegistrationFailedException : Exception { }
}
namespace Wino.Core.Domain.Exceptions;
/// <summary>
/// An exception thrown when the background task registration is failed.
/// </summary>
public class BackgroundTaskRegistrationFailedException : Exception { }

View File

@@ -1,11 +1,10 @@
using System;
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
/// <summary>
/// Thrown when composer cant find the mime to load.
/// </summary>
public class ComposerMimeNotFoundException : Exception
{
/// <summary>
/// Thrown when composer cant find the mime to load.
/// </summary>
public class ComposerMimeNotFoundException : Exception
{
}
}

View File

@@ -1,11 +1,10 @@
using System;
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
public class CustomThemeCreationFailedException : Exception
{
public class CustomThemeCreationFailedException : Exception
public CustomThemeCreationFailedException(string message) : base(message)
{
public CustomThemeCreationFailedException(string message) : base(message)
{
}
}
}

View File

@@ -1,7 +1,6 @@
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
public class GoogleAuthenticationException : System.Exception
{
public class GoogleAuthenticationException : System.Exception
{
public GoogleAuthenticationException(string message) : base(message) { }
}
public GoogleAuthenticationException(string message) : base(message) { }
}

View File

@@ -1,14 +1,30 @@
using System;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
public class ImapClientPoolException : Exception
{
public class ImapClientPoolException : Exception
public ImapClientPoolException()
{
public ImapClientPoolException(Exception innerException, string protocolLog) : base(Translator.Exception_ImapClientPoolFailed, innerException)
{
ProtocolLog = protocolLog;
}
public string ProtocolLog { get; }
}
public ImapClientPoolException(string message, CustomServerInformation customServerInformation, string protocolLog) : base(message)
{
CustomServerInformation = customServerInformation;
ProtocolLog = protocolLog;
}
public ImapClientPoolException(string message, string protocolLog) : base(message)
{
ProtocolLog = protocolLog;
}
public ImapClientPoolException(Exception innerException, string protocolLog) : base(Translator.Exception_ImapClientPoolFailed, innerException)
{
ProtocolLog = protocolLog;
}
public CustomServerInformation CustomServerInformation { get; }
public string ProtocolLog { get; }
}

View File

@@ -1,18 +1,17 @@
using Wino.Core.Domain.Models.AutoDiscovery;
namespace Wino.Core.Domain.Exceptions
{
public class ImapConnectionFailedPackage
{
public ImapConnectionFailedPackage(string errorMessage, string protocolLog, AutoDiscoverySettings settings)
{
ErrorMessage = errorMessage;
ProtocolLog = protocolLog;
Settings = settings;
}
namespace Wino.Core.Domain.Exceptions;
public AutoDiscoverySettings Settings { get; }
public string ErrorMessage { get; set; }
public string ProtocolLog { get; }
public class ImapConnectionFailedPackage
{
public ImapConnectionFailedPackage(string errorMessage, string protocolLog, AutoDiscoverySettings settings)
{
ErrorMessage = errorMessage;
ProtocolLog = protocolLog;
Settings = settings;
}
public AutoDiscoverySettings Settings { get; }
public string ErrorMessage { get; set; }
public string ProtocolLog { get; }
}

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Exceptions
{
public class ImapSynchronizerStrategyException : System.Exception
{
public ImapSynchronizerStrategyException(string message) : base(message)
{
namespace Wino.Core.Domain.Exceptions;
public class ImapSynchronizerStrategyException : System.Exception
{
public ImapSynchronizerStrategyException(string message) : base(message)
{
}
}
}

View File

@@ -1,17 +1,16 @@
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
public class ImapTestSSLCertificateException : System.Exception
{
public class ImapTestSSLCertificateException : System.Exception
public ImapTestSSLCertificateException(string issuer, string expirationDateString, string validFromDateString)
{
public ImapTestSSLCertificateException(string issuer, string expirationDateString, string validFromDateString)
{
Issuer = issuer;
ExpirationDateString = expirationDateString;
ValidFromDateString = validFromDateString;
}
public string Issuer { get; set; }
public string ExpirationDateString { get; set; }
public string ValidFromDateString { get; set; }
Issuer = issuer;
ExpirationDateString = expirationDateString;
ValidFromDateString = validFromDateString;
}
public string Issuer { get; set; }
public string ExpirationDateString { get; set; }
public string ValidFromDateString { get; set; }
}

View File

@@ -1,6 +1,5 @@
using System;
namespace Wino.Core.Domain.Exceptions
{
public class InvalidMoveTargetException : Exception { }
}
namespace Wino.Core.Domain.Exceptions;
public class InvalidMoveTargetException : Exception { }

View File

@@ -1,7 +1,6 @@
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
public class MissingAliasException : System.Exception
{
public class MissingAliasException : System.Exception
{
public MissingAliasException() : base(Translator.Exception_MissingAlias) { }
}
public MissingAliasException() : base(Translator.Exception_MissingAlias) { }
}

View File

@@ -1,11 +1,10 @@
using System;
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
public class SynchronizerEntityNotFoundException : Exception
{
public class SynchronizerEntityNotFoundException : Exception
public SynchronizerEntityNotFoundException(string message) : base(message)
{
public SynchronizerEntityNotFoundException(string message) : base(message)
{
}
}
}

View File

@@ -1,15 +1,14 @@
using System;
namespace Wino.Core.Domain.Exceptions
{
public class SynchronizerException : Exception
{
public SynchronizerException(string message) : base(message)
{
}
namespace Wino.Core.Domain.Exceptions;
public SynchronizerException(string message, Exception innerException) : base(message, innerException)
{
}
public class SynchronizerException : Exception
{
public SynchronizerException(string message) : base(message)
{
}
public SynchronizerException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -1,7 +1,6 @@
namespace Wino.Core.Domain.Exceptions
{
/// <summary>
/// When IMAP account's system folder configuration setup is not done yet.
/// </summary>
public class SystemFolderConfigurationMissingException : System.Exception { }
}
namespace Wino.Core.Domain.Exceptions;
/// <summary>
/// When IMAP account's system folder configuration setup is not done yet.
/// </summary>
public class SystemFolderConfigurationMissingException : System.Exception { }

View File

@@ -1,20 +1,19 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Exceptions
{
/// <summary>
/// Emitted when special folder is needed for an operation but it couldn't be found.
/// </summary>
public class UnavailableSpecialFolderException : Exception
{
public UnavailableSpecialFolderException(SpecialFolderType specialFolderType, Guid accountId)
{
SpecialFolderType = specialFolderType;
AccountId = accountId;
}
namespace Wino.Core.Domain.Exceptions;
public SpecialFolderType SpecialFolderType { get; }
public Guid AccountId { get; set; }
/// <summary>
/// Emitted when special folder is needed for an operation but it couldn't be found.
/// </summary>
public class UnavailableSpecialFolderException : Exception
{
public UnavailableSpecialFolderException(SpecialFolderType specialFolderType, Guid accountId)
{
SpecialFolderType = specialFolderType;
AccountId = accountId;
}
public SpecialFolderType SpecialFolderType { get; }
public Guid AccountId { get; set; }
}

View File

@@ -1,12 +1,11 @@
using System;
namespace Wino.Core.Domain.Exceptions
namespace Wino.Core.Domain.Exceptions;
/// <summary>
/// All server crash types. Wino Server ideally should not throw anything else than this Exception type.
/// </summary>
public class WinoServerException : Exception
{
/// <summary>
/// All server crash types. Wino Server ideally should not throw anything else than this Exception type.
/// </summary>
public class WinoServerException : Exception
{
public WinoServerException(string message) : base(message) { }
}
public WinoServerException(string message) : base(message) { }
}

View File

@@ -1,33 +1,32 @@
using System;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Extensions
namespace Wino.Core.Domain.Extensions;
public static class DateTimeExtensions
{
public static class DateTimeExtensions
/// <summary>
/// Returns a date range for the month of the given date.
/// </summary>
/// <param name="date">Date to get range for.</param>
public static DateRange GetMonthDateRangeStartingWeekday(this DateTime date, DayOfWeek WeekStartDay)
{
/// <summary>
/// Returns a date range for the month of the given date.
/// </summary>
/// <param name="date">Date to get range for.</param>
public static DateRange GetMonthDateRangeStartingWeekday(this DateTime date, DayOfWeek WeekStartDay)
{
DateTime firstDayOfMonth = new DateTime(date.Year, date.Month, 1);
DateTime firstDayOfMonth = new DateTime(date.Year, date.Month, 1);
int daysToSubtract = (7 + (firstDayOfMonth.DayOfWeek - WeekStartDay)) % 7;
DateTime rangeStart = firstDayOfMonth.AddDays(-daysToSubtract);
int daysToSubtract = (7 + (firstDayOfMonth.DayOfWeek - WeekStartDay)) % 7;
DateTime rangeStart = firstDayOfMonth.AddDays(-daysToSubtract);
DateTime rangeEnd = rangeStart.AddDays(34);
DateTime rangeEnd = rangeStart.AddDays(34);
return new DateRange(rangeStart, rangeEnd);
}
return new DateRange(rangeStart, rangeEnd);
}
public static DateTime GetWeekStartDateForDate(this DateTime date, DayOfWeek firstDayOfWeek)
{
// Detect the first day of the week that contains the selected date.
int diff = (7 + (date.DayOfWeek - firstDayOfWeek)) % 7;
public static DateTime GetWeekStartDateForDate(this DateTime date, DayOfWeek firstDayOfWeek)
{
// Detect the first day of the week that contains the selected date.
int diff = (7 + (date.DayOfWeek - firstDayOfWeek)) % 7;
// Start loading from this date instead of visible date.
return date.AddDays(-diff).Date;
}
// Start loading from this date instead of visible date.
return date.AddDays(-diff).Date;
}
}

View File

@@ -1,24 +1,23 @@
using System;
using System.Collections.Generic;
namespace Wino.Core.Domain.Extensions
{
public static class ExceptionExtensions
{
public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
{
if (ex == null)
{
throw new ArgumentNullException("ex");
}
namespace Wino.Core.Domain.Extensions;
var innerException = ex;
do
{
yield return innerException;
innerException = innerException.InnerException;
}
while (innerException != null);
public static class ExceptionExtensions
{
public static IEnumerable<Exception> GetInnerExceptions(this Exception ex)
{
if (ex == null)
{
throw new ArgumentNullException("ex");
}
var innerException = ex;
do
{
yield return innerException;
innerException = innerException.InnerException;
}
while (innerException != null);
}
}

View File

@@ -1,20 +1,19 @@
using System;
using System.IO;
namespace Wino.Core.Domain.Extensions
namespace Wino.Core.Domain.Extensions;
public static class MimeExtensions
{
public static class MimeExtensions
public static string GetBase64MimeMessage(this MimeKit.MimeMessage message)
{
public static string GetBase64MimeMessage(this MimeKit.MimeMessage message)
{
using MemoryStream memoryStream = new();
using MemoryStream memoryStream = new();
message.WriteTo(memoryStream);
message.WriteTo(memoryStream);
return Convert.ToBase64String(memoryStream.ToArray());
}
public static MimeKit.MimeMessage GetMimeMessageFromBase64(this string base64)
=> MimeKit.MimeMessage.Load(new System.IO.MemoryStream(Convert.FromBase64String(base64)));
return Convert.ToBase64String(memoryStream.ToArray());
}
public static MimeKit.MimeMessage GetMimeMessageFromBase64(this string base64)
=> MimeKit.MimeMessage.Load(new System.IO.MemoryStream(Convert.FromBase64String(base64)));
}

View File

@@ -1,16 +1,15 @@
using System;
namespace Wino.Core.Domain.Interfaces
namespace Wino.Core.Domain.Interfaces;
public interface IAccountCalendar
{
public interface IAccountCalendar
{
string Name { get; set; }
string TextColorHex { get; set; }
string BackgroundColorHex { get; set; }
bool IsPrimary { get; set; }
Guid AccountId { get; set; }
string RemoteCalendarId { get; set; }
bool IsExtended { get; set; }
Guid Id { get; set; }
}
string Name { get; set; }
string TextColorHex { get; set; }
string BackgroundColorHex { get; set; }
bool IsPrimary { get; set; }
Guid AccountId { get; set; }
string RemoteCalendarId { get; set; }
bool IsExtended { get; set; }
Guid Id { get; set; }
}

View File

@@ -2,12 +2,11 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Interfaces
namespace Wino.Core.Domain.Interfaces;
public interface IAccountCreationDialog
{
public interface IAccountCreationDialog
{
Task ShowDialogAsync(CancellationTokenSource cancellationTokenSource);
void Complete(bool cancel);
AccountCreationDialogState State { get; set; }
}
Task ShowDialogAsync(CancellationTokenSource cancellationTokenSource);
void Complete(bool cancel);
AccountCreationDialogState State { get; set; }
}

View File

@@ -2,21 +2,20 @@
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Interfaces
namespace Wino.Core.Domain.Interfaces;
public interface IAccountMenuItem : IMenuItem
{
public interface IAccountMenuItem : IMenuItem
{
bool IsEnabled { get; set; }
double SynchronizationProgress { get; set; }
int UnreadItemCount { get; set; }
IEnumerable<MailAccount> HoldingAccounts { get; }
void UpdateAccount(MailAccount account);
}
public interface IMergedAccountMenuItem : IAccountMenuItem
{
int MergedAccountCount { get; }
MergedInbox Parameter { get; }
}
bool IsEnabled { get; set; }
double SynchronizationProgress { get; set; }
int UnreadItemCount { get; set; }
IEnumerable<MailAccount> HoldingAccounts { get; }
void UpdateAccount(MailAccount account);
}
public interface IMergedAccountMenuItem : IAccountMenuItem
{
int MergedAccountCount { get; }
MergedInbox Parameter { get; }
}

View File

@@ -1,6 +1,5 @@
namespace Wino.Core.Domain.Interfaces
namespace Wino.Core.Domain.Interfaces;
public interface IAccountPickerDialog
{
public interface IAccountPickerDialog
{
}
}

View File

@@ -1,38 +1,37 @@
using System;
namespace Wino.Core.Domain.Interfaces
namespace Wino.Core.Domain.Interfaces;
public interface IAccountProviderDetailViewModel
{
public interface IAccountProviderDetailViewModel
{
/// <summary>
/// Entity id that will help to identify the startup entity on launch.
/// </summary>
Guid StartupEntityId { get; }
/// <summary>
/// Entity id that will help to identify the startup entity on launch.
/// </summary>
Guid StartupEntityId { get; }
/// <summary>
/// Name representation of the view model that will be used to identify the startup entity on launch.
/// </summary>
string StartupEntityTitle { get; }
/// <summary>
/// Name representation of the view model that will be used to identify the startup entity on launch.
/// </summary>
string StartupEntityTitle { get; }
/// <summary>
/// E-mail addresses that this account holds.
/// </summary>
/// <summary>
/// E-mail addresses that this account holds.
/// </summary>
string StartupEntityAddresses { get; }
string StartupEntityAddresses { get; }
/// <summary>
/// Represents the account order in the accounts list.
/// </summary>
int Order { get; }
/// <summary>
/// Represents the account order in the accounts list.
/// </summary>
int Order { get; }
/// <summary>
/// Provider details of the account.
/// </summary>
IProviderDetail ProviderDetail { get; set; }
/// <summary>
/// Provider details of the account.
/// </summary>
IProviderDetail ProviderDetail { get; set; }
/// <summary>
/// How many accounts this provider has.
/// </summary>
int HoldingAccountCount { get; }
}
/// <summary>
/// How many accounts this provider has.
/// </summary>
int HoldingAccountCount { get; }
}

Some files were not shown because too many files have changed in this diff Show More