file scoped namespaces (#565)

This commit is contained in:
Aleh Khantsevich
2025-02-16 11:54:23 +01:00
committed by GitHub
parent cf9869b71e
commit 3ddc1a6229
617 changed files with 32107 additions and 32721 deletions

View File

@@ -1,6 +1,5 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Accounts
{
public record AccountCreationDialogResult(MailProviderType ProviderType, string AccountName, SpecialImapProviderDetails SpecialImapProviderDetails);
}
namespace Wino.Core.Domain.Models.Accounts;
public record AccountCreationDialogResult(MailProviderType ProviderType, string AccountName, SpecialImapProviderDetails SpecialImapProviderDetails);

View File

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

View File

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

View File

@@ -1,10 +1,9 @@
namespace Wino.Core.Domain.Models.Accounts
{
/// <summary>
/// Encapsulates the profile information of an account.
/// </summary>
/// <param name="SenderName">Display sender name for the account.</param>
/// <param name="Base64ProfilePictureData">Base 64 encoded profile picture data of the account. Thumbnail size.</param>
/// <param name="AccountAddress">Address of the profile.</param>
public record ProfileInformation(string SenderName, string Base64ProfilePictureData, string AccountAddress);
}
namespace Wino.Core.Domain.Models.Accounts;
/// <summary>
/// Encapsulates the profile information of an account.
/// </summary>
/// <param name="SenderName">Display sender name for the account.</param>
/// <param name="Base64ProfilePictureData">Base 64 encoded profile picture data of the account. Thumbnail size.</param>
/// <param name="AccountAddress">Address of the profile.</param>
public record ProfileInformation(string SenderName, string Base64ProfilePictureData, string AccountAddress);

View File

@@ -1,69 +1,68 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Accounts
namespace Wino.Core.Domain.Models.Accounts;
public class ProviderDetail : IProviderDetail
{
public class ProviderDetail : IProviderDetail
public MailProviderType Type { get; }
public SpecialImapProvider SpecialImapProvider { get; }
public string Name { get; }
public string Description { get; }
public string ProviderImage
{
public MailProviderType Type { get; }
public SpecialImapProvider SpecialImapProvider { get; }
public string Name { get; }
public string Description { get; }
public string ProviderImage
get
{
get
if (SpecialImapProvider == SpecialImapProvider.None)
{
if (SpecialImapProvider == SpecialImapProvider.None)
{
return $"/Wino.Core.UWP/Assets/Providers/{Type}.png";
}
else
{
return $"/Wino.Core.UWP/Assets/Providers/{SpecialImapProvider}.png";
}
return $"/Wino.Core.UWP/Assets/Providers/{Type}.png";
}
}
public bool IsSupported => Type == MailProviderType.Outlook || Type == MailProviderType.Gmail || Type == MailProviderType.IMAP4;
public ProviderDetail(MailProviderType type, SpecialImapProvider specialImapProvider)
{
Type = type;
SpecialImapProvider = specialImapProvider;
switch (Type)
else
{
case MailProviderType.Outlook:
Name = "Outlook";
Description = "Outlook.com, Live.com, Hotmail, MSN";
break;
case MailProviderType.Gmail:
Name = "Gmail";
Description = Translator.ProviderDetail_Gmail_Description;
break;
case MailProviderType.IMAP4:
switch (specialImapProvider)
{
case SpecialImapProvider.None:
Name = Translator.ProviderDetail_IMAP_Title;
Description = Translator.ProviderDetail_IMAP_Description;
break;
case SpecialImapProvider.iCloud:
Name = Translator.ProviderDetail_iCloud_Title;
Description = Translator.ProviderDetail_iCloud_Description;
break;
case SpecialImapProvider.Yahoo:
Name = Translator.ProviderDetail_Yahoo_Title;
Description = Translator.ProviderDetail_Yahoo_Description;
break;
default:
break;
}
break;
return $"/Wino.Core.UWP/Assets/Providers/{SpecialImapProvider}.png";
}
}
}
public bool IsSupported => Type == MailProviderType.Outlook || Type == MailProviderType.Gmail || Type == MailProviderType.IMAP4;
public ProviderDetail(MailProviderType type, SpecialImapProvider specialImapProvider)
{
Type = type;
SpecialImapProvider = specialImapProvider;
switch (Type)
{
case MailProviderType.Outlook:
Name = "Outlook";
Description = "Outlook.com, Live.com, Hotmail, MSN";
break;
case MailProviderType.Gmail:
Name = "Gmail";
Description = Translator.ProviderDetail_Gmail_Description;
break;
case MailProviderType.IMAP4:
switch (specialImapProvider)
{
case SpecialImapProvider.None:
Name = Translator.ProviderDetail_IMAP_Title;
Description = Translator.ProviderDetail_IMAP_Description;
break;
case SpecialImapProvider.iCloud:
Name = Translator.ProviderDetail_iCloud_Title;
Description = Translator.ProviderDetail_iCloud_Description;
break;
case SpecialImapProvider.Yahoo:
Name = Translator.ProviderDetail_Yahoo_Title;
Description = Translator.ProviderDetail_Yahoo_Description;
break;
default:
break;
}
break;
}
}
}

View File

@@ -1,6 +1,5 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Accounts
{
public record SpecialImapProviderDetails(string Address, string Password, string SenderName, SpecialImapProvider SpecialImapProvider);
}
namespace Wino.Core.Domain.Models.Accounts;
public record SpecialImapProviderDetails(string Address, string Password, string SenderName, SpecialImapProvider SpecialImapProvider);

View File

@@ -1,13 +1,12 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Accounts
namespace Wino.Core.Domain.Models.Accounts;
public class UnreadItemCountResult
{
public class UnreadItemCountResult
{
public Guid FolderId { get; set; }
public Guid AccountId { get; set; }
public SpecialFolderType SpecialFolderType { get; set; }
public int UnreadItemCount { get; set; }
}
public Guid FolderId { get; set; }
public Guid AccountId { get; set; }
public SpecialFolderType SpecialFolderType { get; set; }
public int UnreadItemCount { get; set; }
}

View File

@@ -1,11 +1,10 @@
namespace Wino.Core.Domain.Models.Authentication
{
/// <summary>
/// Previously known as TokenInformation.
/// We used to store this model in the database.
/// Now we store it in the memory.
/// </summary>
/// <param name="AccessToken">Access token/</param>
/// <param name="AccountAddress">Address of the authenticated user.</param>
public record TokenInformationEx(string AccessToken, string AccountAddress);
}
namespace Wino.Core.Domain.Models.Authentication;
/// <summary>
/// Previously known as TokenInformation.
/// We used to store this model in the database.
/// Now we store it in the memory.
/// </summary>
/// <param name="AccessToken">Access token/</param>
/// <param name="AccountAddress">Address of the authenticated user.</param>
public record TokenInformationEx(string AccessToken, string AccountAddress);

View File

@@ -3,70 +3,69 @@ using System.Collections.Generic;
using System.Linq;
using Wino.Core.Domain.Exceptions;
namespace Wino.Core.Domain.Models.Authorization
namespace Wino.Core.Domain.Models.Authorization;
public class GoogleAuthorizationRequest
{
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)
{
public const string RedirectUri = "google.pw.oauth2:/oauth2redirect";
State = state;
CodeVerifier = codeVerifier;
CodeChallange = codeChallange;
}
const string authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
const string CodeChallangeMethod = "S256";
// Pre
public string State { get; set; }
public string CodeVerifier { get; set; }
public string CodeChallange { get; set; }
public string ClientId { get; set; }
public GoogleAuthorizationRequest(string state, string codeVerifier, string codeChallange)
{
State = state;
CodeVerifier = codeVerifier;
CodeChallange = codeChallange;
}
// Post
public string AuthorizationCode { get; set; }
// Pre
public string State { get; set; }
public string CodeVerifier { get; set; }
public string CodeChallange { get; set; }
public string ClientId { get; set; }
public string BuildRequest(string clientId)
{
ClientId = clientId;
// Post
public string AuthorizationCode { get; set; }
// 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 https://www.googleapis.com/auth/userinfo.profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
authorizationEndpoint,
Uri.EscapeDataString(RedirectUri),
ClientId,
State,
CodeChallange,
CodeChallangeMethod);
}
public string BuildRequest(string clientId)
{
ClientId = clientId;
public void ValidateAuthorizationCode(Uri callbackUri)
{
if (callbackUri == null)
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthCallbackNull);
// 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 https://www.googleapis.com/auth/userinfo.profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
authorizationEndpoint,
Uri.EscapeDataString(RedirectUri),
ClientId,
State,
CodeChallange,
CodeChallangeMethod);
}
string queryString = callbackUri.Query;
public void ValidateAuthorizationCode(Uri callbackUri)
{
if (callbackUri == null)
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthCallbackNull);
Dictionary<string, string> queryStringParams = queryString.Substring(1).Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
string queryString = callbackUri.Query;
if (queryStringParams.ContainsKey("error"))
throw new GoogleAuthenticationException(string.Format(Translator.Exception_GoogleAuthError, queryStringParams["error"]));
Dictionary<string, string> queryStringParams = queryString.Substring(1).Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
if (!queryStringParams.ContainsKey("code") || !queryStringParams.ContainsKey("state"))
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthCorruptedCode + queryString);
if (queryStringParams.ContainsKey("error"))
throw new GoogleAuthenticationException(string.Format(Translator.Exception_GoogleAuthError, queryStringParams["error"]));
// Gets the Authorization code & state
string code = queryStringParams["code"];
string incomingState = queryStringParams["state"];
if (!queryStringParams.ContainsKey("code") || !queryStringParams.ContainsKey("state"))
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthCorruptedCode + queryString);
// 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));
// 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;
}
AuthorizationCode = code;
}
}

View File

