47 Commits

Author SHA1 Message Date
Aleh Khantsevich
832b363da7 Improved outlook online search even more and removed redundant methods from ChangeProcessor (#586) 2025-02-24 18:53:11 +01:00
Dinuru Seniya
cf8f1ecd67 Code cleanup (#585)
1.  Moved the IsBackground property assignment into the object initializer for the Thread object.

2. Replaced e.Args[e.Args.Length - 1] with e.Args[^1]

3. Added a conditional check to see if GetWindowThreadProcessId returns 0, which indicates failure. If it fails, throw a Win32Exception with the last Win32 error.

4. Removed unused assignment to the variable process

5. Changed the return type of the ConfigureServices method from IServiceProvider to ServiceProvider. It is more specific and faster.

6. Changed notifyIcon to _notifyIcon according to private var naming scheme.

7. Added the CharSet = CharSet.Unicode attribute to the DllImport declarations to specify that the string arguments should be marshaled as Unicode.
2025-02-24 09:50:44 +01:00
Burak Kaan Köse
ee5129830c Gmail crash fix. 2025-02-24 09:48:07 +01:00
Aleh Khantsevich
9facfaffa8 Improved online search performance when doing local operations (#584)
* Improved online search performance when doing local operations

* Retruning an empty list on no item searches.

* Fixed an issue with batch imap downloads.

---------

Co-authored-by: Burak Kaan Köse <bkaankose@outlook.com>
2025-02-23 22:17:40 +01:00
Burak Kaan Köse
31b859ba1a Release notes for v1.10.2 2025-02-23 20:58:33 +01:00
Burak Kaan Köse
b0f5a24c30 New Crowdin updates (#583)
* 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-23 19:09:27 +01:00
Burak Kaan Köse
b60b594e44 Id -> ID in ENG translations. 2025-02-23 19:08:01 +01:00
Burak Kaan Köse
a8cee1016b Enable default accounts synchronization and timer sync for debug builds but not if it is attached. 2025-02-23 17:24:59 +01:00
Burak Kaan Köse
b551af01fa Missing archive id check for gmail synchronizer. 2025-02-23 17:16:53 +01:00
Burak Kaan Köse
b178869a8e Merge branch 'main' of https://github.com/bkaankose/Wino-Mail 2025-02-23 17:05:53 +01:00
Burak Kaan Köse
8e1c60d5f0 Gmail - Archive/Unarchive (#582)
* Disable timer back sync for debug builds.

* Archive / unarchive feature for Gmail.

* Archive folder name override for Gmail.

* Possible crash fix when the next item is being selected after a mail is removed.

* Restore proper account selection after pin/unpin of folder.

* Making sure that incorrect arcive folder id is not saved in Gmailsynchronizer due to migration.
2025-02-23 17:05:46 +01:00
Burak Kaan Köse
71ea49439e Disable timer back sync for debug builds. 2025-02-23 16:01:51 +01:00
Burak Kaan Köse
9d0a2f6535 Ignore folder filter if label specific query is passed to Gmail. 2025-02-23 10:21:58 +01:00
Burak Kaan Köse
c091fffe90 Hnadling of folder delta token 410 GONE for Outlook. 2025-02-23 00:35:13 +01:00
Burak Kaan Köse
7e05d05f94 Implemented cache reset for Gmail history id expiration. (#581) 2025-02-22 23:09:53 +01:00
Burak Kaan Köse
bd5b51c62f Added capability to detect disabled gmail service for Google Workspace accounts during account creation. (#580) 2025-02-22 17:51:38 +01:00
Burak Kaan Köse
1d5eb2eced Added simple validations for advanced imap setup dialog to prevent users from making mistakes. (#579) 2025-02-22 01:54:52 +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
Burak Kaan Köse
c1336428dc AppCenter to AppInsights migration. (#562)
* Remove AppCenter usage and libraries.

* Remove redundant pacakges and add the app insights sink.

* Diagnostic id support and manipulating telemetries.

* Handling of appdomain unhandled exceptions.

* Remove unused package identity package from mail project.

* Fixing printing.
2025-02-16 01:44:41 +01:00
Burak Kaan Köse
f0e513bf0d New Crowdin updates (#559)
* 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)
2025-02-15 12:55:45 +01:00
Burak Kaan Köse
ee9e41c5a7 IMAP Improvements (#558)
* Fixing an issue where scrollviewer overrides a part of template in mail list. Adjusted zoomed out header grid's corner radius.

* IDLE implementation, imap synchronization strategies basics and condstore synchronization.

* Adding iCloud and Yahoo as special IMAP handling scenario.

* iCloud special imap handling.

* Support for killing synchronizers.

* Update privacy policy url.

* Batching condstore downloads into 50, using SORT extension for searches if supported.

* Bumping some nugets. More on the imap synchronizers.

* Delegating idle synchronizations to server to post-sync operations.

* Update mailkit to resolve qresync bug with iCloud.

* Fixing remote highest mode seq checks for qresync and condstore synchronizers.

* Yahoo custom settings.

* Bump google sdk package.

* Fixing the build issue....

* NRE on canceled token accounts during setup.

* Server crash handlers.

* Remove ARM32. Upgrade server to .NET 9.

* Fix icons for yahoo and apple.

* Fixed an issue where disabled folders causing an exception on forced sync.

* Remove smtp encoding constraint.

* Remove commented code.

* Fixing merge conflict

* Addressing double registrations for mailkit remote folder events in synchronizers.

* Making sure idle canceled result is not reported.

* Fixing custom imap server dialog opening.

* Fixing the issue with account creation making the previously selected account as selected as well.

* Fixing app close behavior and logging app close.
2025-02-15 12:53:32 +01:00
Aleh Khantsevich
30f1257983 Attempt to fix source generator issues (#556)
* Changed some properties of source generator project

* Remove accelerate
2025-02-14 19:16:54 +01:00
Aleh Khantsevich
f007cef208 Updated Privacy policy URL (#557) 2025-02-14 19:15:42 +01:00
Burak Kaan Köse
19b5852098 Missing package description and fixing typo. 2025-02-14 02:14:04 +01:00
Aleh Khantsevich
2ec05ea7cc UWP .NET9 (#555)
* Ground work for NET9 UWP switch.

* Add launch settings for Wino.Mail

* Added new test WAP project

* fix platforms in slnx solution

* ManagePackageVersionsCentrally set default

* Fixing assets and couple issues with the new packaging project.

* Add back markdown

* Fix nuget warnings

* FIx error in WAP about build tools

* Add build.props with default language preview

* Some AOT compilation progress.

* More AOT stuff.

* Remove deprecated protocol auth activation handler.

* Fix remaining protocol handler for google auth.

* Even more AOT

* More more AOT fixes

* Fix a few more AOT warnings

* Fix signature editor AOT

* Fix composer and renderer AOT JSON

* Outlook Sync AOT

* Fixing bundle generation and package signing.

---------

Co-authored-by: Burak Kaan Köse <bkaankose@outlook.com>
2025-02-14 01:43:52 +01:00
Aleh Khantsevich
e8dd8bff44 Added save of drafts when closing app (#546) 2025-02-09 10:42:51 +01:00
Aleh Khantsevich
ab3f65edfa clear selection on htmlRender (#544) 2025-02-04 21:47:49 +01:00
779 changed files with 41418 additions and 38758 deletions

View File

@@ -149,7 +149,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter
# Code-block preferences # Code-block preferences
csharp_prefer_braces = true:silent csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = block_scoped:silent csharp_style_namespace_declarations = file_scoped:error
# Expression-level preferences # Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion csharp_prefer_simple_default_expression = true:suggestion
@@ -288,3 +288,5 @@ csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_prefer_readonly_struct = 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_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

7
Directory.Build.Props Normal file
View File

@@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>preview</LangVersion>
<IsAotCompatible>true</IsAotCompatible>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
</Project>

65
Directory.Packages.props Normal file
View File

@@ -0,0 +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.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.Enums;
using Wino.Core.Domain.Interfaces; 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.Interfaces;
using Wino.Core.Domain.Models.Authentication; 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; return new TokenInformationEx(userCredential.Token.AccessToken, account.Address);
public bool ProposeCopyAuthURL { get; set; } }
public override MailProviderType ProviderType => MailProviderType.Gmail; private Task<UserCredential> GetGoogleUserCredentialAsync(MailAccount account)
{
/// <summary> return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets()
/// 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); ClientId = ClientId
}, AuthenticatorConfig.GmailScope, account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
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));
}
} }
} }

View File

@@ -1,16 +0,0 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Authentication
{
public class Office365Authenticator : OutlookAuthenticator
{
public Office365Authenticator(INativeAppService nativeAppService,
IApplicationConfiguration applicationConfiguration,
IAuthenticatorConfig authenticatorConfig) : base(nativeAppService, applicationConfiguration, authenticatorConfig)
{
}
public override MailProviderType ProviderType => MailProviderType.Office365;
}
}

View File

@@ -11,116 +11,115 @@ using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Authentication; 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"; _applicationConfiguration = applicationConfiguration;
private bool isTokenCacheAttached = false;
// Outlook var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
private const string Authority = "https://login.microsoftonline.com/common";
public override MailProviderType ProviderType => MailProviderType.Outlook; var options = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
private readonly IPublicClientApplication _publicClientApplication;
private readonly IApplicationConfiguration _applicationConfiguration;
public OutlookAuthenticator(INativeAppService nativeAppService,
IApplicationConfiguration applicationConfiguration,
IAuthenticatorConfig authenticatorConfig) : base(authenticatorConfig)
{ {
_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) _publicClientApplication = outlookAppBuilder.Build();
{ }
Title = "Wino Mail",
ListOperatingSystemAccounts = true,
};
var outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId) public string[] Scope => AuthenticatorConfig.OutlookScope;
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd)
.WithBroker(options)
.WithDefaultRedirectUri()
.WithAuthority(Authority);
_publicClientApplication = outlookAppBuilder.Build(); private async Task EnsureTokenCacheAttachedAsync()
} {
if (!isTokenCacheAttached)
public string[] Scope => AuthenticatorConfig.OutlookScope;
private async Task EnsureTokenCacheAttachedAsync()
{ {
if (!isTokenCacheAttached) var storageProperties = new StorageCreationPropertiesBuilder(TokenCacheFileName, _applicationConfiguration.PublisherSharedFolderPath).Build();
{ var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
var storageProperties = new StorageCreationPropertiesBuilder(TokenCacheFileName, _applicationConfiguration.PublisherSharedFolderPath).Build(); msalcachehelper.RegisterCache(_publicClientApplication.UserTokenCache);
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(); await EnsureTokenCacheAttachedAsync();
var storedAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address); var authResult = await _publicClientApplication
.AcquireTokenInteractive(Scope)
.ExecuteAsync();
if (storedAccount == null) // If the account is null, it means it's the initial creation of it.
return await GenerateTokenInformationAsync(account); // 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); 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;
}
} }
catch (MsalClientException msalClientException)
public async Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
{ {
try if (msalClientException.ErrorCode == "authentication_canceled" || msalClientException.ErrorCode == "access_denied")
{ throw new AccountSetupCanceledException();
await EnsureTokenCacheAttachedAsync();
var authResult = await _publicClientApplication throw;
.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 new AuthenticationException(Translator.Exception_UnknowErrorDuringAuthentication, new Exception(Translator.Exception_TokenGenerationFailed));
} }
} }

View File

@@ -1,26 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<RootNamespace>Wino.Authentication</RootNamespace> <RootNamespace>Wino.Authentication</RootNamespace>
<Configurations>Debug;Release</Configurations> <Platforms>x86;x64;arm64</Platforms>
<LangVersion>12</LangVersion>
<Platforms>AnyCPU;x64;x86</Platforms>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio> <AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Diagnostics" Version="8.3.2" /> <PackageReference Include="CommunityToolkit.Diagnostics" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" /> <PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Google.Apis.Auth" Version="1.68.0" /> <PackageReference Include="Google.Apis.Auth" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.2" /> <PackageReference Include="Microsoft.Identity.Client" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.66.2" /> <PackageReference Include="Microsoft.Identity.Client.Broker" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.66.2" /> <PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" /> <ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" /> <ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -20,14 +20,6 @@
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
<Platform>x64</Platform> <Platform>x64</Platform>
</ProjectConfiguration> </ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64"> <ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<Platform>ARM64</Platform> <Platform>ARM64</Platform>
@@ -76,7 +68,6 @@
</ItemGroup> </ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" /> <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" PrivateAssets="all" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -81,7 +81,7 @@ namespace Wino.Calendar.ViewModels
if (accountCreationDialogResult == null) return; if (accountCreationDialogResult == null) return;
var accountCreationCancellationTokenSource = new CancellationTokenSource(); var accountCreationCancellationTokenSource = new CancellationTokenSource();
var accountCreationDialog = CalendarDialogService.GetAccountCreationDialog(accountCreationDialogResult.ProviderType); var accountCreationDialog = CalendarDialogService.GetAccountCreationDialog(accountCreationDialogResult);
accountCreationDialog.ShowDialog(accountCreationCancellationTokenSource); accountCreationDialog.ShowDialog(accountCreationCancellationTokenSource);
accountCreationDialog.State = AccountCreationDialogState.SigningIn; accountCreationDialog.State = AccountCreationDialogState.SigningIn;
@@ -92,7 +92,6 @@ namespace Wino.Calendar.ViewModels
{ {
ProviderType = accountCreationDialogResult.ProviderType, ProviderType = accountCreationDialogResult.ProviderType,
Name = accountCreationDialogResult.AccountName, Name = accountCreationDialogResult.AccountName,
AccountColorHex = accountCreationDialogResult.AccountColorHex,
Id = Guid.NewGuid() Id = Guid.NewGuid()
}; };
@@ -104,13 +103,8 @@ namespace Wino.Calendar.ViewModels
if (accountCreationDialog.State == AccountCreationDialogState.Canceled) if (accountCreationDialog.State == AccountCreationDialogState.Canceled)
throw new AccountSetupCanceledException(); throw new AccountSetupCanceledException();
tokenInformationResponse.ThrowIfFailed(); tokenInformationResponse.ThrowIfFailed();
//var tokenInformation = tokenInformationResponse.Data;
//createdAccount.Address = tokenInformation.Address;
//tokenInformation.AccountId = createdAccount.Id;
await AccountService.CreateAccountAsync(createdAccount, null); await AccountService.CreateAccountAsync(createdAccount, null);
// Sync profile information if supported. // Sync profile information if supported.

View File

@@ -9,7 +9,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.2" />
<PackageReference Include="TimePeriodLibrary.NET" Version="2.1.5" /> <PackageReference Include="TimePeriodLibrary.NET" Version="2.1.5" />
</ItemGroup> </ItemGroup>

View File

@@ -1,33 +1,32 @@
using Wino.Core.Domain.Interfaces; 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[] public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
{
"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[] 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[] public string GmailTokenStoreIdentifier => "WinoCalendarGmailTokenStore";
{
"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";
}
} }

View File

@@ -27,7 +27,7 @@ namespace Wino.Calendar.Services
foreach (var type in providers) foreach (var type in providers)
{ {
providerList.Add(new ProviderDetail(type)); providerList.Add(new ProviderDetail(type, SpecialImapProvider.None));
} }
return providerList; return providerList;

View File

@@ -60,29 +60,6 @@
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain> <UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM64\Debug\</OutputPath> <OutputPath>bin\ARM64\Debug\</OutputPath>
@@ -334,7 +311,7 @@
<Version>6.2.14</Version> <Version>6.2.14</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Win2D.uwp"> <PackageReference Include="Win2D.uwp">
<Version>1.28.0</Version> <Version>1.28.1</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Wino.Core.Domain;
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(bool))]
public partial class BasicTypesJsonContext : JsonSerializerContext;

