Initial commit.

This commit is contained in:
Burak Kaan Köse
2024-04-18 01:44:37 +02:00
parent 524ea4c0e1
commit 12d3814626
671 changed files with 77295 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Accounts
{
public class ImapAuthenticationMethodModel(ImapAuthenticationMethod imapAuthenticationMethod, string displayName)
{
public ImapAuthenticationMethod ImapAuthenticationMethod { get; } = imapAuthenticationMethod;
public string DisplayName { get; } = displayName;
}
}

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Accounts
{
public class ImapConnectionSecurityModel(ImapConnectionSecurity imapConnectionSecurity, string displayName)
{
public ImapConnectionSecurity ImapConnectionSecurity { get; } = imapConnectionSecurity;
public string DisplayName { get; } = displayName;
}
}

View File

@@ -0,0 +1,47 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Accounts
{
public class ProviderDetail : IProviderDetail
{
public MailProviderType Type { get; }
public string Name { get; }
public string Description { get; }
public string ProviderImage => $"ms-appx:///Assets/Providers/{Type}.png";
public bool IsSupported => Type == MailProviderType.Outlook || Type == MailProviderType.Gmail || Type == MailProviderType.IMAP4;
public ProviderDetail(MailProviderType type)
{
Type = type;
switch (Type)
{
case MailProviderType.Outlook:
Name = "Outlook";
Description = "Outlook.com, Live.com, Hotmail, MSN";
break;
case MailProviderType.Office365:
Name = "Office 365";
Description = "Office 365, Exchange";
break;
case MailProviderType.Gmail:
Name = "Gmail";
Description = Translator.ProviderDetail_Gmail_Description;
break;
case MailProviderType.Yahoo:
Name = "Yahoo";
Description = "Yahoo Mail";
break;
case MailProviderType.IMAP4:
Name = Translator.ProviderDetail_IMAP_Title;
Description = Translator.ProviderDetail_IMAP_Description;
break;
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
namespace Wino.Core.Domain.Models.Authentication
{
public class TokenInformationBase
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
/// <summary>
/// UTC date for token expiration.
/// </summary>
public DateTime ExpiresAt { get; set; }
/// <summary>
/// Gets the value indicating whether the token is expired or not.
/// </summary>
public bool IsExpired => DateTime.UtcNow >= ExpiresAt;
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Wino.Core.Domain.Exceptions;
namespace Wino.Core.Domain.Models.Authorization
{
public class GoogleAuthorizationRequest
{
public const string RedirectUri = "google.pw.oauth2:/oauth2redirect";
const string authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
const string CodeChallangeMethod = "S256";
public GoogleAuthorizationRequest(string state, string codeVerifier, string codeChallange)
{
State = state;
CodeVerifier = codeVerifier;
CodeChallange = codeChallange;
}
// Pre
public string State { get; set; }
public string CodeVerifier { get; set; }
public string CodeChallange { get; set; }
public string ClientId { get; set; }
// Post
public string AuthorizationCode { get; set; }
public string BuildRequest(string clientId)
{
ClientId = clientId;
// Creates the OAuth 2.0 authorization request.
return string.Format("{0}?response_type=code&scope=https://mail.google.com/ https://www.googleapis.com/auth/gmail.labels&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
authorizationEndpoint,
Uri.EscapeDataString(RedirectUri),
ClientId,
State,
CodeChallange,
CodeChallangeMethod);
}
public void ValidateAuthorizationCode(Uri callbackUri)
{
if (callbackUri == null)
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthCallbackNull);
string queryString = callbackUri.Query;
Dictionary<string, string> queryStringParams = queryString.Substring(1).Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
if (queryStringParams.ContainsKey("error"))
throw new GoogleAuthenticationException(string.Format(Translator.Exception_GoogleAuthError, queryStringParams["error"]));
if (!queryStringParams.ContainsKey("code") || !queryStringParams.ContainsKey("state"))
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthCorruptedCode + queryString);
// Gets the Authorization code & state
string code = queryStringParams["code"];
string incomingState = queryStringParams["state"];
// Compares the receieved state to the expected value, to ensure that
// this app made the request which resulted in authorization
if (incomingState != State)
throw new GoogleAuthenticationException(string.Format(Translator.Exception_GoogleAuthInvalidResponse, incomingState));
AuthorizationCode = code;
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using Wino.Core.Domain.Exceptions;
namespace Wino.Core.Domain.Models.Authorization
{
public class GoogleTokenizationRequest
{
public GoogleTokenizationRequest(GoogleAuthorizationRequest authorizationRequest)
{
if (authorizationRequest == null)
throw new GoogleAuthenticationException("Authorization request is empty.");
AuthorizationRequest = authorizationRequest;
if (string.IsNullOrEmpty(AuthorizationRequest.AuthorizationCode))
throw new GoogleAuthenticationException("Authorization request has empty code.");
}
public GoogleAuthorizationRequest AuthorizationRequest { get; set; }
public string BuildRequest()
{
return string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&scope=&grant_type=authorization_code",
AuthorizationRequest.AuthorizationCode, Uri.EscapeDataString(GoogleAuthorizationRequest.RedirectUri), AuthorizationRequest.ClientId, AuthorizationRequest.CodeVerifier);
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
namespace Wino.Core.Domain.Models.AutoDiscovery
{
public class AutoDiscoveryConnectionTestFailedPackage
{
public AutoDiscoveryConnectionTestFailedPackage(AutoDiscoverySettings settings, Exception error)
{
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
Error = error ?? throw new ArgumentNullException(nameof(error));
}
public AutoDiscoveryConnectionTestFailedPackage(Exception error)
{
Error = error ?? throw new ArgumentNullException(nameof(error));
}
public AutoDiscoverySettings Settings { get; set; }
public Exception Error { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace Wino.Core.Domain.Models.AutoDiscovery
{
public class AutoDiscoveryMinimalSettings
{
public string DisplayName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace Wino.Core.Domain.Models.AutoDiscovery
{
public class AutoDiscoveryProviderSetting
{
[JsonProperty("protocol")]
public string Protocol { get; set; }
[JsonProperty("address")]
public string Address { get; set; }
[JsonProperty("port")]
public int Port { get; set; }
[JsonProperty("secure")]
public string Secure { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Models.AutoDiscovery
{
public class AutoDiscoverySettings
{
[JsonProperty("domain")]
public string Domain { get; set; }
[JsonProperty("password")]
public string Password { get; set; }
[JsonProperty("settings")]
public List<AutoDiscoveryProviderSetting> Settings { get; set; }
/// <summary>
/// Gets whether this domain requires additional steps for password like app-specific password or sth.
/// </summary>
public bool IsPasswordSupportLinkAvailable => !string.IsNullOrEmpty(Password) && Uri.TryCreate(Password, UriKind.Absolute, out _);
public AutoDiscoveryMinimalSettings UserMinimalSettings { get; set; }
public CustomServerInformation ToServerInformation()
{
var imapSettings = GetImapSettings();
var smtpSettings = GetSmptpSettings();
if (imapSettings == null || smtpSettings == null) return null;
bool imapRequiresSSL = imapSettings.Secure == "SSL";
bool smtpRequiresSSL = smtpSettings.Secure == "SSL";
string imapUrl = imapSettings.Address;
string smtpUrl = smtpSettings.Address;
string imapUsername = imapSettings.Username;
string smtpUsername = smtpSettings.Username;
int imapPort = imapSettings.Port;
int smtpPort = smtpSettings.Port;
var serverInfo = new CustomServerInformation
{
Id = Guid.NewGuid(),
DisplayName = UserMinimalSettings.DisplayName,
Address = UserMinimalSettings.Email,
IncomingServerPassword = UserMinimalSettings.Password,
OutgoingServerPassword = UserMinimalSettings.Password,
IncomingRequiresSSL = imapRequiresSSL,
OutgoingRequresSSL = smtpRequiresSSL,
IncomingServer = imapUrl,
OutgoingServer = smtpUrl,
IncomingServerPort = imapPort.ToString(),
OutgoingServerPort = smtpPort.ToString(),
IncomingServerType = Enums.CustomIncomingServerType.IMAP4,
IncomingServerUsername = imapUsername,
OutgoingServerUsername = smtpUsername
};
return serverInfo;
}
public AutoDiscoveryProviderSetting GetImapSettings()
=> Settings?.Find(a => a.Protocol == "IMAP");
public AutoDiscoveryProviderSetting GetSmptpSettings()
=> Settings?.Find(a => a.Protocol == "SMTP");
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Comparers
{
public class DateComparer : IComparer<IMailItem>, IEqualityComparer
{
public int Compare(IMailItem x, IMailItem y)
{
return DateTime.Compare(y.CreationDate, x.CreationDate);
}
public new bool Equals(object x, object y)
{
if (x is IMailItem firstItem && y is IMailItem secondItem)
{
return firstItem.Equals(secondItem);
}
return false;
}
public int GetHashCode(object obj) => (obj as IMailItem).GetHashCode();
public DateComparer()
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.Comparers
{
/// <summary>
/// Used to insert date grouping into proper place in Reader page.
/// </summary>
public class DateTimeComparer : IComparer<DateTime>
{
public int Compare(DateTime x, DateTime y)
{
return DateTime.Compare(y, x);
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Models.Comparers
{
public class FolderNameComparer : IComparer<MailItemFolder>
{
public int Compare(MailItemFolder x, MailItemFolder y)
{
return x.FolderName.CompareTo(y.FolderName);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Comparers
{
public class ListItemComparer : IComparer<object>
{
public bool SortByName { get; set; }
public DateComparer DateComparer = new DateComparer();
public readonly NameComparer NameComparer = new NameComparer();
public int Compare(object x, object y)
{
if (x is IMailItem xMail && y is IMailItem yMail)
{
var itemComparer = GetItemComparer();
return itemComparer.Compare(xMail, yMail);
}
else if (x is DateTime dateX && y is DateTime dateY)
return DateTime.Compare(dateY, dateX);
else if (x is string stringX && y is string stringY)
return stringY.CompareTo(stringX);
return 0;
}
public IComparer<IMailItem> GetItemComparer()
{
if (SortByName)
return NameComparer;
else
return DateComparer;
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Comparers
{
public class NameComparer : IComparer<IMailItem>
{
public int Compare(IMailItem x, IMailItem y)
{
return string.Compare(x.FromName, y.FromName);
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Folders
{
/// <summary>
/// Grouped folder information for the menu for given account.
/// </summary>
public class AccountFolderTree
{
public MailAccount Account { get; }
public List<IMailItemFolder> Folders { get; set; } = new List<IMailItemFolder>();
public AccountFolderTree(MailAccount account)
{
Account = account;
}
public bool HasSpecialTypeFolder(SpecialFolderType type)
{
foreach (var folderStructure in Folders)
{
bool hasSpecialFolder = folderStructure.ContainsSpecialFolderType(type);
if (hasSpecialFolder)
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,14 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Menus;
namespace Wino.Core.Domain.Models.Folders
{
public class FolderOperationMenuItem : MenuOperationItemBase<FolderOperation>, IMenuOperation
{
protected FolderOperationMenuItem(FolderOperation operation, bool isEnabled) : base(operation, isEnabled) { }
public static FolderOperationMenuItem Create(FolderOperation operation, bool isEnabled = true)
=> new FolderOperationMenuItem(operation, isEnabled);
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Folders
{
public interface IMailItemFolder
{
string BackgroundColorHex { get; set; }
string DeltaToken { get; set; }
string FolderName { get; set; }
long HighestModeSeq { get; set; }
Guid Id { get; set; }
bool IsHidden { get; set; }
bool IsSticky { get; set; }
bool IsSynchronizationEnabled { get; set; }
bool IsSystemFolder { get; set; }
DateTime? LastSynchronizedDate { get; set; }
Guid MailAccountId { get; set; }
string ParentRemoteFolderId { get; set; }
string RemoteFolderId { get; set; }
SpecialFolderType SpecialFolderType { get; set; }
string TextColorHex { get; set; }
uint UidValidity { get; set; }
List<IMailItemFolder> ChildFolders { get; set; }
bool IsMoveTarget { get; }
bool ShowUnreadCount { get; set; }
bool ContainsSpecialFolderType(SpecialFolderType type);
}
}

View File

@@ -0,0 +1,40 @@
using System.Collections.Specialized;
using System.Linq;
using MimeKit;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.MailItem
{
public class DraftCreationOptions
{
public MimeMessage ReferenceMimeMessage { get; set; }
public MailCopy ReferenceMailCopy { get; set; }
public DraftCreationReason Reason { get; set; }
#region Mailto Protocol Related Stuff
public const string MailtoSubjectParameterKey = "subject";
public const string MailtoBodyParameterKey = "body";
public const string MailtoToParameterKey = "mailto";
public const string MailtoCCParameterKey = "cc";
public const string MailtoBCCParameterKey = "bcc";
public NameValueCollection MailtoParameters { get; set; }
private bool IsMailtoParameterExists(string parameterKey)
=> MailtoParameters != null
&& MailtoParameters.AllKeys.Contains(parameterKey);
public bool TryGetMailtoValue(string key, out string value)
{
bool valueExists = IsMailtoParameterExists(key);
value = valueExists ? MailtoParameters[key] : string.Empty;
return valueExists;
}
#endregion
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
using MimeKit;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Models.MailItem
{
public class DraftPreperationRequest : DraftCreationOptions
{
public DraftPreperationRequest(MailAccount account, MailCopy createdLocalDraftCopy, MimeMessage createdLocalDraftMimeMessage)
{
Account = account ?? throw new ArgumentNullException(nameof(account));
CreatedLocalDraftCopy = createdLocalDraftCopy ?? throw new ArgumentNullException(nameof(createdLocalDraftCopy));
CreatedLocalDraftMimeMessage = createdLocalDraftMimeMessage ?? throw new ArgumentNullException(nameof(createdLocalDraftMimeMessage));
}
public MailCopy CreatedLocalDraftCopy { get; set; }
public MimeMessage CreatedLocalDraftMimeMessage { get; set; }
public MailAccount Account { get; }
}
}

View File

@@ -0,0 +1,33 @@
using System;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Interface of simplest representation of a MailCopy.
/// </summary>
public interface IMailItem
{
Guid UniqueId { get; }
string Id { get; }
string Subject { get; }
string ThreadId { get; }
string MessageId { get; }
string References { get; }
string InReplyTo { get; }
string PreviewText { get; }
string FromName { get; }
DateTime CreationDate { get; }
string FromAddress { get; }
bool HasAttachments { get; }
bool IsFlagged { get; }
bool IsFocused { get; }
bool IsRead { get; }
string DraftId { get; }
bool IsDraft { get; }
Guid FileId { get; }
MailItemFolder AssignedFolder { get; }
MailAccount AssignedAccount { get; }
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.ObjectModel;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Interface that represents conversation threads.
/// Even though this type has 1 single UI representation most of the time,
/// it can contain multiple IMailItem.
/// </summary>
public interface IMailItemThread : IMailItem
{
ObservableCollection<IMailItem> ThreadItems { get; }
IMailItem LatestMailItem { get; }
IMailItem FirstMailItem { get; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.MailItem
{
public class MailDetailInformation
{
public string Id { get; set; }
public Guid AccountId { get; set; }
public Guid FolderId { get; set; }
public string RemoteFolderId { get; set; }
public SpecialFolderType SpecialFolderType { get; set; }
public bool IsRead { get; set; }
public bool IsFlagged { get; set; }
public bool IsDraft { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Class that holds information when the drag/drop of mails are performed.
/// </summary>
public class MailDragPackage
{
public MailDragPackage(IEnumerable<IMailItem> draggingMails)
{
DraggingMails = draggingMails;
}
public MailDragPackage(IMailItem draggingMail)
{
DraggingMails =
[
draggingMail
];
}
public IEnumerable<IMailItem> DraggingMails { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Wino.Core.Domain.Models.MailItem
{
public class MailFolderPairMetadata
{
public Guid FolderId { get; set; }
public string RemoteFolderId { get; set; }
public string MailCopyId { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
using MimeKit;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Models.MailItem
{
public record NewMailItemPackage(MailCopy Copy, MimeMessage Mime, string AssignedRemoteFolderId);
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Folders;
namespace Wino.Core.Domain.Models.MailItem
{
public record MailListInitializationOptions(IEnumerable<IMailItemFolder> Folders,
FilterOptionType FilterType,
SortingOptionType SortingOptionType,
bool CreateThreads,
bool? IsFocusedOnly,
string SearchQuery,
IEnumerable<Guid> ExistingUniqueIds);
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Folders;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Encapsulates the options for preparing requests to execute mail operations for mail items like Move, Delete, MarkAsRead, etc.
/// </summary>
public class MailOperationPreperationRequest
{
public MailOperationPreperationRequest(MailOperation action,
IEnumerable<MailCopy> mailItems,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false)
{
Action = action;
MailItems = mailItems ?? throw new ArgumentNullException(nameof(mailItems));
ToggleExecution = toggleExecution;
MoveTargetFolder = moveTargetFolder;
IgnoreHardDeleteProtection = ignoreHardDeleteProtection;
}
public MailOperationPreperationRequest(MailOperation action,
MailCopy singleMailItem,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false)
{
Action = action;
MailItems = new List<MailCopy>() { singleMailItem };
ToggleExecution = toggleExecution;
MoveTargetFolder = moveTargetFolder;
IgnoreHardDeleteProtection = ignoreHardDeleteProtection;
}
/// <summary>
/// Action to execute.
/// </summary>
public MailOperation Action { get; set; }
/// <summary>
/// Mail copies execute the action on.
/// </summary>
public IEnumerable<MailCopy> MailItems { get; set; }
/// <summary>
/// Whether the operation can be reverted if needed.
/// eg. MarkAsRead on already read item will set the action to MarkAsUnread.
/// This is used in hover actions for example.
/// </summary>
public bool ToggleExecution { get; set; }
/// <summary>
/// Whether hard delete protection should be ignored.
/// Discard draft requests for example should ignore hard delete protection.
/// </summary>
public bool IgnoreHardDeleteProtection { get; set; }
/// <summary>
/// Moving folder for the Move operation.
/// If null and the action is Move, the user will be prompted to select a folder.
/// </summary>
public IMailItemFolder MoveTargetFolder { get; }
}
}

View File

@@ -0,0 +1,9 @@
using MimeKit;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Encapsulates MimeMessage and the path to the file.
/// </summary>
public record MimeMessageInformation(MimeMessage MimeMessage, string Path);
}

View File

@@ -0,0 +1,13 @@
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Class that holds immutable information about special folders in Outlook.
/// </summary>
/// <param name="InboxId"></param>
/// <param name="TrashId"></param>
/// <param name="JunkId"></param>
/// <param name="DraftId"></param>
/// <param name="SentId"></param>
/// <param name="ArchiveId"></param>
public record OutlookSpecialFolderIdInformation(string InboxId, string TrashId, string JunkId, string DraftId, string SentId, string ArchiveId);
}

View File

@@ -0,0 +1,7 @@
using MimeKit;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Models.MailItem
{
public record SendDraftPreparationRequest(MailCopy MailItem, MimeMessage Mime, MailItemFolder DraftFolder, MailItemFolder SentFolder, MailAccountPreferences AccountPreferences);
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Models.MailItem
{
public class ThreadMailItem : IMailItemThread
{
// TODO: Ideally this should be SortedList.
public ObservableCollection<IMailItem> ThreadItems { get; } = new ObservableCollection<IMailItem>();
public IMailItem LatestMailItem => ThreadItems.LastOrDefault();
public IMailItem FirstMailItem => ThreadItems.FirstOrDefault();
public void AddThreadItem(IMailItem item)
{
if (item == null) return;
if (ThreadItems.Any(a => a.Id == item.Id))
{
return;
}
if (item != null && item.IsDraft)
{
ThreadItems.Insert(0, item);
return;
}
var insertItem = ThreadItems.FirstOrDefault(a => !a.IsDraft && a.CreationDate < item.CreationDate);
if (insertItem == null)
ThreadItems.Insert(ThreadItems.Count, item);
else
{
var index = ThreadItems.IndexOf(insertItem);
ThreadItems.Insert(index, item);
}
}
#region IMailItem
public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty;
public string Id => LatestMailItem?.Id ?? string.Empty;
// Show subject from last item.
public string Subject => LatestMailItem?.Subject ?? string.Empty;
public string ThreadId => LatestMailItem?.ThreadId ?? string.Empty;
public string PreviewText => FirstMailItem?.PreviewText ?? string.Empty;
public string FromName => LatestMailItem?.FromName ?? string.Empty;
public string FromAddress => LatestMailItem?.FromAddress ?? string.Empty;
public bool HasAttachments => ThreadItems.Any(a => a.HasAttachments);
public bool IsFlagged => ThreadItems.Any(a => a.IsFlagged);
public bool IsFocused => LatestMailItem?.IsFocused ?? false;
public bool IsRead => ThreadItems.All(a => a.IsRead);
public DateTime CreationDate => FirstMailItem?.CreationDate ?? DateTime.MinValue;
public bool IsDraft => ThreadItems.Any(a => a.IsDraft);
public string DraftId => string.Empty;
public string MessageId => LatestMailItem?.MessageId;
public string References => LatestMailItem?.References ?? string.Empty;
public string InReplyTo => LatestMailItem?.InReplyTo ?? string.Empty;
public MailItemFolder AssignedFolder => LatestMailItem?.AssignedFolder;
public MailAccount AssignedAccount => LatestMailItem?.AssignedAccount;
public Guid FileId => LatestMailItem?.FileId ?? Guid.Empty;
#endregion
}
}

View File

@@ -0,0 +1,21 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Menus
{
public class MailOperationMenuItem : MenuOperationItemBase<MailOperation>, IMenuOperation
{
/// <summary>
/// Gets or sets whether this menu item should be placed in SecondaryCommands if used in CommandBar.
/// </summary>
public bool IsSecondaryMenuPreferred { get; set; }
protected MailOperationMenuItem(MailOperation operation, bool isEnabled, bool isSecondaryMenuItem = false) : base(operation, isEnabled)
{
IsSecondaryMenuPreferred = isSecondaryMenuItem;
}
public static MailOperationMenuItem Create(MailOperation operation, bool isEnabled = true, bool isSecondaryMenuItem = false)
=> new MailOperationMenuItem(operation, isEnabled, isSecondaryMenuItem);
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Wino.Core.Domain.Models.Menus
{
public class MenuOperationItemBase<TOperation> where TOperation : Enum
{
public MenuOperationItemBase(TOperation operation, bool isEnabled)
{
Operation = operation;
IsEnabled = isEnabled;
Identifier = operation.ToString();
}
public TOperation Operation { get; set; }
public string Identifier { get; set; }
public bool IsEnabled { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Navigation
{
public class NavigateMailFolderEventArgs
{
public NavigateMailFolderEventArgs(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool> folderInitLoadAwaitTask = null)
{
BaseFolderMenuItem = baseFolderMenuItem;
FolderInitLoadAwaitTask = folderInitLoadAwaitTask;
}
/// <summary>
/// Base folder menu item.
/// </summary>
public IBaseFolderMenuItem BaseFolderMenuItem { get; set; }
/// <summary>
/// Completion source for waiting folder's mail initialization.
/// </summary>
public TaskCompletionSource<bool> FolderInitLoadAwaitTask { get; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Wino.Core.Domain.Models.Navigation
{
public enum NavigationMode
{
New,
Back,
Forward,
Refresh
}
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Core.Domain.Models.Navigation
{
public enum NavigationTransitionType
{
None, // Supress
DrillIn,
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Personalization
{
/// <summary>
/// Base class for all app themes.
/// </summary>
public abstract class AppThemeBase
{
public Guid Id { get; set; }
public string ThemeName { get; set; }
public ApplicationElementTheme ForceElementTheme { get; set; }
public string AccentColor { get; set; }
public bool IsAccentColorAssigned => !string.IsNullOrEmpty(AccentColor);
public string BackgroundPreviewImage => GetBackgroundPreviewImagePath();
public abstract AppThemeType AppThemeType { get; }
protected AppThemeBase(string themeName, Guid id)
{
ThemeName = themeName;
Id = id;
}
public abstract Task<string> GetThemeResourceDictionaryContentAsync();
public abstract string GetBackgroundPreviewImagePath();
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Wino.Core.Domain.Models.Personalization
{
public class CustomThemeMetadata
{
public Guid Id { get; set; }
public string Name { get; set; }
public string AccentColorHex { get; set; }
public bool HasCustomAccentColor => !string.IsNullOrEmpty(AccentColorHex);
}
}

View File

@@ -0,0 +1,16 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Personalization
{
public class ElementThemeContainer
{
public ElementThemeContainer(ApplicationElementTheme nativeTheme, string title)
{
NativeTheme = nativeTheme;
Title = title;
}
public ApplicationElementTheme NativeTheme { get; set; }
public string Title { get; set; }
}
}

View File

@@ -0,0 +1,4 @@
namespace Wino.Core.Domain.Models.Personalization
{
public record MailListPaneLengthPreferences(string Title, double Length);
}

View File

@@ -0,0 +1,30 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Reader
{
public class EditorToolbarSection
{
public EditorToolbarSectionType SectionType { get; set; }
public string Title
{
get
{
switch (SectionType)
{
case EditorToolbarSectionType.None:
return Translator.EditorToolbarOption_None;
case EditorToolbarSectionType.Format:
return Translator.EditorToolbarOption_Format;
case EditorToolbarSectionType.Insert:
return Translator.EditorToolbarOption_Insert;
case EditorToolbarSectionType.Draw:
return Translator.EditorToolbarOption_Draw;
case EditorToolbarSectionType.Options:
return Translator.EditorToolbarOption_Options;
default:
return "Unknown Editor Option";
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Reader
{
public class FilterOption
{
public FilterOptionType Type { get; set; }
public string Title { get; set; }
public FilterOption(string title, FilterOptionType type)
{
Title = title;
Type = type;
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using MimeKit;
namespace Wino.Core.Domain.Models.Reader
{
/// <summary>
/// Final model to be passed to renderer page.
/// Data here are created based on rendering settings.
/// </summary>
public class MailRenderModel
{
public string RenderHtml { get; }
public MailRenderingOptions MailRenderingOptions { get; }
public List<MimePart> Attachments { get; set; } = new List<MimePart>();
public string UnsubscribeLink { get; set; }
public bool CanUnsubscribe => !string.IsNullOrEmpty(UnsubscribeLink);
public MailRenderModel(string renderHtml, MailRenderingOptions mailRenderingOptions = null)
{
RenderHtml = renderHtml;
MailRenderingOptions = mailRenderingOptions;
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Wino.Core.Domain.Models.Reader
{
/// <summary>
/// Rendering options for mail.
/// </summary>
public class MailRenderingOptions
{
private const bool DefaultLoadImageValue = true;
private const bool DefaultLoadStylesValue = true;
public bool LoadImages { get; set; } = DefaultLoadImageValue;
public bool LoadStyles { get; set; } = DefaultLoadStylesValue;
// HtmlDocument.Load call is redundant if all the settings are in default values.
// Therefore we will purify the HTML only if needed.
public bool IsPurifyingNeeded() => LoadImages != DefaultLoadImageValue || LoadStyles != DefaultLoadStylesValue;
}
}

View File

@@ -0,0 +1,6 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Reader
{
public record ReaderFontModel(ReaderFont Font, string FontFamilyName);
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Comparers;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Reader
{
public class SortingOption
{
public SortingOptionType Type { get; set; }
public string Title { get; set; }
public IComparer<IMailItem> Comparer
{
get
{
if (Type == SortingOptionType.ReceiveDate)
return new DateComparer();
else
return new NameComparer();
}
}
public SortingOption(string title, SortingOptionType type)
{
Title = title;
Type = type;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Wino.Core.Domain.Models.Requests
{
/// <summary>
/// Interface for all messages to report UI changes from synchronizers to UI.
/// None of these messages can't run a code that manipulates database.
/// They are sent either from processor or view models to signal some other
/// parts of the application.
/// </summary>
public interface IUIMessage;
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Requests
{
public abstract record RequestBase<TBatchRequestType>(MailCopy Item, MailSynchronizerOperation Operation) : IRequest
where TBatchRequestType : IBatchChangeRequest
{
public abstract IBatchChangeRequest CreateBatch(IEnumerable<IRequest> requests);
public abstract void ApplyUIChanges();
public abstract void RevertUIChanges();
}
public abstract record FolderRequestBase(MailItemFolder Folder, MailSynchronizerOperation Operation) : IFolderRequest
{
public abstract void ApplyUIChanges();
public abstract void RevertUIChanges();
}
public abstract record BatchRequestBase(IEnumerable<IRequest> Items, MailSynchronizerOperation Operation) : IBatchChangeRequest
{
public abstract void ApplyUIChanges();
public abstract void RevertUIChanges();
}
}

View File

@@ -0,0 +1,15 @@
using System;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Requests
{
/// <summary>
/// Defines a single rule for toggling user actions if needed.
/// For example: If user wants to mark a mail as read, but it's already read, then it should be marked as unread.
/// </summary>
/// <param name="SourceAction"></param>
/// <param name="TargetAction"></param>
/// <param name="Condition"></param>
public record ToggleRequestRule(MailOperation SourceAction, MailOperation TargetAction, Func<IMailItem, bool> Condition);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Core.Domain.Models.Store
{
public enum StoreProductType
{
UnlimitedAccounts
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Synchronization
{
public class SynchronizationOptions
{
/// <summary>
/// Unique id of synchronization.
/// </summary>
public Guid Id { get; } = Guid.NewGuid();
/// <summary>
/// Account to execute synchronization for.
/// </summary>
public Guid AccountId { get; set; }
/// <summary>
/// Type of the synchronization to be performed.
/// </summary>
public SynchronizationType Type { get; set; }
/// <summary>
/// Collection of FolderId to perform SynchronizationType.Custom type sync.
/// </summary>
public List<Guid> SynchronizationFolderIds { get; set; }
/// <summary>
/// A listener to be notified about the progress of the synchronization.
/// </summary>
public ISynchronizationProgress ProgressListener { get; set; }
/// <summary>
/// When doing a linked inbox synchronization, we must ignore reporting completion to the caller for each folder.
/// This Id will help tracking that. Id is unique, but this one can be the same for all sync requests
/// inside the same linked inbox sync.
/// </summary>
public Guid? GroupedSynchronizationTrackingId { get; set; }
public override string ToString() => $"Type: {Type}, Folders: {(SynchronizationFolderIds == null ? "None" : string.Join(",", SynchronizationFolderIds))}";
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Synchronization
{
public class SynchronizationResult
{
protected SynchronizationResult() { }
public IEnumerable<IMailItem> DownloadedMessages { get; set; } = new List<IMailItem>();
public SynchronizationCompletedState CompletedState { get; set; }
public static SynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
public static SynchronizationResult Completed(IEnumerable<IMailItem> downloadedMessages)
=> new() { DownloadedMessages = downloadedMessages, CompletedState = SynchronizationCompletedState.Success };
public static SynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
}
}

View File

@@ -0,0 +1,6 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Translations
{
public record AppLanguageModel(AppLanguage Language, string DisplayName);
}