@@ -1,27 +1,26 @@
using System;
using Wino.Core.Domain.Exceptions;
namespace Wino.Core.Domain.Models.Authorization
namespace Wino.Core.Domain.Models.Authorization;
public class GoogleTokenizationRequest
{
public class GoogleTokenizationRequest
public GoogleTokenizationRequest(GoogleAuthorizationRequest authorizationRequest)
{
public GoogleTokenizationRequest(GoogleAuthorizationRequest authorizationRequest)
{
if (authorizationRequest == null)
throw new GoogleAuthenticationException("Authorization request is empty.");
if (authorizationRequest == null)
throw new GoogleAuthenticationException("Authorization request is empty.");
AuthorizationRequest = authorizationRequest;
AuthorizationRequest = authorizationRequest;
if (string.IsNullOrEmpty(AuthorizationRequest.AuthorizationCode))
throw new GoogleAuthenticationException("Authorization request has empty code.");
}
if (string.IsNullOrEmpty(AuthorizationRequest.AuthorizationCode))
throw new GoogleAuthenticationException("Authorization request has empty code.");
}
public GoogleAuthorizationRequest AuthorizationRequest { get; set; }
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);
}
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

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

View File

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

View File

@@ -3,70 +3,69 @@ using System.Collections.Generic;
using System.Text.Json.Serialization;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Models.AutoDiscovery
namespace Wino.Core.Domain.Models.AutoDiscovery;
public class AutoDiscoverySettings
{
public class AutoDiscoverySettings
[JsonPropertyName("domain")]
public string Domain { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
[JsonPropertyName("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()
{
[JsonPropertyName("domain")]
public string Domain { get; set; }
var imapSettings = GetImapSettings();
var smtpSettings = GetSmptpSettings();
[JsonPropertyName("password")]
public string Password { get; set; }
if (imapSettings == null || smtpSettings == null) return null;
[JsonPropertyName("settings")]
public List<AutoDiscoveryProviderSetting> Settings { get; set; }
string imapUrl = imapSettings.Address;
string smtpUrl = smtpSettings.Address;
/// <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 _);
string imapUsername = imapSettings.Username;
string smtpUsername = smtpSettings.Username;
public AutoDiscoveryMinimalSettings UserMinimalSettings { get; set; }
int imapPort = imapSettings.Port;
int smtpPort = smtpSettings.Port;
public CustomServerInformation ToServerInformation()
var serverInfo = new CustomServerInformation
{
var imapSettings = GetImapSettings();
var smtpSettings = GetSmptpSettings();
Id = Guid.NewGuid(),
DisplayName = UserMinimalSettings.DisplayName,
Address = UserMinimalSettings.Email,
IncomingServerPassword = UserMinimalSettings.Password,
OutgoingServerPassword = UserMinimalSettings.Password,
IncomingAuthenticationMethod = Enums.ImapAuthenticationMethod.Auto,
OutgoingAuthenticationMethod = Enums.ImapAuthenticationMethod.Auto,
OutgoingServerSocketOption = Enums.ImapConnectionSecurity.Auto,
IncomingServerSocketOption = Enums.ImapConnectionSecurity.Auto,
IncomingServer = imapUrl,
OutgoingServer = smtpUrl,
IncomingServerPort = imapPort.ToString(),
OutgoingServerPort = smtpPort.ToString(),
IncomingServerType = Enums.CustomIncomingServerType.IMAP4,
IncomingServerUsername = imapUsername,
OutgoingServerUsername = smtpUsername,
MaxConcurrentClients = 5
};
if (imapSettings == null || smtpSettings == null) return null;
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,
IncomingAuthenticationMethod = Enums.ImapAuthenticationMethod.Auto,
OutgoingAuthenticationMethod = Enums.ImapAuthenticationMethod.Auto,
OutgoingServerSocketOption = Enums.ImapConnectionSecurity.Auto,
IncomingServerSocketOption = Enums.ImapConnectionSecurity.Auto,
IncomingServer = imapUrl,
OutgoingServer = smtpUrl,
IncomingServerPort = imapPort.ToString(),
OutgoingServerPort = smtpPort.ToString(),
IncomingServerType = Enums.CustomIncomingServerType.IMAP4,
IncomingServerUsername = imapUsername,
OutgoingServerUsername = smtpUsername,
MaxConcurrentClients = 5
};
return serverInfo;
}
public AutoDiscoveryProviderSetting GetImapSettings()
=> Settings?.Find(a => a.Protocol == "IMAP");
public AutoDiscoveryProviderSetting GetSmptpSettings()
=> Settings?.Find(a => a.Protocol == "SMTP");
return serverInfo;
}
public AutoDiscoveryProviderSetting GetImapSettings()
=> Settings?.Find(a => a.Protocol == "IMAP");
public AutoDiscoveryProviderSetting GetSmptpSettings()
=> Settings?.Find(a => a.Protocol == "SMTP");
}

View File

@@ -2,26 +2,25 @@
using Itenso.TimePeriod;
using Wino.Core.Domain.Collections;
namespace Wino.Core.Domain.Models.Calendar
namespace Wino.Core.Domain.Models.Calendar;
/// <summary>
/// Represents a day in the calendar.
/// Can hold events, appointments, wheather status etc.
/// </summary>
public class CalendarDayModel
{
/// <summary>
/// Represents a day in the calendar.
/// Can hold events, appointments, wheather status etc.
/// </summary>
public class CalendarDayModel
public ITimePeriod Period { get; }
public CalendarEventCollection EventsCollection { get; }
public CalendarDayModel(DateTime representingDate, CalendarRenderOptions calendarRenderOptions)
{
public ITimePeriod Period { get; }
public CalendarEventCollection EventsCollection { get; }
public CalendarDayModel(DateTime representingDate, CalendarRenderOptions calendarRenderOptions)
{
RepresentingDate = representingDate;
Period = new TimeRange(representingDate, representingDate.AddDays(1));
CalendarRenderOptions = calendarRenderOptions;
EventsCollection = new CalendarEventCollection(Period, calendarRenderOptions.CalendarSettings);
}
public DateTime RepresentingDate { get; }
public CalendarRenderOptions CalendarRenderOptions { get; }
RepresentingDate = representingDate;
Period = new TimeRange(representingDate, representingDate.AddDays(1));
CalendarRenderOptions = calendarRenderOptions;
EventsCollection = new CalendarEventCollection(Period, calendarRenderOptions.CalendarSettings);
}
public DateTime RepresentingDate { get; }
public CalendarRenderOptions CalendarRenderOptions { get; }
}

View File

@@ -1,7 +1,6 @@
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar
{
public record CalendarItemTarget(CalendarItem Item, CalendarEventTargetType TargetType);
}
namespace Wino.Core.Domain.Models.Calendar;
public record CalendarItemTarget(CalendarItem Item, CalendarEventTargetType TargetType);

View File

@@ -1,17 +1,16 @@
using System;
namespace Wino.Core.Domain.Models.Calendar
{
public class CalendarPageNavigationArgs
{
/// <summary>
/// When the app launches, automatically request the default calendar navigation options.
/// </summary>
public bool RequestDefaultNavigation { get; set; }
namespace Wino.Core.Domain.Models.Calendar;
/// <summary>
/// Display the calendar view for the specified date.
/// </summary>
public DateTime NavigationDate { get; set; }
}
public class CalendarPageNavigationArgs
{
/// <summary>
/// When the app launches, automatically request the default calendar navigation options.
/// </summary>
public bool RequestDefaultNavigation { get; set; }
/// <summary>
/// Display the calendar view for the specified date.
/// </summary>
public DateTime NavigationDate { get; set; }
}

View File

@@ -1,14 +1,13 @@
namespace Wino.Core.Domain.Models.Calendar
namespace Wino.Core.Domain.Models.Calendar;
public class CalendarRenderOptions
{
public class CalendarRenderOptions
public CalendarRenderOptions(DateRange dateRange, CalendarSettings calendarSettings)
{
public CalendarRenderOptions(DateRange dateRange, CalendarSettings calendarSettings)
{
DateRange = dateRange;
CalendarSettings = calendarSettings;
}
public int TotalDayCount => DateRange.TotalDays;
public DateRange DateRange { get; }
public CalendarSettings CalendarSettings { get; }
DateRange = dateRange;
CalendarSettings = calendarSettings;
}
public int TotalDayCount => DateRange.TotalDays;
public DateRange DateRange { get; }
public CalendarSettings CalendarSettings { get; }
}

View File

@@ -3,46 +3,45 @@ using System.Collections.Generic;
using System.Globalization;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar
namespace Wino.Core.Domain.Models.Calendar;
public record CalendarSettings(DayOfWeek FirstDayOfWeek,
List<DayOfWeek> WorkingDays,
TimeSpan WorkingHourStart,
TimeSpan WorkingHourEnd,
double HourHeight,
DayHeaderDisplayType DayHeaderDisplayType,
CultureInfo CultureInfo)
{
public record CalendarSettings(DayOfWeek FirstDayOfWeek,
List<DayOfWeek> WorkingDays,
TimeSpan WorkingHourStart,
TimeSpan WorkingHourEnd,
double HourHeight,
DayHeaderDisplayType DayHeaderDisplayType,
CultureInfo CultureInfo)
public TimeSpan? GetTimeSpan(string selectedTime)
{
public TimeSpan? GetTimeSpan(string selectedTime)
{
// Regardless of the format, we need to parse the time to a TimeSpan.
// User may list as 14:00 but enters 2:00 PM by input.
// Be flexible, not annoying.
// Regardless of the format, we need to parse the time to a TimeSpan.
// User may list as 14:00 but enters 2:00 PM by input.
// Be flexible, not annoying.
if (DateTime.TryParse(selectedTime, out DateTime parsedTime))
{
return parsedTime.TimeOfDay;
}
else
{
return null;
}
if (DateTime.TryParse(selectedTime, out DateTime parsedTime))
{
return parsedTime.TimeOfDay;
}
public string GetTimeString(TimeSpan timeSpan)
else
{
// Here we don't need to be flexible cuz we're saving back the value to the combos.
// They are populated based on the format and must be returned with the format.
var format = DayHeaderDisplayType switch
{
DayHeaderDisplayType.TwelveHour => "h:mm tt",
DayHeaderDisplayType.TwentyFourHour => "HH:mm",
_ => throw new ArgumentOutOfRangeException(nameof(DayHeaderDisplayType))
};
var dateTime = DateTime.Today.Add(timeSpan);
return dateTime.ToString(format, CultureInfo.InvariantCulture);
return null;
}
}
public string GetTimeString(TimeSpan timeSpan)
{
// Here we don't need to be flexible cuz we're saving back the value to the combos.
// They are populated based on the format and must be returned with the format.
var format = DayHeaderDisplayType switch
{
DayHeaderDisplayType.TwelveHour => "h:mm tt",
DayHeaderDisplayType.TwentyFourHour => "HH:mm",
_ => throw new ArgumentOutOfRangeException(nameof(DayHeaderDisplayType))
};
var dateTime = DateTime.Today.Add(timeSpan);
return dateTime.ToString(format, CultureInfo.InvariantCulture);
}
}

View File

@@ -2,37 +2,36 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies;
public abstract class BaseCalendarTypeDrawingStrategy
{
public abstract class BaseCalendarTypeDrawingStrategy
public CalendarSettings Settings { get; }
public CalendarDisplayType HandlingType { get; }
/// <summary>
/// Day range of the pre-rendered items.
/// </summary>
public abstract DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount);
/// <summary>
/// Gets the previous date range for rendering.
/// For example, 1 week view with 7 days will return -7 <> 0 days.
/// </summary>
/// <param name="CurrentDateRange">Current displayed date range.</param>
/// <param name="DayDisplayCount">Day display count/</param>
public abstract DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount);
public abstract DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount);
/// <summary>
/// How many items should be placed in 1 FlipViewItem.
/// </summary>
public abstract int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount);
protected BaseCalendarTypeDrawingStrategy(CalendarSettings settings, CalendarDisplayType handlingType)
{
public CalendarSettings Settings { get; }
public CalendarDisplayType HandlingType { get; }
/// <summary>
/// Day range of the pre-rendered items.
/// </summary>
public abstract DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount);
/// <summary>
/// Gets the previous date range for rendering.
/// For example, 1 week view with 7 days will return -7 <> 0 days.
/// </summary>
/// <param name="CurrentDateRange">Current displayed date range.</param>
/// <param name="DayDisplayCount">Day display count/</param>
public abstract DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount);
public abstract DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount);
/// <summary>
/// How many items should be placed in 1 FlipViewItem.
/// </summary>
public abstract int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount);
protected BaseCalendarTypeDrawingStrategy(CalendarSettings settings, CalendarDisplayType handlingType)
{
Settings = settings;
HandlingType = handlingType;
}
Settings = settings;
HandlingType = handlingType;
}
}