View File

@@ -7,150 +7,149 @@ using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar; 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; Period = period;
public event EventHandler<ICalendarItem> CalendarItemRemoved; Settings = settings;
public event EventHandler CalendarItemsCleared; RegularEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalRegularEvents);
AllDayEvents = new ReadOnlyObservableCollection<ICalendarItem>(_internalAllDayEvents);
}
private ObservableRangeCollection<ICalendarItem> _internalRegularEvents = []; public bool HasCalendarEvent(AccountCalendar accountCalendar)
private ObservableRangeCollection<ICalendarItem> _internalAllDayEvents = []; => _allItems.Any(x => x.AssignedCalendar.Id == accountCalendar.Id);
public ReadOnlyObservableCollection<ICalendarItem> RegularEvents { get; } public ICalendarItem GetCalendarItem(Guid calendarItemId)
public ReadOnlyObservableCollection<ICalendarItem> AllDayEvents { get; } // TODO: Rename this to include multi-day events. {
public ITimePeriod Period { get; } return _allItems.FirstOrDefault(x => x.Id == calendarItemId);
public CalendarSettings Settings { get; } }
private readonly List<ICalendarItem> _allItems = new List<ICalendarItem>(); public void ClearSelectionStates()
{
public CalendarEventCollection(ITimePeriod period, CalendarSettings settings) foreach (var item in _allItems)
{ {
Period = period; if (item is ICalendarItemViewModel calendarItemViewModel)
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) calendarItemViewModel.IsSelected = false;
{
calendarItemViewModel.IsSelected = false;
}
} }
} }
}
public void FilterByCalendars(IEnumerable<Guid> visibleCalendarIds)
{ public void FilterByCalendars(IEnumerable<Guid> visibleCalendarIds)
foreach (var item in _allItems) {
{ foreach (var item in _allItems)
var collections = GetProperCollectionsForCalendarItem(item); {
var collections = GetProperCollectionsForCalendarItem(item);
foreach (var collection in collections)
{ foreach (var collection in collections)
if (!visibleCalendarIds.Contains(item.AssignedCalendar.Id) && collection.Contains(item)) {
{ if (!visibleCalendarIds.Contains(item.AssignedCalendar.Id) && collection.Contains(item))
RemoveCalendarItemInternal(collection, item, false); {
} RemoveCalendarItemInternal(collection, item, false);
else if (visibleCalendarIds.Contains(item.AssignedCalendar.Id) && !collection.Contains(item)) }
{ else if (visibleCalendarIds.Contains(item.AssignedCalendar.Id) && !collection.Contains(item))
AddCalendarItemInternal(collection, item, false); {
} AddCalendarItemInternal(collection, item, false);
} }
} }
} }
}
private IEnumerable<ObservableRangeCollection<ICalendarItem>> GetProperCollectionsForCalendarItem(ICalendarItem calendarItem)
{ private IEnumerable<ObservableRangeCollection<ICalendarItem>> GetProperCollectionsForCalendarItem(ICalendarItem calendarItem)
// All-day events go to all days. {
// Multi-day events go to both. // All-day events go to all days.
// Anything else goes to regular. // Multi-day events go to both.
// Anything else goes to regular.
if (calendarItem.IsAllDayEvent)
{ if (calendarItem.IsAllDayEvent)
return [_internalAllDayEvents]; {
} return [_internalAllDayEvents];
else if (calendarItem.IsMultiDayEvent) }
{ else if (calendarItem.IsMultiDayEvent)
return [_internalRegularEvents, _internalAllDayEvents]; {
} return [_internalRegularEvents, _internalAllDayEvents];
else }
{ else
return [_internalRegularEvents]; {
} return [_internalRegularEvents];
} }
}
public void AddCalendarItem(ICalendarItem calendarItem)
{ public void AddCalendarItem(ICalendarItem calendarItem)
var collections = GetProperCollectionsForCalendarItem(calendarItem); {
var collections = GetProperCollectionsForCalendarItem(calendarItem);
foreach (var collection in collections)
{ foreach (var collection in collections)
AddCalendarItemInternal(collection, calendarItem); {
} AddCalendarItemInternal(collection, calendarItem);
} }
}
public void RemoveCalendarItem(ICalendarItem calendarItem)
{ public void RemoveCalendarItem(ICalendarItem calendarItem)
var collections = GetProperCollectionsForCalendarItem(calendarItem); {
var collections = GetProperCollectionsForCalendarItem(calendarItem);
foreach (var collection in collections)
{ foreach (var collection in collections)
RemoveCalendarItemInternal(collection, calendarItem); {
} RemoveCalendarItemInternal(collection, calendarItem);
} }
}
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)
{ 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)); if (calendarItem is not ICalendarItemViewModel)
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
collection.Add(calendarItem);
collection.Add(calendarItem);
if (create)
{ if (create)
_allItems.Add(calendarItem); {
} _allItems.Add(calendarItem);
}
CalendarItemAdded?.Invoke(this, calendarItem);
} CalendarItemAdded?.Invoke(this, calendarItem);
}
private void RemoveCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool destroy = true)
{ 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)); if (calendarItem is not ICalendarItemViewModel)
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
collection.Remove(calendarItem);
collection.Remove(calendarItem);
if (destroy)
{ if (destroy)
_allItems.Remove(calendarItem); {
} _allItems.Remove(calendarItem);
}
CalendarItemRemoved?.Invoke(this, calendarItem);
} CalendarItemRemoved?.Invoke(this, calendarItem);
}
public void Clear()
{ public void Clear()
_internalAllDayEvents.Clear(); {
_internalRegularEvents.Clear(); _internalAllDayEvents.Clear();
_allItems.Clear(); _internalRegularEvents.Clear();
_allItems.Clear();
CalendarItemsCleared?.Invoke(this, EventArgs.Empty);
} CalendarItemsCleared?.Invoke(this, EventArgs.Empty);
} }
} }

View File

@@ -2,41 +2,40 @@
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar; 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> get
/// Gets the range of dates that are currently displayed in the collection.
/// </summary>
public DateRange DisplayRange
{ {
get if (Count == 0) return null;
{
if (Count == 0) return null;
var minimumLoadedDate = this[0].CalendarRenderOptions.DateRange.StartDate; var minimumLoadedDate = this[0].CalendarRenderOptions.DateRange.StartDate;
var maximumLoadedDate = this[Count - 1].CalendarRenderOptions.DateRange.EndDate; 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.Collections.Specialized;
using System.ComponentModel; 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> /// <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> /// </summary>
/// <typeparam name="T"></typeparam> public ObservableRangeCollection()
public class ObservableRangeCollection<T> : ObservableCollection<T> : base()
{ {
}
/// <summary> /// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
/// </summary> /// </summary>
public ObservableRangeCollection() /// <param name="collection">collection: The collection from which the elements are copied.</param>
: base() /// <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); 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(); var raiseEvents = false;
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) foreach (var item in collection)
{ {
Items.Add(item); Items.Remove(item);
itemAdded = true; 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))); if (!Items.Remove(changedItems[i]))
OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); {
changedItems.RemoveAt(i); //Can't use a foreach because changedItems is intended to be (carefully) modified
if (changedItems is null) i--;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action)); }
else
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, changedItems: changedItems, startingIndex: startingIndex));
} }
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> /// </summary>
/// MIME header that exists in all the drafts created from Wino. public const string WinoLocalDraftHeader = "X-Wino-Draft-Id";
/// </summary> public const string LocalDraftStartPrefix = "localDraft_";
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 ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey);
public const string ToastActionKey = nameof(ToastActionKey); public const string ToastActionKey = nameof(ToastActionKey);
public const string ClientLogFile = "Client_.log"; public const string ClientLogFile = "Client_.log";
public const string ServerLogFile = "Server_.log"; public const string ServerLogFile = "Server_.log";
public const string LogArchiveFileName = "WinoLogs.zip"; public const string LogArchiveFileName = "WinoLogs.zip";
public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer); public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer);
public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier); public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier);
}
} }