View File

@@ -2,35 +2,34 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies;
public class DayCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy
{
public class DayCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy
public DayCalendarDrawingStrategy(CalendarSettings settings) : base(settings, CalendarDisplayType.Day)
{
public DayCalendarDrawingStrategy(CalendarSettings settings) : base(settings, CalendarDisplayType.Day)
{
}
public override DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.EndDate, CurrentDateRange.EndDate.AddDays(DayDisplayCount * 5));
}
public override DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.StartDate.AddDays(-DayDisplayCount * 5), CurrentDateRange.StartDate);
}
public override DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount)
{
// Add good amount of days to the left and right of the DisplayDate.
var start = DisplayDate.AddDays(-4 * DayDisplayCount);
var end = DisplayDate.AddDays(4 * DayDisplayCount);
return new DateRange(start, end);
}
public override int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount) => DayDisplayCount;
}
public override DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.EndDate, CurrentDateRange.EndDate.AddDays(DayDisplayCount * 5));
}
public override DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.StartDate.AddDays(-DayDisplayCount * 5), CurrentDateRange.StartDate);
}
public override DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount)
{
// Add good amount of days to the left and right of the DisplayDate.
var start = DisplayDate.AddDays(-4 * DayDisplayCount);
var end = DisplayDate.AddDays(4 * DayDisplayCount);
return new DateRange(start, end);
}
public override int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount) => DayDisplayCount;
}

View File

@@ -2,32 +2,31 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Extensions;
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies;
public class MonthCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy
{
public class MonthCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy
public MonthCalendarDrawingStrategy(CalendarSettings settings)
: base(settings, CalendarDisplayType.Month)
{
public MonthCalendarDrawingStrategy(CalendarSettings settings)
: base(settings, CalendarDisplayType.Month)
{
}
public override DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.EndDate, CurrentDateRange.EndDate.AddDays(35));
}
public override DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.StartDate.AddDays(-35), CurrentDateRange.StartDate);
}
public override DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount)
{
// Get the first day of the month.
var firstDayOfMonth = new DateTime(DisplayDate.Year, DisplayDate.Month, 1);
return DateTimeExtensions.GetMonthDateRangeStartingWeekday(firstDayOfMonth, Settings.FirstDayOfWeek);
}
public override int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount) => 35;
}
public override DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.EndDate, CurrentDateRange.EndDate.AddDays(35));
}
public override DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount)
{
return new DateRange(CurrentDateRange.StartDate.AddDays(-35), CurrentDateRange.StartDate);
}
public override DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount)
{
// Get the first day of the month.
var firstDayOfMonth = new DateTime(DisplayDate.Year, DisplayDate.Month, 1);
return DateTimeExtensions.GetMonthDateRangeStartingWeekday(firstDayOfMonth, Settings.FirstDayOfWeek);
}
public override int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount) => 35;
}

View File

@@ -1,35 +1,34 @@
using System;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies
namespace Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies;
public class WeekCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy
{
public class WeekCalendarDrawingStrategy : BaseCalendarTypeDrawingStrategy
public WeekCalendarDrawingStrategy(CalendarSettings settings) : base(settings, Enums.CalendarDisplayType.Week) { }
public override DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount)
=> new DateRange(CurrentDateRange.EndDate, CurrentDateRange.EndDate.AddDays(7 * 2));
public override DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount)
=> new DateRange(CurrentDateRange.StartDate.AddDays(-7 * 2), CurrentDateRange.StartDate);
public override DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount)
{
public WeekCalendarDrawingStrategy(CalendarSettings settings) : base(settings, Enums.CalendarDisplayType.Week) { }
// Detect the first day of the week that contains the selected date.
DayOfWeek firstDayOfWeek = Settings.FirstDayOfWeek;
public override DateRange GetNextDateRange(DateRange CurrentDateRange, int DayDisplayCount)
=> new DateRange(CurrentDateRange.EndDate, CurrentDateRange.EndDate.AddDays(7 * 2));
int diff = (7 + (DisplayDate.DayOfWeek - Settings.FirstDayOfWeek)) % 7;
public override DateRange GetPreviousDateRange(DateRange CurrentDateRange, int DayDisplayCount)
=> new DateRange(CurrentDateRange.StartDate.AddDays(-7 * 2), CurrentDateRange.StartDate);
// Start loading from this date instead of visible date.
var weekStartDate = DisplayDate.AddDays(-diff).Date;
public override DateRange GetRenderDateRange(DateTime DisplayDate, int DayDisplayCount)
{
// Detect the first day of the week that contains the selected date.
DayOfWeek firstDayOfWeek = Settings.FirstDayOfWeek;
// Load -+ 14 days
var startDate = weekStartDate.AddDays(-14);
var endDte = weekStartDate.AddDays(14);
int diff = (7 + (DisplayDate.DayOfWeek - Settings.FirstDayOfWeek)) % 7;
// Start loading from this date instead of visible date.
var weekStartDate = DisplayDate.AddDays(-diff).Date;
// Load -+ 14 days
var startDate = weekStartDate.AddDays(-14);
var endDte = weekStartDate.AddDays(14);
return new DateRange(startDate, endDte);
}
public override int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount) => 7;
return new DateRange(startDate, endDte);
}
public override int GetRenderDayCount(DateTime DisplayDate, int DayDisplayCount) => 7;
}

View File

@@ -1,10 +1,9 @@
using System;
namespace Wino.Core.Domain.Models.Calendar
{
/// <summary>
/// Contains the clicked date on the calendar view.
/// </summary>
/// <param name="ClickedDate">Requested date.</param>
public record CalendarViewDayClickedEventArgs(DateTime ClickedDate);
}
namespace Wino.Core.Domain.Models.Calendar;
/// <summary>
/// Contains the clicked date on the calendar view.
/// </summary>
/// <param name="ClickedDate">Requested date.</param>
public record CalendarViewDayClickedEventArgs(DateTime ClickedDate);

View File

@@ -1,42 +1,41 @@
using System;
using System.Linq;
namespace Wino.Core.Domain.Models.Calendar
namespace Wino.Core.Domain.Models.Calendar;
public class DateRange
{
public class DateRange
public DateRange(DateTime startDate, DateTime endDate)
{
public DateRange(DateTime startDate, DateTime endDate)
{
StartDate = startDate;
EndDate = endDate;
}
StartDate = startDate;
EndDate = endDate;
}
public DateTime StartDate { get; }
public DateTime EndDate { get; }
public DateTime StartDate { get; }
public DateTime EndDate { get; }
public int TotalDays => (EndDate - StartDate).Days;
public int TotalDays => (EndDate - StartDate).Days;
public override string ToString() => $"{StartDate.ToString("dd MMMM")} - {EndDate.ToString("dd MMMM")}";
public override string ToString() => $"{StartDate.ToString("dd MMMM")} - {EndDate.ToString("dd MMMM")}";
public bool IsInRange(DateTime date)
{
return date >= StartDate && date <= EndDate;
}
public bool IsInRange(DateTime date)
{
return date >= StartDate && date <= EndDate;
}
/// <summary>
/// Gets the most visible month index in the visible date range.
/// </summary>
public int GetMostVisibleMonthIndex()
{
var dateRange = Enumerable.Range(0, (EndDate - StartDate).Days + 1).Select(offset => StartDate.AddDays(offset));
/// <summary>
/// Gets the most visible month index in the visible date range.
/// </summary>
public int GetMostVisibleMonthIndex()
{
var dateRange = Enumerable.Range(0, (EndDate - StartDate).Days + 1).Select(offset => StartDate.AddDays(offset));
var groupedByMonth = dateRange.GroupBy(date => date.Month)
.Select(g => new { Month = g.Key, DayCount = g.Count() });
var groupedByMonth = dateRange.GroupBy(date => date.Month)
.Select(g => new { Month = g.Key, DayCount = g.Count() });
// Find the month with the maximum count of days
var mostVisibleMonth = groupedByMonth.OrderByDescending(g => g.DayCount).FirstOrDefault();
// Find the month with the maximum count of days
var mostVisibleMonth = groupedByMonth.OrderByDescending(g => g.DayCount).FirstOrDefault();
return mostVisibleMonth?.Month ?? -1;
}
return mostVisibleMonth?.Month ?? -1;
}
}

View File

@@ -1,14 +1,13 @@
namespace Wino.Core.Domain.Models.Calendar
{
public class DayHeaderRenderModel
{
public DayHeaderRenderModel(string dayHeader, double hourHeight)
{
DayHeader = dayHeader;
HourHeight = hourHeight;
}
namespace Wino.Core.Domain.Models.Calendar;
public string DayHeader { get; }
public double HourHeight { get; }
public class DayHeaderRenderModel
{
public DayHeaderRenderModel(string dayHeader, double hourHeight)
{
DayHeader = dayHeader;
HourHeight = hourHeight;
}
public string DayHeader { get; }
public double HourHeight { get; }
}

View File

@@ -3,50 +3,49 @@ using System.Linq;
using Itenso.TimePeriod;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Calendar
namespace Wino.Core.Domain.Models.Calendar;
/// <summary>
/// Represents a range of days in the calendar.
/// Corresponds to 1 view of the FlipView in CalendarPage.
/// </summary>
public class DayRangeRenderModel
{
/// <summary>
/// Represents a range of days in the calendar.
/// Corresponds to 1 view of the FlipView in CalendarPage.
/// </summary>
public class DayRangeRenderModel
public ITimePeriod Period { get; }
public List<CalendarDayModel> CalendarDays { get; } = [];
// TODO: Get rid of this at some point.
public List<DayHeaderRenderModel> DayHeaders { get; } = [];
public CalendarRenderOptions CalendarRenderOptions { get; }
public DayRangeRenderModel(CalendarRenderOptions calendarRenderOptions)
{
public ITimePeriod Period { get; }
public List<CalendarDayModel> CalendarDays { get; } = [];
CalendarRenderOptions = calendarRenderOptions;
// TODO: Get rid of this at some point.
public List<DayHeaderRenderModel> DayHeaders { get; } = [];
public CalendarRenderOptions CalendarRenderOptions { get; }
public DayRangeRenderModel(CalendarRenderOptions calendarRenderOptions)
for (var i = 0; i < CalendarRenderOptions.TotalDayCount; i++)
{
CalendarRenderOptions = calendarRenderOptions;
var representingDate = calendarRenderOptions.DateRange.StartDate.AddDays(i);
var calendarDayModel = new CalendarDayModel(representingDate, calendarRenderOptions);
for (var i = 0; i < CalendarRenderOptions.TotalDayCount; i++)
CalendarDays.Add(calendarDayModel);
}
Period = new TimeRange(CalendarDays.First().RepresentingDate, CalendarDays.Last().RepresentingDate.AddDays(1));
// Create day headers based on culture info.
for (var i = 0; i < 24; i++)
{
var representingDate = calendarRenderOptions.DateRange.StartDate.Date.AddHours(i);
string dayHeader = calendarRenderOptions.CalendarSettings.DayHeaderDisplayType switch
{
var representingDate = calendarRenderOptions.DateRange.StartDate.AddDays(i);
var calendarDayModel = new CalendarDayModel(representingDate, calendarRenderOptions);
DayHeaderDisplayType.TwelveHour => representingDate.ToString("h tt", calendarRenderOptions.CalendarSettings.CultureInfo),
DayHeaderDisplayType.TwentyFourHour => representingDate.ToString("HH", calendarRenderOptions.CalendarSettings.CultureInfo),
_ => "N/A"
};
CalendarDays.Add(calendarDayModel);
}
Period = new TimeRange(CalendarDays.First().RepresentingDate, CalendarDays.Last().RepresentingDate.AddDays(1));
// Create day headers based on culture info.
for (var i = 0; i < 24; i++)
{
var representingDate = calendarRenderOptions.DateRange.StartDate.Date.AddHours(i);
string dayHeader = calendarRenderOptions.CalendarSettings.DayHeaderDisplayType switch
{
DayHeaderDisplayType.TwelveHour => representingDate.ToString("h tt", calendarRenderOptions.CalendarSettings.CultureInfo),
DayHeaderDisplayType.TwentyFourHour => representingDate.ToString("HH", calendarRenderOptions.CalendarSettings.CultureInfo),
_ => "N/A"
};
DayHeaders.Add(new DayHeaderRenderModel(dayHeader, calendarRenderOptions.CalendarSettings.HourHeight));
}
DayHeaders.Add(new DayHeaderRenderModel(dayHeader, calendarRenderOptions.CalendarSettings.HourHeight));
}
}
}

View File

@@ -1,14 +1,13 @@
using System.IO;
namespace Wino.Core.Domain.Models.Common
namespace Wino.Core.Domain.Models.Common;
/// <summary>
/// Abstraction for StorageFile
/// </summary>
/// <param name="FullFilePath">Full path of the file.</param>
/// <param name="Data">Content</param>
public record SharedFile(string FullFilePath, byte[] Data)
{
/// <summary>
/// Abstraction for StorageFile
/// </summary>
/// <param name="FullFilePath">Full path of the file.</param>
/// <param name="Data">Content</param>
public record SharedFile(string FullFilePath, byte[] Data)
{
public string FileName => Path.GetFileName(FullFilePath);
}
public string FileName => Path.GetFileName(FullFilePath);
}

View File

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

View File

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

View File

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

View File

@@ -2,37 +2,36 @@
using System.Collections.Generic;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Comparers
namespace Wino.Core.Domain.Models.Comparers;
public class ListItemComparer : IComparer<object>
{
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)
{
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)
{
if (x is IMailItem xMail && y is IMailItem yMail)
{
var itemComparer = GetItemComparer();
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;
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);
public IComparer<IMailItem> GetItemComparer()
{
if (SortByName)
return NameComparer;
else
return DateComparer;
}
return 0;
}
public IComparer<IMailItem> GetItemComparer()
{
if (SortByName)
return NameComparer;
else
return DateComparer;
}
}

View File

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

View File

@@ -1,25 +1,24 @@
using System.IO;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Models.Connectivity
namespace Wino.Core.Domain.Models.Connectivity;
public class ImapClientPoolOptions
{
public class ImapClientPoolOptions
public Stream ProtocolLog { get; }
public CustomServerInformation ServerInformation { get; }
public bool IsTestPool { get; }
protected ImapClientPoolOptions(CustomServerInformation serverInformation, Stream protocolLog, bool isTestPool)
{
public Stream ProtocolLog { get; }
public CustomServerInformation ServerInformation { get; }
public bool IsTestPool { get; }
protected ImapClientPoolOptions(CustomServerInformation serverInformation, Stream protocolLog, bool isTestPool)
{
ServerInformation = serverInformation;
ProtocolLog = protocolLog;
IsTestPool = isTestPool;
}
public static ImapClientPoolOptions CreateDefault(CustomServerInformation serverInformation, Stream protocolLog)
=> new(serverInformation, protocolLog, false);
public static ImapClientPoolOptions CreateTestPool(CustomServerInformation serverInformation, Stream protocolLog)
=> new(serverInformation, protocolLog, true);
ServerInformation = serverInformation;
ProtocolLog = protocolLog;
IsTestPool = isTestPool;
}
public static ImapClientPoolOptions CreateDefault(CustomServerInformation serverInformation, Stream protocolLog)
=> new(serverInformation, protocolLog, false);
public static ImapClientPoolOptions CreateTestPool(CustomServerInformation serverInformation, Stream protocolLog)
=> new(serverInformation, protocolLog, true);
}

View File

@@ -3,46 +3,45 @@ using System.Linq;
using System.Text.Json.Serialization;
using Wino.Core.Domain.Extensions;
namespace Wino.Core.Domain.Models.Connectivity
namespace Wino.Core.Domain.Models.Connectivity;
/// <summary>
/// Contains validation of the IMAP server connectivity during account setup.
/// </summary>
public class ImapConnectivityTestResults
{
/// <summary>
/// Contains validation of the IMAP server connectivity during account setup.
/// </summary>
public class ImapConnectivityTestResults
[JsonConstructor]
protected ImapConnectivityTestResults() { }
public bool IsSuccess { get; set; }
public bool IsCertificateUIRequired { get; set; }
public string FailedReason { get; set; }
public string FailureProtocolLog { get; set; }
public static ImapConnectivityTestResults Success() => new ImapConnectivityTestResults() { IsSuccess = true };
public static ImapConnectivityTestResults Failure(Exception ex, string failureProtocolLog) => new ImapConnectivityTestResults()
{
[JsonConstructor]
protected ImapConnectivityTestResults() { }
FailedReason = string.Join(Environment.NewLine, ex.GetInnerExceptions().Select(e => e.Message)),
FailureProtocolLog = failureProtocolLog
};
public bool IsSuccess { get; set; }
public bool IsCertificateUIRequired { get; set; }
public string FailedReason { get; set; }
public string FailureProtocolLog { get; set; }
public static ImapConnectivityTestResults Success() => new ImapConnectivityTestResults() { IsSuccess = true };
public static ImapConnectivityTestResults Failure(Exception ex, string failureProtocolLog) => new ImapConnectivityTestResults()
public static ImapConnectivityTestResults CertificateUIRequired(string issuer,
string expirationString,
string validFromString)
{
return new ImapConnectivityTestResults()
{
FailedReason = string.Join(Environment.NewLine, ex.GetInnerExceptions().Select(e => e.Message)),
FailureProtocolLog = failureProtocolLog
IsSuccess = false,
IsCertificateUIRequired = true,
CertificateIssuer = issuer,
CertificateExpirationDateString = expirationString,
CertificateValidFromDateString = validFromString
};
public static ImapConnectivityTestResults CertificateUIRequired(string issuer,
string expirationString,
string validFromString)
{
return new ImapConnectivityTestResults()
{
IsSuccess = false,
IsCertificateUIRequired = true,
CertificateIssuer = issuer,
CertificateExpirationDateString = expirationString,
CertificateValidFromDateString = validFromString
};
}
public string CertificateIssuer { get; set; }
public string CertificateValidFromDateString { get; set; }
public string CertificateExpirationDateString { get; set; }
}
public string CertificateIssuer { get; set; }
public string CertificateValidFromDateString { get; set; }
public string CertificateExpirationDateString { get; set; }
}