View File

@@ -2,24 +2,23 @@
using SQLite; using SQLite;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Entities.Calendar 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;
/// <summary> public class AccountCalendar : IAccountCalendar
/// Unused for now. {
/// </summary> [PrimaryKey]
public string TextColorHex { get; set; } public Guid Id { get; set; }
public string BackgroundColorHex { get; set; } public Guid AccountId { get; set; }
public string TimeZone { 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 SQLite;
using Wino.Core.Domain.Enums; 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. [PrimaryKey]
public class CalendarEventAttendee public Guid Id { get; set; }
{ public Guid CalendarItemId { get; set; }
[PrimaryKey] public string Name { get; set; }
public Guid Id { get; set; } public string Email { get; set; }
public Guid CalendarItemId { get; set; } public AttendeeStatus AttendenceStatus { get; set; }
public string Name { get; set; } public bool IsOrganizer { get; set; }
public string Email { get; set; } public bool IsOptionalAttendee { get; set; }
public AttendeeStatus AttendenceStatus { get; set; } public string Comment { 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.Enums;
using Wino.Core.Domain.Interfaces; 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})")] [PrimaryKey]
public class CalendarItem : ICalendarItem 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] get
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 return StartDate.AddSeconds(DurationInSeconds);
{
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
};
} }
} }
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 SQLite;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Entities.Calendar namespace Wino.Core.Domain.Entities.Calendar;
{
public class Reminder
{
[PrimaryKey]
public Guid Id { get; set; }
public Guid CalendarItemId { get; set; }
public DateTimeOffset ReminderTime { get; set; } public class Reminder
public CalendarItemReminderType ReminderType { get; set; } {
} [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 System;
using SQLite; 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 System;
using SQLite; 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> /// </summary>
/// Display address of the alias. public string AliasAddress { get; set; }
/// </summary>
public string AliasAddress { get; set; }
/// <summary> /// <summary>
/// Address to be included in Reply-To header when alias is used for sending messages. /// Address to be included in Reply-To header when alias is used for sending messages.
/// </summary> /// </summary>
public string ReplyToAddress { get; set; } public string ReplyToAddress { get; set; }
/// <summary> /// <summary>
/// Whether this alias is the primary alias for the account. /// Whether this alias is the primary alias for the account.
/// </summary> /// </summary>
public bool IsPrimary { get; set; } public bool IsPrimary { get; set; }
/// <summary> /// <summary>
/// Whether the alias is verified by the server. /// Whether the alias is verified by the server.
/// Only Gmail aliases are verified for now. /// Only Gmail aliases are verified for now.
/// Non-verified alias messages might be rejected by SMTP server. /// Non-verified alias messages might be rejected by SMTP server.
/// </summary> /// </summary>
public bool IsVerified { get; set; } public bool IsVerified { get; set; }
/// <summary> /// <summary>
/// Whether this alias is the root alias for the account. /// Whether this alias is the root alias for the account.
/// Root alias means the first alias that was created for the account. /// Root alias means the first alias that was created for the account.
/// It can't be deleted or changed. /// It can't be deleted or changed.
/// </summary> /// </summary>
public bool IsRootAlias { get; set; } public bool IsRootAlias { get; set; }
/// <summary> /// <summary>
/// Optional sender name for the alias. /// Optional sender name for the alias.
/// Falls back to account's sender name if not set when preparing messages. /// Falls back to account's sender name if not set when preparing messages.
/// Used for Gmail only. /// Used for Gmail only.
/// </summary> /// </summary>
public string AliasSenderName { get; set; } public string AliasSenderName { get; set; }
} }
public class MailAccountAlias : RemoteAccountAlias public class MailAccountAlias : RemoteAccountAlias
{ {
/// <summary> /// <summary>
/// Unique Id for the alias. /// Unique Id for the alias.
/// </summary> /// </summary>
[PrimaryKey] [PrimaryKey]
public Guid Id { get; set; } public Guid Id { get; set; }
/// <summary> /// <summary>
/// Account id that this alias is attached to. /// Account id that this alias is attached to.
/// </summary> /// </summary>
public Guid AccountId { get; set; } public Guid AccountId { get; set; }
/// <summary> /// <summary>
/// Root aliases can't be deleted. /// Root aliases can't be deleted.
/// </summary> /// </summary>
public bool CanDelete => !IsRootAlias; 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.Enums;
using Wino.Core.Domain.Models.MailItem; 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>
/// Summary of the parsed MIME messages. /// Unique Id of the mail.
/// Wino will do non-network operations on this table and others from the original MIME.
/// </summary> /// </summary>
public class MailCopy : IMailItem [PrimaryKey]
{ public Guid UniqueId { get; set; }
/// <summary>
/// Unique Id of the mail.
/// </summary>
[PrimaryKey]
public Guid UniqueId { get; set; }
/// <summary> /// <summary>
/// Not unique id of the item. Some operations held on this Id, some on the UniqueId. /// 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. /// Same message can be in different folder. In that case UniqueId is used.
/// </summary> /// </summary>
public string Id { get; set; } public string Id { get; set; }
/// <summary> /// <summary>
/// Folder that this mail belongs to. /// Folder that this mail belongs to.
/// </summary> /// </summary>
public Guid FolderId { get; set; } public Guid FolderId { get; set; }
/// <summary> /// <summary>
/// Conversation id for the mail. /// Conversation id for the mail.
/// </summary> /// </summary>
public string ThreadId { get; set; } public string ThreadId { get; set; }
/// <summary> /// <summary>
/// MIME MessageId if exists. /// MIME MessageId if exists.
/// </summary> /// </summary>
public string MessageId { get; set; } public string MessageId { get; set; }
/// <summary> /// <summary>
/// References header from MIME /// References header from MIME
/// </summary> /// </summary>
public string References { get; set; } public string References { get; set; }
/// <summary> /// <summary>
/// In-Reply-To header from MIME /// In-Reply-To header from MIME
/// </summary> /// </summary>
public string InReplyTo { get; set; } public string InReplyTo { get; set; }
/// <summary> /// <summary>
/// Name for the sender. /// Name for the sender.
/// </summary> /// </summary>
public string FromName { get; set; } public string FromName { get; set; }
/// <summary> /// <summary>
/// Address of the sender. /// Address of the sender.
/// </summary> /// </summary>
public string FromAddress { get; set; } public string FromAddress { get; set; }
/// <summary> /// <summary>
/// Subject of the mail. /// Subject of the mail.
/// </summary> /// </summary>
public string Subject { get; set; } public string Subject { get; set; }
/// <summary> /// <summary>
/// Short preview of the content. /// Short preview of the content.
/// </summary> /// </summary>
public string PreviewText { get; set; } public string PreviewText { get; set; }
/// <summary> /// <summary>
/// Date that represents this mail has been created in provider servers. /// Date that represents this mail has been created in provider servers.
/// Stored always in UTC. /// Stored always in UTC.
/// </summary> /// </summary>
public DateTime CreationDate { get; set; } public DateTime CreationDate { get; set; }
/// <summary> /// <summary>
/// Importance of the mail. /// Importance of the mail.
/// </summary> /// </summary>
public MailImportance Importance { get; set; } public MailImportance Importance { get; set; }
/// <summary> /// <summary>
/// Read status for the mail. /// Read status for the mail.
/// </summary> /// </summary>
public bool IsRead { get; set; } public bool IsRead { get; set; }
/// <summary> /// <summary>
/// Flag status. /// Flag status.
/// Flagged for Outlook. /// Flagged for Outlook.
/// Important for Gmail. /// Important for Gmail.
/// </summary> /// </summary>
public bool IsFlagged { get; set; } public bool IsFlagged { get; set; }
/// <summary> /// <summary>
/// To support Outlook. /// To support Outlook.
/// Gmail doesn't use it. /// Gmail doesn't use it.
/// </summary> /// </summary>
public bool IsFocused { get; set; } public bool IsFocused { get; set; }
/// <summary> /// <summary>
/// Whether mail has attachments included or not. /// Whether mail has attachments included or not.
/// </summary> /// </summary>
public bool HasAttachments { get; set; } public bool HasAttachments { get; set; }
/// <summary> /// <summary>
/// Assigned draft id. /// Assigned draft id.
/// </summary> /// </summary>
public string DraftId { get; set; } public string DraftId { get; set; }
/// <summary> /// <summary>
/// Whether this mail is only created locally. /// Whether this mail is only created locally.
/// </summary> /// </summary>
[Ignore] [Ignore]
public bool IsLocalDraft => !string.IsNullOrEmpty(DraftId) && DraftId.StartsWith(Constants.LocalDraftStartPrefix); public bool IsLocalDraft => !string.IsNullOrEmpty(DraftId) && DraftId.StartsWith(Constants.LocalDraftStartPrefix);
/// <summary> /// <summary>
/// Whether this copy is draft or not. /// Whether this copy is draft or not.
/// </summary> /// </summary>
public bool IsDraft { get; set; } public bool IsDraft { get; set; }
/// <summary> /// <summary>
/// File id that this mail is assigned to. /// File id that this mail is assigned to.
/// This Id is immutable. It's used to find the file in the file system. /// 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. /// Even after mapping local draft to remote draft, it will not change.
/// </summary> /// </summary>
public Guid FileId { get; set; } public Guid FileId { get; set; }
/// <summary> /// <summary>
/// Folder that this mail is assigned to. /// Folder that this mail is assigned to.
/// Warning: This field is not populated by queries. /// Warning: This field is not populated by queries.
/// Services or View Models are responsible for populating this field. /// Services or View Models are responsible for populating this field.
/// </summary> /// </summary>
[Ignore] [Ignore]
public MailItemFolder AssignedFolder { get; set; } public MailItemFolder AssignedFolder { get; set; }
/// <summary> /// <summary>
/// Account that this mail is assigned to. /// Account that this mail is assigned to.
/// Warning: This field is not populated by queries. /// Warning: This field is not populated by queries.
/// Services or View Models are responsible for populating this field. /// Services or View Models are responsible for populating this field.
/// </summary> /// </summary>
[Ignore] [Ignore]
public MailAccount AssignedAccount { get; set; } public MailAccount AssignedAccount { get; set; }
/// <summary> /// <summary>
/// Contact information of the sender if exists. /// Contact information of the sender if exists.
/// Warning: This field is not populated by queries. /// Warning: This field is not populated by queries.
/// Services or View Models are responsible for populating this field. /// Services or View Models are responsible for populating this field.
/// </summary> /// </summary>
[Ignore] [Ignore]
public AccountContact SenderContact { get; set; } public AccountContact SenderContact { get; set; }
public IEnumerable<Guid> GetContainingIds() => [UniqueId]; public IEnumerable<Guid> GetContainingIds() => [UniqueId];
public override string ToString() => $"{Subject} <-> {Id}"; public override string ToString() => $"{Subject} <-> {Id}";
}
} }

View File

@@ -5,71 +5,70 @@ using SQLite;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Folders; 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}")] [PrimaryKey]
public class MailItemFolder : IMailItemFolder 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] if (SpecialFolderType == type)
public Guid Id { get; set; } return true;
public string RemoteFolderId { get; set; } foreach (var child in ChildFolders)
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)
{ {
if (SpecialFolderType == type) if (child.SpecialFolderType == type)
return true;
foreach (var child in ChildFolders)
{ {
if (child.SpecialFolderType == type) return true;
{ }
return true; else
} {
else return child.ContainsSpecialFolderType(type);
{
return child.ContainsSpecialFolderType(type);
}
} }
return false;
} }
public static MailItemFolder CreateMoreFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.More, FolderName = Translator.MoreFolderNameOverride }; return false;
public static MailItemFolder CreateCategoriesFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.Category, FolderName = Translator.CategoriesFolderNameOverride };
public override string ToString() => FolderName;
} }
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 System;
using SQLite; using SQLite;
namespace Wino.Core.Domain.Entities.Mail namespace Wino.Core.Domain.Entities.Mail;
{
public class MergedInbox
{
[PrimaryKey]
public Guid Id { get; set; }
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 System.Collections.Generic;
using SQLite; 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> /// <summary>
/// Back storage for simple name-address book. /// E-mail address of the contact.
/// These values will be inserted during MIME fetch.
/// </summary> /// </summary>
[PrimaryKey]
public string Address { get; set; }
// TODO: This can easily evolve to Contact store, just like People app in Windows 10/11. /// <summary>
// Do it. /// Display name of the contact.
public class AccountContact : IEquatable<AccountContact> /// </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> return Equals(obj as AccountContact);
/// E-mail address of the contact. }
/// </summary>
[PrimaryKey]
public string Address { get; set; }
/// <summary> public bool Equals(AccountContact other)
/// Display name of the contact. {
/// </summary> return other is not null &&
public string Name { get; set; } Address == other.Address &&
Name == other.Name;
}
/// <summary> public override int GetHashCode()
/// Base64 encoded profile image of the contact. {
/// </summary> return HashCode.Combine(Address, Name);
public string Base64ContactPicture { get; set; } }
/// <summary> public static bool operator ==(AccountContact left, AccountContact right)
/// All registered accounts have their contacts registered as root. {
/// Root contacts must not be overridden by any configuration. return EqualityComparer<AccountContact>.Default.Equals(left, right);
/// They are created on account creation. }
/// </summary>
public bool IsRootContact { get; set; }
/// <summary> public static bool operator !=(AccountContact left, AccountContact right)
/// Short display name of the contact. {
/// Eather Name or Address. return !(left == right);
/// </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);
}
} }
} }