View File

@@ -2,32 +2,31 @@
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Folders
namespace Wino.Core.Domain.Models.Folders;
/// <summary>
/// Grouped folder information for the menu for given account.
/// </summary>
public class AccountFolderTree
{
/// <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)
{
public MailAccount Account { get; }
public List<IMailItemFolder> Folders { get; set; } = new List<IMailItemFolder>();
Account = account;
}
public AccountFolderTree(MailAccount account)
public bool HasSpecialTypeFolder(SpecialFolderType type)
{
foreach (var folderStructure in Folders)
{
Account = account;
bool hasSpecialFolder = folderStructure.ContainsSpecialFolderType(type);
if (hasSpecialFolder)
return true;
}
public bool HasSpecialTypeFolder(SpecialFolderType type)
{
foreach (var folderStructure in Folders)
{
bool hasSpecialFolder = folderStructure.ContainsSpecialFolderType(type);
if (hasSpecialFolder)
return true;
}
return false;
}
return false;
}
}

View File

@@ -2,13 +2,12 @@
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) { }
namespace Wino.Core.Domain.Models.Folders;
public static FolderOperationMenuItem Create(FolderOperation operation, bool isEnabled = true)
=> new FolderOperationMenuItem(operation, isEnabled);
}
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

@@ -1,12 +1,11 @@
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Folders
{
/// <summary>
/// Encapsulates a request to prepare a folder operation like Rename, Delete, etc.
/// </summary>
/// <param name="Action">Folder operation.</param>
/// <param name="Folder">Target folder.</param>
public record FolderOperationPreperationRequest(FolderOperation Action, MailItemFolder Folder) { }
}
namespace Wino.Core.Domain.Models.Folders;
/// <summary>
/// Encapsulates a request to prepare a folder operation like Rename, Delete, etc.
/// </summary>
/// <param name="Action">Folder operation.</param>
/// <param name="Folder">Target folder.</param>
public record FolderOperationPreperationRequest(FolderOperation Action, MailItemFolder Folder) { }

View File

@@ -2,30 +2,29 @@
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; }
namespace Wino.Core.Domain.Models.Folders;
bool ContainsSpecialFolderType(SpecialFolderType type);
}
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

@@ -1,10 +1,9 @@
using Wino.Core.Domain.Entities.Mail;
namespace Wino.Core.Domain.Models.Folders
{
public record SystemFolderConfiguration(MailItemFolder SentFolder,
MailItemFolder DraftFolder,
MailItemFolder ArchiveFolder,
MailItemFolder TrashFolder,
MailItemFolder JunkFolder);
}
namespace Wino.Core.Domain.Models.Folders;
public record SystemFolderConfiguration(MailItemFolder SentFolder,
MailItemFolder DraftFolder,
MailItemFolder ArchiveFolder,
MailItemFolder TrashFolder,
MailItemFolder JunkFolder);

View File

@@ -5,251 +5,250 @@ using MimeKit;
using MimeKit.Text;
using MimeKit.Tnef;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a browser control.
/// </summary>
public class HtmlPreviewVisitor : MimeVisitor
{
List<MultipartRelated> stack = new List<MultipartRelated>();
List<MimeEntity> attachments = new List<MimeEntity>();
readonly string tempDir;
public string Body { get; set; }
/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a browser control.
/// Creates a new HtmlPreviewVisitor.
/// </summary>
public class HtmlPreviewVisitor : MimeVisitor
/// <param name="tempDirectory">A temporary directory used for storing image files.</param>
public HtmlPreviewVisitor(string tempDirectory)
{
List<MultipartRelated> stack = new List<MultipartRelated>();
List<MimeEntity> attachments = new List<MimeEntity>();
tempDir = tempDirectory;
}
readonly string tempDir;
/// <summary>
/// The list of attachments that were in the MimeMessage.
/// </summary>
public IList<MimeEntity> Attachments
{
get { return attachments; }
}
public string Body { get; set; }
/// <summary>
/// The HTML string that can be set on the BrowserControl.
/// </summary>
public string HtmlBody
{
get { return Body ?? string.Empty; }
}
/// <summary>
/// Creates a new HtmlPreviewVisitor.
/// </summary>
/// <param name="tempDirectory">A temporary directory used for storing image files.</param>
public HtmlPreviewVisitor(string tempDirectory)
protected override void VisitMultipartAlternative(MultipartAlternative alternative)
{
// walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
for (int i = alternative.Count - 1; i >= 0 && Body == null; i--)
alternative[i].Accept(this);
}
protected override void VisitMultipartRelated(MultipartRelated related)
{
var root = related.Root;
// push this multipart/related onto our stack
stack.Add(related);
// visit the root document
root.Accept(this);
// pop this multipart/related off our stack
stack.RemoveAt(stack.Count - 1);
}
// look up the image based on the img src url within our multipart/related stack
bool TryGetImage(string url, out MimePart image)
{
UriKind kind;
int index;
Uri uri;
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
kind = UriKind.Absolute;
else if (Uri.IsWellFormedUriString(url, UriKind.Relative))
kind = UriKind.Relative;
else
kind = UriKind.RelativeOrAbsolute;
try
{
tempDir = tempDirectory;
uri = new Uri(url, kind);
}
/// <summary>
/// The list of attachments that were in the MimeMessage.
/// </summary>
public IList<MimeEntity> Attachments
catch
{
get { return attachments; }
}
/// <summary>
/// The HTML string that can be set on the BrowserControl.
/// </summary>
public string HtmlBody
{
get { return Body ?? string.Empty; }
}
protected override void VisitMultipartAlternative(MultipartAlternative alternative)
{
// walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
for (int i = alternative.Count - 1; i >= 0 && Body == null; i--)
alternative[i].Accept(this);
}
protected override void VisitMultipartRelated(MultipartRelated related)
{
var root = related.Root;
// push this multipart/related onto our stack
stack.Add(related);
// visit the root document
root.Accept(this);
// pop this multipart/related off our stack
stack.RemoveAt(stack.Count - 1);
}
// look up the image based on the img src url within our multipart/related stack
bool TryGetImage(string url, out MimePart image)
{
UriKind kind;
int index;
Uri uri;
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
kind = UriKind.Absolute;
else if (Uri.IsWellFormedUriString(url, UriKind.Relative))
kind = UriKind.Relative;
else
kind = UriKind.RelativeOrAbsolute;
try
{
uri = new Uri(url, kind);
}
catch
{
image = null;
return false;
}
for (int i = stack.Count - 1; i >= 0; i--)
{
if ((index = stack[i].IndexOf(uri)) == -1)
continue;
image = stack[i][index] as MimePart;
return image != null;
}
image = null;
return false;
}
// Save the image to our temp directory and return a "file://" url suitable for
// the browser control to load.
// Note: if you'd rather embed the image data into the HTML, you can construct a
// "data:" url instead.
string SaveImage(MimePart image)
for (int i = stack.Count - 1; i >= 0; i--)
{
using (var memory = new MemoryStream())
{
image.Content.DecodeTo(memory);
var buffer = memory.GetBuffer();
var length = (int)memory.Length;
var base64 = Convert.ToBase64String(buffer, 0, length);
if ((index = stack[i].IndexOf(uri)) == -1)
continue;
return string.Format("data:{0};base64,{1}", image.ContentType.MimeType, base64);
}
//string fileName = url
// .Replace(':', '_')
// .Replace('\\', '_')
// .Replace('/', '_');
//string path = Path.Combine(tempDir, fileName);
//if (!File.Exists(path))
//{
// using (var output = File.Create(path))
// image.Content.DecodeTo(output);
//}
//return "file://" + path.Replace('\\', '/');
image = stack[i][index] as MimePart;
return image != null;
}
// Replaces <img src=...> urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback(HtmlTagContext ctx, HtmlWriter htmlWriter)
image = null;
return false;
}
// Save the image to our temp directory and return a "file://" url suitable for
// the browser control to load.
// Note: if you'd rather embed the image data into the HTML, you can construct a
// "data:" url instead.
string SaveImage(MimePart image)
{
using (var memory = new MemoryStream())
{
if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0)
image.Content.DecodeTo(memory);
var buffer = memory.GetBuffer();
var length = (int)memory.Length;
var base64 = Convert.ToBase64String(buffer, 0, length);
return string.Format("data:{0};base64,{1}", image.ContentType.MimeType, base64);
}
//string fileName = url
// .Replace(':', '_')
// .Replace('\\', '_')
// .Replace('/', '_');
//string path = Path.Combine(tempDir, fileName);
//if (!File.Exists(path))
//{
// using (var output = File.Create(path))
// image.Content.DecodeTo(output);
//}
//return "file://" + path.Replace('\\', '/');
}
// Replaces <img src=...> urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback(HtmlTagContext ctx, HtmlWriter htmlWriter)
{
if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0)
{
ctx.WriteTag(htmlWriter, false);
// replace the src attribute with a file:// URL
foreach (var attribute in ctx.Attributes)
{
ctx.WriteTag(htmlWriter, false);
// replace the src attribute with a file:// URL
foreach (var attribute in ctx.Attributes)
if (attribute.Id == HtmlAttributeId.Src)
{
if (attribute.Id == HtmlAttributeId.Src)
{
MimePart image;
string url;
MimePart image;
string url;
if (!TryGetImage(attribute.Value, out image))
{
htmlWriter.WriteAttribute(attribute);
continue;
}
url = SaveImage(image);
htmlWriter.WriteAttributeName(attribute.Name);
htmlWriter.WriteAttributeValue(url);
}
else
if (!TryGetImage(attribute.Value, out image))
{
htmlWriter.WriteAttribute(attribute);
}
}
}
else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag)
{
ctx.WriteTag(htmlWriter, false);
// add and/or replace oncontextmenu="return false;"
foreach (var attribute in ctx.Attributes)
{
if (attribute.Name.ToLowerInvariant() == "oncontextmenu")
continue;
}
htmlWriter.WriteAttribute(attribute);
}
url = SaveImage(image);
htmlWriter.WriteAttribute("oncontextmenu", "return false;");
}
else
{
if (ctx.TagId == HtmlTagId.Unknown)
{
ctx.DeleteTag = true;
ctx.DeleteEndTag = true;
htmlWriter.WriteAttributeName(attribute.Name);
htmlWriter.WriteAttributeValue(url);
}
else
{
ctx.WriteTag(htmlWriter, true);
htmlWriter.WriteAttribute(attribute);
}
}
}
protected override void VisitTextPart(TextPart entity)
else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag)
{
TextConverter converter;
ctx.WriteTag(htmlWriter, false);
if (Body != null)
// add and/or replace oncontextmenu="return false;"
foreach (var attribute in ctx.Attributes)
{
// since we've already found the body, treat this as an attachment
attachments.Add(entity);
return;
if (attribute.Name.ToLowerInvariant() == "oncontextmenu")
continue;
htmlWriter.WriteAttribute(attribute);
}
if (entity.IsHtml)
htmlWriter.WriteAttribute("oncontextmenu", "return false;");
}
else
{
if (ctx.TagId == HtmlTagId.Unknown)
{
converter = new HtmlToHtml
{
HtmlTagCallback = HtmlTagCallback
};
}
else if (entity.IsFlowed)
{
var flowed = new FlowedToHtml();
string delsp;
if (entity.ContentType.Parameters.TryGetValue("delsp", out delsp))
flowed.DeleteSpace = delsp.ToLowerInvariant() == "yes";
converter = flowed;
ctx.DeleteTag = true;
ctx.DeleteEndTag = true;
}
else
{
converter = new TextToHtml();
ctx.WriteTag(htmlWriter, true);
}
Body = converter.Convert(entity.Text);
}
protected override void VisitTnefPart(TnefPart entity)
{
// extract any attachments in the MS-TNEF part
attachments.AddRange(entity.ExtractAttachments());
}
protected override void VisitMessagePart(MessagePart entity)
{
// treat message/rfc822 parts as attachments
attachments.Add(entity);
}
protected override void VisitMimePart(MimePart entity)
{
// realistically, if we've gotten this far, then we can treat this as an attachment
// even if the IsAttachment property is false.
attachments.Add(entity);
}
}
protected override void VisitTextPart(TextPart entity)
{
TextConverter converter;
if (Body != null)
{
// since we've already found the body, treat this as an attachment
attachments.Add(entity);
return;
}
if (entity.IsHtml)
{
converter = new HtmlToHtml
{
HtmlTagCallback = HtmlTagCallback
};
}
else if (entity.IsFlowed)
{
var flowed = new FlowedToHtml();
string delsp;
if (entity.ContentType.Parameters.TryGetValue("delsp", out delsp))
flowed.DeleteSpace = delsp.ToLowerInvariant() == "yes";
converter = flowed;
}
else
{
converter = new TextToHtml();
}
Body = converter.Convert(entity.Text);
}
protected override void VisitTnefPart(TnefPart entity)
{
// extract any attachments in the MS-TNEF part
attachments.AddRange(entity.ExtractAttachments());
}
protected override void VisitMessagePart(MessagePart entity)
{
// treat message/rfc822 parts as attachments
attachments.Add(entity);
}
protected override void VisitMimePart(MimePart entity)
{
// realistically, if we've gotten this far, then we can treat this as an attachment
// even if the IsAttachment property is false.
attachments.Add(entity);
}
}

View File

@@ -1,15 +1,14 @@
using System;
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// An interface that returns the UniqueId store for IMailItem.
/// For threads, it may be multiple items.
/// For single mails, it'll always be one item.
/// </summary>
public interface IMailHashContainer
{
/// <summary>
/// An interface that returns the UniqueId store for IMailItem.
/// For threads, it may be multiple items.
/// For single mails, it'll always be one item.
/// </summary>
public interface IMailHashContainer
{
IEnumerable<Guid> GetContainingIds();
}
IEnumerable<Guid> GetContainingIds();
}

View File

@@ -2,34 +2,33 @@
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Interface of simplest representation of a MailCopy.
/// </summary>
public interface IMailItem : IMailHashContainer
{
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; }
namespace Wino.Core.Domain.Models.MailItem;
MailItemFolder AssignedFolder { get; }
MailAccount AssignedAccount { get; }
AccountContact SenderContact { get; }
}
/// <summary>
/// Interface of simplest representation of a MailCopy.
/// </summary>
public interface IMailItem : IMailHashContainer
{
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; }
AccountContact SenderContact { get; }
}

View File

@@ -1,16 +1,15 @@
using System.Collections.ObjectModel;
namespace Wino.Core.Domain.Models.MailItem
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
{
/// <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; }
}
ObservableCollection<IMailItem> ThreadItems { get; }
IMailItem LatestMailItem { get; }
IMailItem FirstMailItem { get; }
}

View File

@@ -1,20 +1,19 @@
using MailKit;
using MimeKit;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Encapsulates all required information to create a MimeMessage for IMAP synchronizer.
/// </summary>
public class ImapMessageCreationPackage
{
public IMessageSummary MessageSummary { get; }
public MimeMessage MimeMessage { get; }
namespace Wino.Core.Domain.Models.MailItem;
public ImapMessageCreationPackage(IMessageSummary messageSummary, MimeMessage mimeMessage)
{
MessageSummary = messageSummary;
MimeMessage = mimeMessage;
}
/// <summary>
/// Encapsulates all required information to create a MimeMessage for IMAP synchronizer.
/// </summary>
public class ImapMessageCreationPackage
{
public IMessageSummary MessageSummary { get; }
public MimeMessage MimeMessage { get; }
public ImapMessageCreationPackage(IMessageSummary messageSummary, MimeMessage mimeMessage)
{
MessageSummary = messageSummary;
MimeMessage = mimeMessage;
}
}

View File

@@ -1,18 +1,17 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.MailItem
{
public class MailDetailInformation
{
public string Id { get; set; }
namespace Wino.Core.Domain.Models.MailItem;
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; }
}
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

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

View File

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

View File

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

View File

@@ -3,13 +3,12 @@ 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);
}
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

@@ -4,36 +4,35 @@ using Wino.Core.Domain.Entities.Mail;
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>
/// <param name="Action"> Action to execute. </param>
/// <param name="MailItems"> Mail copies execute the action on. </param>
/// <param name="ToggleExecution"> 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. </param>
/// <param name="IgnoreHardDeleteProtection"> Whether hard delete protection should be ignored.
/// Discard draft requests for example should ignore hard delete protection. </param>
/// <param name="MoveTargetFolder"> Moving folder for the Move operation.
/// If null and the action is Move, the user will be prompted to select a folder. </param>
public record MailOperationPreperationRequest(MailOperation Action, IEnumerable<MailCopy> MailItems, bool ToggleExecution, bool IgnoreHardDeleteProtection, IMailItemFolder MoveTargetFolder)
{
public MailOperationPreperationRequest(MailOperation action,
IEnumerable<MailCopy> mailItems,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, mailItems ?? throw new ArgumentNullException(nameof(mailItems)), toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
namespace Wino.Core.Domain.Models.MailItem;
public MailOperationPreperationRequest(MailOperation action,
MailCopy singleMailItem,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, new List<MailCopy>() { singleMailItem }, toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
/// <summary>
/// Encapsulates the options for preparing requests to execute mail operations for mail items like Move, Delete, MarkAsRead, etc.
/// </summary>
/// <param name="Action"> Action to execute. </param>
/// <param name="MailItems"> Mail copies execute the action on. </param>
/// <param name="ToggleExecution"> 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. </param>
/// <param name="IgnoreHardDeleteProtection"> Whether hard delete protection should be ignored.
/// Discard draft requests for example should ignore hard delete protection. </param>
/// <param name="MoveTargetFolder"> Moving folder for the Move operation.
/// If null and the action is Move, the user will be prompted to select a folder. </param>
public record MailOperationPreperationRequest(MailOperation Action, IEnumerable<MailCopy> MailItems, bool ToggleExecution, bool IgnoreHardDeleteProtection, IMailItemFolder MoveTargetFolder)
{
public MailOperationPreperationRequest(MailOperation action,
IEnumerable<MailCopy> mailItems,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, mailItems ?? throw new ArgumentNullException(nameof(mailItems)), toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
public MailOperationPreperationRequest(MailOperation action,
MailCopy singleMailItem,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, new List<MailCopy>() { singleMailItem }, toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
}

View File

@@ -1,9 +1,8 @@
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);
}
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

@@ -1,13 +1,12 @@
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);
}
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

@@ -4,19 +4,18 @@ using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Extensions;
namespace Wino.Core.Domain.Models.MailItem
{
public record SendDraftPreparationRequest(MailCopy MailItem,
MailAccountAlias SendingAlias,
MailItemFolder SentFolder,
MailItemFolder DraftFolder,
MailAccountPreferences AccountPreferences,
string Base64MimeMessage)
{
[JsonIgnore]
private MimeMessage mime;
namespace Wino.Core.Domain.Models.MailItem;
[JsonIgnore]
public MimeMessage Mime => mime ??= Base64MimeMessage.GetMimeMessageFromBase64();
}
public record SendDraftPreparationRequest(MailCopy MailItem,
MailAccountAlias SendingAlias,
MailItemFolder SentFolder,
MailItemFolder DraftFolder,
MailAccountPreferences AccountPreferences,
string Base64MimeMessage)
{
[JsonIgnore]
private MimeMessage mime;
[JsonIgnore]
public MimeMessage Mime => mime ??= Base64MimeMessage.GetMimeMessageFromBase64();
}

View File

@@ -5,91 +5,90 @@ using System.Linq;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
public class ThreadMailItem : IMailItemThread
{
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 bool AddThreadItem(IMailItem item)
{
// TODO: Ideally this should be SortedList.
public ObservableCollection<IMailItem> ThreadItems { get; } = new ObservableCollection<IMailItem>();
if (item == null) return false;
public IMailItem LatestMailItem => ThreadItems.LastOrDefault();
public IMailItem FirstMailItem => ThreadItems.FirstOrDefault();
public bool AddThreadItem(IMailItem item)
if (ThreadItems.Any(a => a.Id == item.Id))
{
if (item == null) return false;
if (ThreadItems.Any(a => a.Id == item.Id))
{
return false;
}
if (item != null && item.IsDraft)
{
ThreadItems.Insert(0, item);
return true;
}
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);
}
return false;
}
if (item != null && item.IsDraft)
{
ThreadItems.Insert(0, item);
return true;
}
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;
var insertItem = ThreadItems.FirstOrDefault(a => !a.IsDraft && a.CreationDate < item.CreationDate);
#region IMailItem
if (insertItem == null)
ThreadItems.Insert(ThreadItems.Count, item);
else
{
var index = ThreadItems.IndexOf(insertItem);
public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty;
public string Id => LatestMailItem?.Id ?? string.Empty;
ThreadItems.Insert(index, item);
}
// 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;
public AccountContact SenderContact => LatestMailItem?.SenderContact;
#endregion
return true;
}
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;
#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;
public AccountContact SenderContact => LatestMailItem?.SenderContact;
#endregion
}