View File

@@ -1,53 +1,74 @@
using System; using System;
using System.Collections.Generic;
using SQLite; using SQLite;
using Wino.Core.Domain.Enums; 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] // Printout the public connection properties.
public Guid Id { get; set; }
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> return connectionProperties;
/// 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; }
} }
} }

View File

@@ -3,103 +3,108 @@ using SQLite;
using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums; 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> /// <summary>
/// Given name of the account in Wino. /// Given name of the account in Wino.
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// TODO: Display name of the authenticated user/account. /// TODO: Display name of the authenticated user/account.
/// API integrations will query this value from the API. /// API integrations will query this value from the API.
/// IMAP is populated by user on setup dialog. /// IMAP is populated by user on setup dialog.
/// </summary> /// </summary>
public string SenderName { get; set; } public string SenderName { get; set; }
/// <summary> /// <summary>
/// Account e-mail address. /// Account e-mail address.
/// </summary> /// </summary>
public string Address { get; set; } public string Address { get; set; }
/// <summary> /// <summary>
/// Provider type of the account. Outlook,Gmail etc... /// Provider type of the account. Outlook,Gmail etc...
/// </summary> /// </summary>
public MailProviderType ProviderType { get; set; } public MailProviderType ProviderType { get; set; }
/// <summary> /// <summary>
/// For tracking mail change delta. /// For tracking mail change delta.
/// Gmail : historyId /// Gmail : historyId
/// Outlook: deltaToken /// Outlook: deltaToken
/// </summary> /// </summary>
public string SynchronizationDeltaIdentifier { get; set; } public string SynchronizationDeltaIdentifier { get; set; }
/// <summary> /// <summary>
/// For tracking calendar change delta. /// For tracking calendar change delta.
/// Gmail: It's per-calendar, so unused. /// Gmail: It's per-calendar, so unused.
/// Outlook: deltaLink /// Outlook: deltaLink
/// </summary> /// </summary>
public string CalendarSynchronizationDeltaIdentifier { get; set; } public string CalendarSynchronizationDeltaIdentifier { get; set; }
/// <summary> /// <summary>
/// TODO: Gets or sets the custom account identifier color in hex. /// TODO: Gets or sets the custom account identifier color in hex.
/// </summary> /// </summary>
public string AccountColorHex { get; set; } public string AccountColorHex { get; set; }
/// <summary> /// <summary>
/// Base64 encoded profile picture of the account. /// Base64 encoded profile picture of the account.
/// </summary> /// </summary>
public string Base64ProfilePictureData { get; set; } public string Base64ProfilePictureData { get; set; }
/// <summary> /// <summary>
/// Gets or sets the listing order of the account in the accounts list. /// Gets or sets the listing order of the account in the accounts list.
/// </summary> /// </summary>
public int Order { get; set; } public int Order { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether the account has any reason for an interactive user action to fix continue operating. /// Gets or sets whether the account has any reason for an interactive user action to fix continue operating.
/// </summary> /// </summary>
public AccountAttentionReason AttentionReason { get; set; } public AccountAttentionReason AttentionReason { get; set; }
/// <summary> /// <summary>
/// Gets or sets the id of the merged inbox this account belongs to. /// Gets or sets the id of the merged inbox this account belongs to.
/// </summary> /// </summary>
public Guid? MergedInboxId { get; set; } public Guid? MergedInboxId { get; set; }
/// <summary> /// <summary>
/// Contains the merged inbox this account belongs to. /// Gets or sets the additional IMAP provider assignment for the account.
/// Ignored for all SQLite operations. /// Providers that use IMAP as a synchronizer but have special requirements.
/// </summary> /// </summary>
[Ignore] public SpecialImapProvider SpecialImapProvider { get; set; }
public MergedInbox MergedInbox { get; set; }
/// <summary> /// <summary>
/// Populated only when account has custom server information. /// Contains the merged inbox this account belongs to.
/// </summary> /// Ignored for all SQLite operations.
/// </summary>
[Ignore]
public MergedInbox MergedInbox { get; set; }
[Ignore] /// <summary>
public CustomServerInformation ServerInformation { get; set; } /// Populated only when account has custom server information.
/// </summary>
/// <summary> [Ignore]
/// Account preferences. public CustomServerInformation ServerInformation { get; set; }
/// </summary>
[Ignore]
public MailAccountPreferences Preferences { get; set; }
/// <summary> /// <summary>
/// Gets whether the account can perform ProfileInformation sync type. /// Account preferences.
/// </summary> /// </summary>
public bool IsProfileInfoSyncSupported => ProviderType == MailProviderType.Outlook || ProviderType == MailProviderType.Office365 || ProviderType == MailProviderType.Gmail; [Ignore]
public MailAccountPreferences Preferences { get; set; }
/// <summary> /// <summary>
/// Gets whether the account can perform AliasInformation sync type. /// Gets whether the account can perform ProfileInformation sync type.
/// </summary> /// </summary>
public bool IsAliasSyncSupported => ProviderType == MailProviderType.Gmail; 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;
} }