View File

@@ -1,14 +1,13 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.MailItem
{
/// <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);
}
namespace Wino.Core.Domain.Models.MailItem;
/// <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

@@ -1,21 +1,20 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Menus
namespace Wino.Core.Domain.Models.Menus;
public class MailOperationMenuItem : MenuOperationItemBase<MailOperation>, IMenuOperation
{
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)
{
/// <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);
IsSecondaryMenuPreferred = isSecondaryMenuItem;
}
public static MailOperationMenuItem Create(MailOperation operation, bool isEnabled = true, bool isSecondaryMenuItem = false)
=> new MailOperationMenuItem(operation, isEnabled, isSecondaryMenuItem);
}

View File

@@ -1,18 +1,17 @@
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();
}
namespace Wino.Core.Domain.Models.Menus;
public TOperation Operation { get; set; }
public string Identifier { get; set; }
public bool IsEnabled { get; set; }
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

@@ -1,24 +1,23 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Navigation
namespace Wino.Core.Domain.Models.Navigation;
public class NavigateMailFolderEventArgs
{
public class NavigateMailFolderEventArgs
public NavigateMailFolderEventArgs(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool> folderInitLoadAwaitTask = null)
{
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; }
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

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

View File

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

View File

@@ -2,28 +2,27 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Personalization
namespace Wino.Core.Domain.Models.Personalization;
/// <summary>
/// Base class for all app themes.
/// </summary>
public abstract class AppThemeBase
{
/// <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)
{
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();
ThemeName = themeName;
Id = id;
}
public abstract Task<string> GetThemeResourceDictionaryContentAsync();
public abstract string GetBackgroundPreviewImagePath();
}

View File

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

View File

@@ -1,16 +1,15 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Personalization
{
public class ElementThemeContainer
{
public ElementThemeContainer(ApplicationElementTheme nativeTheme, string title)
{
NativeTheme = nativeTheme;
Title = title;
}
namespace Wino.Core.Domain.Models.Personalization;
public ApplicationElementTheme NativeTheme { get; set; }
public string Title { get; set; }
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

@@ -1,16 +1,15 @@
using System;
namespace Wino.Core.Domain.Models.Printing
{
public class PrintInformation
{
public PrintInformation(string pDFFilePath, string pDFTitle)
{
PDFFilePath = pDFFilePath ?? throw new ArgumentNullException(nameof(pDFFilePath));
PDFTitle = pDFTitle ?? throw new ArgumentNullException(nameof(pDFTitle));
}
namespace Wino.Core.Domain.Models.Printing;
public string PDFFilePath { get; }
public string PDFTitle { get; }
public class PrintInformation
{
public PrintInformation(string pDFFilePath, string pDFTitle)
{
PDFFilePath = pDFFilePath ?? throw new ArgumentNullException(nameof(pDFFilePath));
PDFTitle = pDFTitle ?? throw new ArgumentNullException(nameof(pDFTitle));
}
public string PDFFilePath { get; }
public string PDFTitle { get; }
}

View File

@@ -1,16 +1,15 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Reader
{
public class FilterOption
{
public FilterOptionType Type { get; set; }
public string Title { get; set; }
namespace Wino.Core.Domain.Models.Reader;
public FilterOption(string title, FilterOptionType type)
{
Title = title;
Type = type;
}
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

@@ -1,32 +1,31 @@
using System.Collections.Generic;
using MimeKit;
namespace Wino.Core.Domain.Models.Reader
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
{
/// <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; } = [];
public UnsubscribeInfo UnsubscribeInfo { get; set; }
public MailRenderModel(string renderHtml, MailRenderingOptions mailRenderingOptions = null)
{
public string RenderHtml { get; }
public MailRenderingOptions MailRenderingOptions { get; }
public List<MimePart> Attachments { get; set; } = [];
public UnsubscribeInfo UnsubscribeInfo { get; set; }
public MailRenderModel(string renderHtml, MailRenderingOptions mailRenderingOptions = null)
{
RenderHtml = renderHtml;
MailRenderingOptions = mailRenderingOptions;
}
}
public class UnsubscribeInfo
{
public string HttpLink { get; set; }
public string MailToLink { get; set; }
public bool IsOneClick { get; set; }
public bool CanUnsubscribe => HttpLink != null || MailToLink != null;
RenderHtml = renderHtml;
MailRenderingOptions = mailRenderingOptions;
}
}
public class UnsubscribeInfo
{
public string HttpLink { get; set; }
public string MailToLink { get; set; }
public bool IsOneClick { get; set; }
public bool CanUnsubscribe => HttpLink != null || MailToLink != null;
}

View File

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

View File

@@ -3,27 +3,26 @@ 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();
}
}
namespace Wino.Core.Domain.Models.Reader;
public SortingOption(string title, SortingOptionType type)
public class SortingOption
{
public SortingOptionType Type { get; set; }
public string Title { get; set; }
public IComparer<IMailItem> Comparer
{
get
{
Title = title;
Type = type;
if (Type == SortingOptionType.ReceiveDate)
return new DateComparer();
else
return new NameComparer();
}
}
public SortingOption(string title, SortingOptionType type)
{
Title = title;
Type = type;
}
}

View File

@@ -1,16 +1,15 @@
using System.Text.Json.Serialization;
namespace Wino.Core.Domain.Models.Reader
{
/// <summary>
/// Used to pass messages from the webview to the app.
/// </summary>
public class WebViewMessage
{
[JsonPropertyName("type")]
public string Type { get; set; }
namespace Wino.Core.Domain.Models.Reader;
[JsonPropertyName("value")]
public string Value { get; set; }
}
/// <summary>
/// Used to pass messages from the webview to the app.
/// </summary>
public class WebViewMessage
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
}

View File

@@ -4,37 +4,36 @@ using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Requests
namespace Wino.Core.Domain.Models.Requests;
public abstract record RequestBase<TOperation> where TOperation : Enum
{
public abstract record RequestBase<TOperation> where TOperation : Enum
{
public virtual void ApplyUIChanges() { }
public virtual void RevertUIChanges() { }
public virtual int ResynchronizationDelay => 0;
public abstract TOperation Operation { get; }
public virtual object GroupingKey() { return Operation; }
}
public abstract record MailRequestBase(MailCopy Item) : RequestBase<MailSynchronizerOperation>, IMailActionRequest
{
}
public abstract record FolderRequestBase(MailItemFolder Folder, FolderSynchronizerOperation Operation) : IFolderActionRequest
{
public abstract void ApplyUIChanges();
public abstract void RevertUIChanges();
public virtual int ResynchronizationDelay => 0;
public virtual object GroupingKey() { return Operation; }
}
public class BatchCollection<TRequestType> : List<TRequestType>, IUIChangeRequest where TRequestType : IUIChangeRequest
{
public BatchCollection(IEnumerable<TRequestType> collection) : base(collection)
{
}
public void ApplyUIChanges() => ForEach(x => x.ApplyUIChanges());
public void RevertUIChanges() => ForEach(x => x.RevertUIChanges());
}
public virtual void ApplyUIChanges() { }
public virtual void RevertUIChanges() { }
public virtual int ResynchronizationDelay => 0;
public abstract TOperation Operation { get; }
public virtual object GroupingKey() { return Operation; }
}
public abstract record MailRequestBase(MailCopy Item) : RequestBase<MailSynchronizerOperation>, IMailActionRequest
{
}
public abstract record FolderRequestBase(MailItemFolder Folder, FolderSynchronizerOperation Operation) : IFolderActionRequest
{
public abstract void ApplyUIChanges();
public abstract void RevertUIChanges();
public virtual int ResynchronizationDelay => 0;
public virtual object GroupingKey() { return Operation; }
}
public class BatchCollection<TRequestType> : List<TRequestType>, IUIChangeRequest where TRequestType : IUIChangeRequest
{
public BatchCollection(IEnumerable<TRequestType> collection) : base(collection)
{
}
public void ApplyUIChanges() => ForEach(x => x.ApplyUIChanges());
public void RevertUIChanges() => ForEach(x => x.RevertUIChanges());
}

View File

@@ -1,15 +1,14 @@
using System;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Requests
namespace Wino.Core.Domain.Models.Requests;
/// <summary>
/// Encapsulates request to queue and account for synchronizer.
/// </summary>
/// <param name="AccountId">Which account to execute this request for.</param>
/// <param name="Request">Prepared request for the server.</param>
public record ServerRequestPackage(Guid AccountId, IRequestBase Request) : IClientMessage
{
/// <summary>
/// Encapsulates request to queue and account for synchronizer.
/// </summary>
/// <param name="AccountId">Which account to execute this request for.</param>
/// <param name="Request">Prepared request for the server.</param>
public record ServerRequestPackage(Guid AccountId, IRequestBase Request) : IClientMessage
{
public override string ToString() => $"Server Package: {Request.GetType().Name}";
}
public override string ToString() => $"Server Package: {Request.GetType().Name}";
}

View File

@@ -1,40 +1,39 @@
using Wino.Core.Domain.Exceptions;
namespace Wino.Core.Domain.Models.Server
namespace Wino.Core.Domain.Models.Server;
/// <summary>
/// Encapsulates responses from the Wino server.
/// Exceptions are stored separately in the Message and StackTrace properties due to serialization issues.
/// </summary>
/// <typeparam name="T">Type of the expected response.</typeparam>
public class WinoServerResponse<T>
{
/// <summary>
/// Encapsulates responses from the Wino server.
/// Exceptions are stored separately in the Message and StackTrace properties due to serialization issues.
/// </summary>
/// <typeparam name="T">Type of the expected response.</typeparam>
public class WinoServerResponse<T>
public bool IsSuccess { get; set; }
public string Message { get; set; }
public T Data { get; set; }
public static WinoServerResponse<T> CreateSuccessResponse(T data)
{
public bool IsSuccess { get; set; }
public string Message { get; set; }
public T Data { get; set; }
public static WinoServerResponse<T> CreateSuccessResponse(T data)
return new WinoServerResponse<T>
{
return new WinoServerResponse<T>
{
IsSuccess = true,
Data = data
};
}
IsSuccess = true,
Data = data
};
}
public static WinoServerResponse<T> CreateErrorResponse(string message)
public static WinoServerResponse<T> CreateErrorResponse(string message)
{
return new WinoServerResponse<T>
{
return new WinoServerResponse<T>
{
IsSuccess = false,
Message = message
};
}
IsSuccess = false,
Message = message
};
}
public void ThrowIfFailed()
{
if (!IsSuccess)
throw new WinoServerException(Message);
}
public void ThrowIfFailed()
{
if (!IsSuccess)
throw new WinoServerException(Message);
}
}

View File

@@ -1,6 +1,5 @@
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Settings
{
public record SettingOption(string Title, string Description, WinoPage NavigationPage, string PathIcon);
}
namespace Wino.Core.Domain.Models.Settings;
public record SettingOption(string Title, string Description, WinoPage NavigationPage, string PathIcon);

View File

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

View File

@@ -2,30 +2,29 @@
using System.Collections.Generic;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Synchronization
namespace Wino.Core.Domain.Models.Synchronization;
public class CalendarSynchronizationOptions
{
public class CalendarSynchronizationOptions
{
/// <summary>
/// Unique id of synchronization.
/// </summary>
public Guid Id { get; } = Guid.NewGuid();
/// <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>
/// Account to execute synchronization for.
/// </summary>
public Guid AccountId { get; set; }
/// <summary>
/// Type of the synchronization to be performed.
/// </summary>
public CalendarSynchronizationType Type { get; set; }
/// <summary>
/// Type of the synchronization to be performed.
/// </summary>
public CalendarSynchronizationType Type { get; set; }
/// <summary>
/// Calendar ids to synchronize.
/// </summary>
public List<Guid> SynchronizationCalendarIds { get; set; }
/// <summary>
/// Calendar ids to synchronize.
/// </summary>
public List<Guid> SynchronizationCalendarIds { get; set; }
public override string ToString() => $"Type: {Type}, Calendars: {(SynchronizationCalendarIds == null ? "All" : string.Join(",", SynchronizationCalendarIds))}";
}
public override string ToString() => $"Type: {Type}, Calendars: {(SynchronizationCalendarIds == null ? "All" : string.Join(",", SynchronizationCalendarIds))}";
}

View File

@@ -4,43 +4,42 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
namespace Wino.Core.Domain.Models.Synchronization
namespace Wino.Core.Domain.Models.Synchronization;
public class CalendarSynchronizationResult
{
public class CalendarSynchronizationResult
{
public CalendarSynchronizationResult() { }
public CalendarSynchronizationResult() { }
/// <summary>
/// Gets the new downloaded events from synchronization.
/// Server will create notifications for these event.
/// It's ignored in serialization. Client should not react to this.
/// </summary>
[JsonIgnore]
public IEnumerable<ICalendarItem> DownloadedEvents { get; set; } = [];
/// <summary>
/// Gets the new downloaded events from synchronization.
/// Server will create notifications for these event.
/// It's ignored in serialization. Client should not react to this.
/// </summary>
[JsonIgnore]
public IEnumerable<ICalendarItem> DownloadedEvents { get; set; } = [];
public ProfileInformation ProfileInformation { get; set; }
public ProfileInformation ProfileInformation { get; set; }
public SynchronizationCompletedState CompletedState { get; set; }
public SynchronizationCompletedState CompletedState { get; set; }
public static CalendarSynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
public static CalendarSynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
// Mail synchronization
public static CalendarSynchronizationResult Completed(IEnumerable<ICalendarItem> downloadedEvent)
=> new()
{
DownloadedEvents = downloadedEvent,
CompletedState = SynchronizationCompletedState.Success
};
// Mail synchronization
public static CalendarSynchronizationResult Completed(IEnumerable<ICalendarItem> downloadedEvent)
=> new()
{
DownloadedEvents = downloadedEvent,
CompletedState = SynchronizationCompletedState.Success
};
// Profile synchronization
public static CalendarSynchronizationResult Completed(ProfileInformation profileInformation)
=> new()
{
ProfileInformation = profileInformation,
CompletedState = SynchronizationCompletedState.Success
};
// Profile synchronization
public static CalendarSynchronizationResult Completed(ProfileInformation profileInformation)
=> new()
{
ProfileInformation = profileInformation,
CompletedState = SynchronizationCompletedState.Success
};
public static CalendarSynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
public static CalendarSynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
}
public static CalendarSynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
public static CalendarSynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
}

View File

@@ -2,43 +2,42 @@
using System.Collections.Generic;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Synchronization
namespace Wino.Core.Domain.Models.Synchronization;
public class MailSynchronizationOptions
{
public class MailSynchronizationOptions
{
/// <summary>
/// Unique id of synchronization.
/// </summary>
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>
/// Unique id of synchronization.
/// </summary>
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>
/// Account to execute synchronization for.
/// </summary>
public Guid AccountId { get; set; }
/// <summary>
/// Account to execute synchronization for.
/// </summary>
public Guid AccountId { get; set; }
/// <summary>
/// Type of the synchronization to be performed.
/// </summary>
public MailSynchronizationType Type { get; set; }
/// <summary>
/// Type of the synchronization to be performed.
/// </summary>
public MailSynchronizationType Type { get; set; }
/// <summary>
/// Collection of FolderId to perform SynchronizationType.Custom type sync.
/// </summary>
public List<Guid> SynchronizationFolderIds { get; set; }
/// <summary>
/// Collection of FolderId to perform SynchronizationType.Custom type sync.
/// </summary>
public List<Guid> SynchronizationFolderIds { get; set; }
/// <summary>
/// If true, additional folders like Sent,Drafts and Deleted will not be synchronized
/// with InboxOnly and CustomFolders sync type.
/// </summary>
public bool ExcludeMustHaveFolders { get; set; }
/// <summary>
/// If true, additional folders like Sent,Drafts and Deleted will not be synchronized
/// with InboxOnly and CustomFolders sync type.
/// </summary>
public bool ExcludeMustHaveFolders { 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; }
/// <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}";
}
public override string ToString() => $"Type: {Type}";
}

View File

@@ -4,43 +4,42 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Synchronization
namespace Wino.Core.Domain.Models.Synchronization;
public class MailSynchronizationResult
{
public class MailSynchronizationResult
{
public MailSynchronizationResult() { }
public MailSynchronizationResult() { }
/// <summary>
/// Gets the new downloaded messages from synchronization.
/// Server will create notifications for these messages.
/// It's ignored in serialization. Client should not react to this.
/// </summary>
[JsonIgnore]
public IEnumerable<IMailItem> DownloadedMessages { get; set; } = [];
/// <summary>
/// Gets the new downloaded messages from synchronization.
/// Server will create notifications for these messages.
/// It's ignored in serialization. Client should not react to this.
/// </summary>
[JsonIgnore]
public IEnumerable<IMailItem> DownloadedMessages { get; set; } = [];
public ProfileInformation ProfileInformation { get; set; }
public ProfileInformation ProfileInformation { get; set; }
public SynchronizationCompletedState CompletedState { get; set; }
public SynchronizationCompletedState CompletedState { get; set; }
public static MailSynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
public static MailSynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
// Mail synchronization
public static MailSynchronizationResult Completed(IEnumerable<IMailItem> downloadedMessages)
=> new()
{
DownloadedMessages = downloadedMessages,
CompletedState = SynchronizationCompletedState.Success
};
// Mail synchronization
public static MailSynchronizationResult Completed(IEnumerable<IMailItem> downloadedMessages)
=> new()
{
DownloadedMessages = downloadedMessages,
CompletedState = SynchronizationCompletedState.Success
};
// Profile synchronization
public static MailSynchronizationResult Completed(ProfileInformation profileInformation)
=> new()
{
ProfileInformation = profileInformation,
CompletedState = SynchronizationCompletedState.Success
};
// Profile synchronization
public static MailSynchronizationResult Completed(ProfileInformation profileInformation)
=> new()
{
ProfileInformation = profileInformation,
CompletedState = SynchronizationCompletedState.Success
};
public static MailSynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
public static MailSynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
}
public static MailSynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
public static MailSynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
}

View File

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