View File

@@ -1,54 +1,53 @@
using System; using System;
using SQLite; 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> /// <summary>
/// Id of the account in MailAccount table. /// Id of the account in MailAccount table.
/// </summary> /// </summary>
public Guid AccountId { get; set; } public Guid AccountId { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether sent draft messages should be appended to the sent folder. /// Gets or sets whether sent draft messages should be appended to the sent folder.
/// Some IMAP servers do this automatically, some don't. /// Some IMAP servers do this automatically, some don't.
/// It's disabled by default. /// It's disabled by default.
/// </summary> /// </summary>
public bool ShouldAppendMessagesToSentFolder { get; set; } public bool ShouldAppendMessagesToSentFolder { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether the notifications are enabled for the account. /// Gets or sets whether the notifications are enabled for the account.
/// </summary> /// </summary>
public bool IsNotificationsEnabled { get; set; } public bool IsNotificationsEnabled { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether the account has Focused inbox support. /// Gets or sets whether the account has Focused inbox support.
/// Null if the account provider type doesn't support Focused inbox. /// Null if the account provider type doesn't support Focused inbox.
/// </summary> /// </summary>
public bool? IsFocusedInboxEnabled { get; set; } public bool? IsFocusedInboxEnabled { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether signature should be appended automatically. /// Gets or sets whether signature should be appended automatically.
/// </summary> /// </summary>
public bool IsSignatureEnabled { get; set; } public bool IsSignatureEnabled { get; set; }
/// <summary> /// <summary>
/// Gets or sets whether this account's unread items should be included in taskbar badge. /// Gets or sets whether this account's unread items should be included in taskbar badge.
/// </summary> /// </summary>
public bool IsTaskbarBadgeEnabled { get; set; } = true; public bool IsTaskbarBadgeEnabled { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets signature for new messages. Null if signature is not needed. /// Gets or sets signature for new messages. Null if signature is not needed.
/// </summary> /// </summary>
public Guid? SignatureIdForNewMessages { get; set; } public Guid? SignatureIdForNewMessages { get; set; }
/// <summary> /// <summary>
/// Gets or sets signature for following messages. Null if signature is not needed. /// Gets or sets signature for following messages. Null if signature is not needed.
/// </summary> /// </summary>
public Guid? SignatureIdForFollowingMessages { get; set; } 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,
None, MissingSystemFolderConfiguration
InvalidCredentials,
MissingSystemFolderConfiguration
}
} }

View File

@@ -0,0 +1,7 @@
namespace Wino.Core.Domain.Enums;
public enum AccountCacheResetReason
{
AccountRemoval,
ExpiredCache
}

View File

@@ -1,17 +1,16 @@
namespace Wino.Core.Domain.Enums namespace Wino.Core.Domain.Enums;
public enum AccountCreationDialogState
{ {
public enum AccountCreationDialogState Idle,
{ SigningIn,
Idle, PreparingFolders,
SigningIn, Completed,
PreparingFolders, ManuelSetupWaiting,
Completed, TestingConnection,
ManuelSetupWaiting, AutoDiscoverySetup,
TestingConnection, AutoDiscoveryInProgress,
AutoDiscoverySetup, FetchingProfileInformation,
AutoDiscoveryInProgress, Canceled,
FetchingProfileInformation, FetchingEvents
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> Idle,
/// Indicates the state of synchronizer. ExecutingRequests,
/// </summary> Synchronizing
public enum AccountSynchronizerState
{
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,
None, Deutsch,
English, Russian,
Deutsch, Turkish,
Russian, Polish,
Turkish, Czech,
Polish, Chinese,
Czech, Spanish,
Chinese, French,
Spanish, Indonesian,
French, Greek,
Indonesian, PortugeseBrazil,
Greek, Italian,
PortugeseBrazil, Romanian
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,
System, Custom,
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,
Default, Dark
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,
NeedsAction, Tentative,
Accepted, Declined
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,
Day, WorkWeek,
Week, Month,
WorkWeek, Year
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> User,
/// Trigger to load more data. App
/// </summary>
public enum CalendarInitInitiative
{
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,
Daily, Monthly,
Weekly, Yearly
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,
NotResponded, Tentative,
Confirmed, Cancelled,
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,
Default, Private,
Public, Confidential
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> Replace,
/// Which way in time to load more data for calendar. Previous,
/// </summary> Next
public enum CalendarLoadDirection
{
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.
ExecuteRequests, // Execute all requests in the queue. CalendarEvents, // Sync all events for all calendars.
CalendarMetadata, // Sync calendar metadata. SingleCalendar, // Sync events for only specified calendars.
CalendarEvents, // Sync all events for all calendars. UpdateProfile // Update profile information only.
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,
MailMarkAs, MailHardDelete,
MailChangeFlag, MailMove,
MailHardDelete, MailAlwaysMoveTo,
MailMove, MailChangeFocused,
MailAlwaysMoveTo, MailArchive,
MailChangeFocused, MailUnarchive,
MailArchive, FolderMarkAsRead,
MailUnarchive, FolderDelete,
FolderMarkAsRead, FolderEmpty,
FolderDelete, FolderRename,
FolderEmpty, CreateNewDraft,
FolderRename, CreateReplyDraft,
CreateNewDraft, CreateForwardDraft,
CreateReplyDraft, DiscardDraft,
CreateForwardDraft, SendDraft,
DiscardDraft, FetchSingleItem
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,
Empty, ReplyAll,
Reply, Forward
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,
All, Flagged,
Unread, Mentions,
Flagged, Files
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> None,
/// Defines all possible folder operations that can be done. Pin,
/// Available values for each folder is returned by IContextMenuProvider Unpin,
/// that integrators hold. MarkAllAsRead,
/// </summary> DontSync,
public enum FolderOperation Empty,
{ Rename,
None, Delete,
Pin, Move,
Unpin, TurnOffNotifications,
MarkAllAsRead, CreateSubFolder,
DontSync, Seperator
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,
Auto, NormalPassword,
None, EncryptedPassword,
NormalPassword, Ntlm,
EncryptedPassword, CramMd5,
Ntlm, DigestMd5
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,
Auto, StartTls,
None, SslTls
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,
Information, Warning,
Success, Error
Warning,
Error
}
} }

View File

@@ -0,0 +1,7 @@
namespace Wino.Core.Domain.Enums;
public enum InvalidMoveTargetReason
{
NonMoveTarget, // This folder does not allow moving mails.
MultipleAccounts // Multiple mails from different accounts cannot be moved.
}

View File

@@ -1,16 +1,15 @@
namespace Wino.Core.Domain.Enums namespace Wino.Core.Domain.Enums;
public enum MailAttachmentType
{ {
public enum MailAttachmentType None,
{ Executable,
None, Image,
Executable, Audio,
Image, Video,
Audio, PDF,
Video, HTML,
PDF, RarArchive,
HTML, Archive,
RarArchive, Other
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,
Low, High
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,
Spacious, Compact,
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,
WhenSelected, AfterDelay
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. MarkRead,
public enum MailSynchronizerOperation Move,
{ Delete, // Hard delete.
MarkRead, CreateDraft,
Move, Send,
Delete, // Hard delete. ChangeFlag,
CreateDraft, AlwaysMoveTo,
Send, MoveToFocused,
ChangeFlag, Archive,
AlwaysMoveTo, }
MoveToFocused,
Archive, public enum FolderSynchronizerOperation
} {
RenameFolder,
public enum FolderSynchronizerOperation EmptyFolder,
{ MarkFolderRead,
RenameFolder, }
EmptyFolder,
MarkFolderRead, // UI requests
} public enum MailOperation
{
// UI requests None,
public enum MailOperation Archive,
{ UnArchive,
None, SoftDelete,
Archive, HardDelete,
UnArchive, Move,
SoftDelete, MoveToJunk,
HardDelete, MoveToFocused,
Move, MoveToOther,
MoveToJunk, AlwaysMoveToOther,
MoveToFocused, AlwaysMoveToFocused,
MoveToOther, SetFlag,
AlwaysMoveToOther, ClearFlag,
AlwaysMoveToFocused, MarkAsRead,
SetFlag, MarkAsUnread,
ClearFlag, MarkAsNotJunk,
MarkAsRead, Seperator,
MarkAsUnread, Ignore,
MarkAsNotJunk, Reply,
Seperator, ReplyAll,
Ignore, Zoom,
Reply, SaveAs,
ReplyAll, Find,
Zoom, Forward,
SaveAs, DarkEditor,
Find, LightEditor,
Forward, Print,
DarkEditor, ViewMessageSource,
LightEditor, DiscardLocalDraft,
Print, Navigate // For toast activation
ViewMessageSource,
DiscardLocalDraft,
Navigate // For toast activation
}
} }

View File

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

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.
// Shared FoldersOnly, // Only synchronize folder metadata.
UpdateProfile, // Only update profile information InboxOnly, // Only Inbox, Sent, Draft and Deleted folders.
ExecuteRequests, // Run the queued requests, and then synchronize if needed. CustomFolders, // Only sync folders that are specified in the options.
FoldersOnly, // Only synchronize folder metadata. FullFolders, // Synchronize all folders. This won't update profile or alias information.
InboxOnly, // Only Inbox, Sent and Draft folders. Alias, // Only update alias information
CustomFolders, // Only sync folders that are specified in the options. IMAPIdle // Idle client triggered synchronization.
FullFolders, // Synchronize all folders. This won't update profile or alias information.
Alias, // Only update alias information
}
} }

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> Move,
/// Defines the potential reasons for picking folder in the folder picking dialog. SpecialFolder,
/// </summary> Any
public enum PickFolderReason
{
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,
Abandoned, Failed,
Canceled, Submitted
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> MinimizedTray, // Still runs, tray icon is visible.
/// What should happen to server app when the client is terminated. Invisible, // Still runs, tray icon is invisible.
/// </summary> Terminate // Server is terminated as Wino terminates.
public enum ServerBackgroundMode
{
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,
Inbox, Important,
Starred, Sent,
Important, Draft,
Sent, Archive,
Draft, Deleted,
Archive, Junk,
Deleted, Chat,
Junk, Category,
Chat, Unread,
Category, Forums,
Unread, Updates,
Forums, Personal,
Updates, Promotions,
Personal, Social,
Promotions, Other,
Social, More
Other,
More
}
} }

View File

@@ -0,0 +1,8 @@
namespace Wino.Core.Domain.Enums;
public enum SpecialImapProvider
{
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,
Enabled, DisabledByUser,
Disabled, DisabledByPolicy,
DisabledByUser, Fatal
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 purchase request succeeded. // Summary:
Succeeded, // The current user has already purchased the specified app or add-on.
// AlreadyPurchased,
// Summary: //
// The current user has already purchased the specified app or add-on. // Summary:
AlreadyPurchased, // The purchase request did not succeed.
// NotPurchased,
// 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.
Success, // All succeeded. Failed // Exception.
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> Client,
/// Enumeration for the source of synchronization. Server
/// Right now it can either be from the client or the server.
/// </summary>
public enum SynchronizationSource
{
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,
Unknown, Calendar
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,
Information, Error,
Warning, Question
Error,
Question
}
} }

View File

@@ -1,34 +1,33 @@
namespace Wino.Core.Domain.Enums 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,
// Calendar /// <summary>
CalendarPage, /// All registered views.
CalendarSettingsPage, /// </summary>
EventDetailsPage 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,
None, Connected,
Connecting, Disconnected,
Connected, Failed
Disconnected,
Failed
}
} }

View File

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

View File

@@ -1,19 +1,18 @@
using System; using System;
using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Exceptions 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;
}
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; using System;
namespace Wino.Core.Domain.Exceptions namespace Wino.Core.Domain.Exceptions;
{
/// <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) /// <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; using System;
namespace Wino.Core.Domain.Exceptions namespace Wino.Core.Domain.Exceptions;
{
/// <summary> /// <summary>
/// An exception thrown when the background task registration is failed. /// An exception thrown when the background task registration is failed.
/// </summary> /// </summary>
public class BackgroundTaskRegistrationFailedException : Exception { } public class BackgroundTaskRegistrationFailedException : Exception { }
}

View File

@@ -1,11 +1,10 @@
using System; 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; 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

@@ -0,0 +1,3 @@
namespace Wino.Core.Domain.Exceptions;
public class GmailServiceDisabledException : System.Exception { }

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 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; using Wino.Core.Domain.Models.AutoDiscovery;
namespace Wino.Core.Domain.Exceptions namespace Wino.Core.Domain.Exceptions;
{
public class ImapConnectionFailedPackage
{
public ImapConnectionFailedPackage(string errorMessage, string protocolLog, AutoDiscoverySettings settings)
{
ErrorMessage = errorMessage;
ProtocolLog = protocolLog;
Settings = settings;
}
public AutoDiscoverySettings Settings { get; } public class ImapConnectionFailedPackage
public string ErrorMessage { get; set; } {
public string ProtocolLog { get; } 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

@@ -0,0 +1,9 @@
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;
Issuer = issuer; ValidFromDateString = validFromDateString;
ExpirationDateString = expirationDateString;
ValidFromDateString = validFromDateString;
}
public string Issuer { get; set; }
public string ExpirationDateString { get; set; }
public string ValidFromDateString { get; set; }
} }
public string Issuer { get; set; }
public string ExpirationDateString { get; set; }
public string ValidFromDateString { get; set; }
} }

View File

@@ -1,6 +1,9 @@
using System; using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Exceptions namespace Wino.Core.Domain.Exceptions;
public class InvalidMoveTargetException(InvalidMoveTargetReason reason) : Exception
{ {
public class InvalidMoveTargetException : Exception { } public InvalidMoveTargetReason Reason { get; } = reason;
} }

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

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