Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc4838578e | ||
|
|
548996405a | ||
|
|
a9a5f0bd14 | ||
|
|
ec05ff6123 | ||
|
|
10c7ab421b | ||
|
|
a8a5cc53ea | ||
|
|
8fe48ca438 | ||
|
|
cbd5a515a9 | ||
|
|
5912adff93 | ||
|
|
983bc21448 | ||
|
|
6d08368462 | ||
|
|
cde7bb3524 | ||
|
|
133dc91561 | ||
|
|
f408f59beb | ||
|
|
8763bf11ab | ||
|
|
99592a52be | ||
|
|
25a8a52573 | ||
|
|
5901344459 |
@@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Windows.ApplicationModel;
|
|
||||||
using Windows.ApplicationModel.Background;
|
|
||||||
|
|
||||||
namespace Wino.BackgroundTasks
|
|
||||||
{
|
|
||||||
public sealed class SessionConnectedTask : IBackgroundTask
|
|
||||||
{
|
|
||||||
public async void Run(IBackgroundTaskInstance taskInstance)
|
|
||||||
{
|
|
||||||
var def = taskInstance.GetDeferral();
|
|
||||||
|
|
||||||
// Run server on session connected by launching the Full Thrust process.
|
|
||||||
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
|
||||||
|
|
||||||
def.Complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -104,7 +104,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="AppUpdatedTask.cs" />
|
<Compile Include="AppUpdatedTask.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SessionConnectedTask.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
namespace Wino.Core.Domain.Interfaces
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces
|
|
||||||
{
|
{
|
||||||
public interface IBackgroundTaskService
|
public interface IBackgroundTaskService
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Manages background task registrations, requests access if needed, checks the statusses of them etc.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="BackgroundTaskExecutionRequestDeniedException">If the access request is denied for some reason.</exception>
|
|
||||||
/// <exception cref="BackgroundTaskRegistrationFailedException">If one of the requires background tasks are failed during registration.</exception>
|
|
||||||
Task HandleBackgroundTaskRegistrations();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unregisters all existing background tasks. Useful for migrations.
|
/// Unregisters all existing background tasks. Useful for migrations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
using System.Collections.Specialized;
|
using Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
public interface ILaunchProtocolService
|
||||||
{
|
{
|
||||||
public interface ILaunchProtocolService
|
/// <summary>
|
||||||
{
|
/// Used to handle toasts.
|
||||||
object LaunchParameter { get; set; }
|
/// </summary>
|
||||||
NameValueCollection MailtoParameters { get; set; }
|
object LaunchParameter { get; set; }
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to handle mailto links.
|
||||||
|
/// </summary>
|
||||||
|
MailToUri MailToUri { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
{
|
{
|
||||||
Task<MailCopy> GetSingleMailItemAsync(string mailCopyId, string remoteFolderId);
|
Task<MailCopy> GetSingleMailItemAsync(string mailCopyId, string remoteFolderId);
|
||||||
Task<MailCopy> GetSingleMailItemAsync(Guid uniqueMailId);
|
Task<MailCopy> GetSingleMailItemAsync(Guid uniqueMailId);
|
||||||
Task<MailCopy> CreateDraftAsync(MailAccount composerAccount, string generatedReplyMimeMessageBase64, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null);
|
|
||||||
Task<List<IMailItem>> FetchMailsAsync(MailListInitializationOptions options);
|
Task<List<IMailItem>> FetchMailsAsync(MailListInitializationOptions options);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,23 +43,12 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps new mail item with the existing local draft copy.
|
/// Maps new mail item with the existing local draft copy.
|
||||||
///
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newMailCopyId"></param>
|
/// <param name="newMailCopyId"></param>
|
||||||
/// <param name="newDraftId"></param>
|
/// <param name="newDraftId"></param>
|
||||||
/// <param name="newThreadId"></param>
|
/// <param name="newThreadId"></param>
|
||||||
Task MapLocalDraftAsync(string newMailCopyId, string newDraftId, string newThreadId);
|
Task MapLocalDraftAsync(string newMailCopyId, string newDraftId, string newThreadId);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a draft message with the given options.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="accountId">Account to create draft for.</param>
|
|
||||||
/// <param name="options">Draft creation options.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// Base64 encoded string of MimeMessage object.
|
|
||||||
/// This is mainly for serialization purposes.
|
|
||||||
/// </returns>
|
|
||||||
Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions options);
|
|
||||||
Task UpdateMailAsync(MailCopy mailCopy);
|
Task UpdateMailAsync(MailCopy mailCopy);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -106,9 +94,18 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// Checks whether the mail exists in the folder.
|
/// Checks whether the mail exists in the folder.
|
||||||
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
|
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageId">Message id</param>
|
/// <param name="mailCopyId">MailCopy id</param>
|
||||||
/// <param name="folderId">Folder's local id.</param>
|
/// <param name="folderId">Folder's local id.</param>
|
||||||
/// <returns>Whether mail exists in the folder or not.</returns>
|
/// <returns>Whether mail exists in the folder or not.</returns>
|
||||||
Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId);
|
Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a draft MailCopy and MimeMessage based on the given options.
|
||||||
|
/// For forward/reply it would include the referenced message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountId">AccountId which should have new draft.</param>
|
||||||
|
/// <param name="draftCreationOptions">Options like new email/forward/draft.</param>
|
||||||
|
/// <returns>Draft MailCopy and Draft MimeMessage as base64.</returns>
|
||||||
|
Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(Guid accountId, DraftCreationOptions draftCreationOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// Queues new draft creation request for synchronizer.
|
/// Queues new draft creation request for synchronizer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="draftPreperationRequest">A class that holds the parameters for creating a draft.</param>
|
/// <param name="draftPreperationRequest">A class that holds the parameters for creating a draft.</param>
|
||||||
Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest);
|
Task ExecuteAsync(DraftPreparationRequest draftPreperationRequest);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queues a new request for synchronizer to send a draft.
|
/// Queues a new request for synchronizer to send a draft.
|
||||||
|
|||||||
@@ -19,19 +19,13 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Launches Full Trust process (Wino Server) and awaits connection completion.
|
/// Launches Full Trust process (Wino Server) and awaits connection completion.
|
||||||
/// If connection is not established in 5 seconds, it will return false.
|
/// If connection is not established in 10 seconds, it will return false.
|
||||||
/// If the server process is already running, it'll connect to existing one.
|
/// If the server process is already running, it'll connect to existing one.
|
||||||
/// If the server process is not running, it'll be launched and connection establishment is awaited.
|
/// If the server process is not running, it'll be launched and connection establishment is awaited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Whether connection is established or not.</returns>
|
/// <returns>Whether connection is established or not.</returns>
|
||||||
Task<bool> ConnectAsync();
|
Task<bool> ConnectAsync();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disconnects from existing connection and disposes the connection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Whether disconnection is succesfull or not.</returns>
|
|
||||||
Task<bool> DisconnectAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queues a new user request to be processed by Wino Server.
|
/// Queues a new user request to be processed by Wino Server.
|
||||||
/// Healthy connection must present before calling this method.
|
/// Healthy connection must present before calling this method.
|
||||||
@@ -48,6 +42,13 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// <param name="clientMessage">Request type.</param>
|
/// <param name="clientMessage">Request type.</param>
|
||||||
/// <returns>Response received from the server for the given TResponse type.</returns>
|
/// <returns>Response received from the server for the given TResponse type.</returns>
|
||||||
Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage) where TRequestType : IClientMessage;
|
Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage) where TRequestType : IClientMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle for connecting to the server.
|
||||||
|
/// If the server is already running, it'll connect to existing one.
|
||||||
|
/// Callers can await this handle to wait for connection establishment.
|
||||||
|
/// </summary>
|
||||||
|
TaskCompletionSource<bool> ConnectingHandle { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync
|
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.AutoDiscovery
|
namespace Wino.Core.Domain.Models.AutoDiscovery
|
||||||
{
|
{
|
||||||
public class AutoDiscoveryProviderSetting
|
public class AutoDiscoveryProviderSetting
|
||||||
{
|
{
|
||||||
[JsonProperty("protocol")]
|
[JsonPropertyName("protocol")]
|
||||||
public string Protocol { get; set; }
|
public string Protocol { get; set; }
|
||||||
|
|
||||||
[JsonProperty("address")]
|
[JsonPropertyName("address")]
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
|
||||||
[JsonProperty("port")]
|
[JsonPropertyName("port")]
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
[JsonProperty("secure")]
|
[JsonPropertyName("secure")]
|
||||||
public string Secure { get; set; }
|
public string Secure { get; set; }
|
||||||
|
|
||||||
[JsonProperty("username")]
|
[JsonPropertyName("username")]
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.AutoDiscovery
|
namespace Wino.Core.Domain.Models.AutoDiscovery
|
||||||
{
|
{
|
||||||
public class AutoDiscoverySettings
|
public class AutoDiscoverySettings
|
||||||
{
|
{
|
||||||
[JsonProperty("domain")]
|
[JsonPropertyName("domain")]
|
||||||
public string Domain { get; set; }
|
public string Domain { get; set; }
|
||||||
|
|
||||||
[JsonProperty("password")]
|
[JsonPropertyName("password")]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
[JsonProperty("settings")]
|
[JsonPropertyName("settings")]
|
||||||
public List<AutoDiscoveryProviderSetting> Settings { get; set; }
|
public List<AutoDiscoveryProviderSetting> Settings { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
76
Wino.Core.Domain/Models/Launch/MailToUri.cs
Normal file
76
Wino.Core.Domain/Models/Launch/MailToUri.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
|
public class MailToUri
|
||||||
|
{
|
||||||
|
public string Subject { get; private set; }
|
||||||
|
public string Body { get; private set; }
|
||||||
|
public List<string> To { get; } = [];
|
||||||
|
public List<string> Cc { get; } = [];
|
||||||
|
public List<string> Bcc { get; } = [];
|
||||||
|
public Dictionary<string, string> OtherParameters { get; } = [];
|
||||||
|
|
||||||
|
public MailToUri(string mailToUrl)
|
||||||
|
{
|
||||||
|
ParseMailToUrl(mailToUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseMailToUrl(string mailToUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mailToUrl))
|
||||||
|
throw new ArgumentException("mailtoUrl cannot be null or empty.", nameof(mailToUrl));
|
||||||
|
|
||||||
|
if (!mailToUrl.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
throw new ArgumentException("URL must start with 'mailto:'.", nameof(mailToUrl));
|
||||||
|
|
||||||
|
var mailToWithoutScheme = mailToUrl.Substring(7); // Remove "mailto:"
|
||||||
|
var components = mailToWithoutScheme.Split('?');
|
||||||
|
if (!string.IsNullOrEmpty(components[0]))
|
||||||
|
{
|
||||||
|
To.AddRange(components[0].Split(',').Select(email => HttpUtility.UrlDecode(email).Trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (components.Length <= 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters = components[1].Split('&');
|
||||||
|
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
{
|
||||||
|
var keyValue = parameter.Split('=');
|
||||||
|
if (keyValue.Length != 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var key = keyValue[0].ToLowerInvariant();
|
||||||
|
var value = HttpUtility.UrlDecode(keyValue[1]);
|
||||||
|
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case "to":
|
||||||
|
To.AddRange(value.Split(',').Select(email => email.Trim()));
|
||||||
|
break;
|
||||||
|
case "subject":
|
||||||
|
Subject = value;
|
||||||
|
break;
|
||||||
|
case "body":
|
||||||
|
Body = value;
|
||||||
|
break;
|
||||||
|
case "cc":
|
||||||
|
Cc.AddRange(value.Split(',').Select(email => email.Trim()));
|
||||||
|
break;
|
||||||
|
case "bcc":
|
||||||
|
Bcc.AddRange(value.Split(',').Select(email => email.Trim()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
OtherParameters[key] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,27 @@
|
|||||||
using System.Collections.Specialized;
|
using MimeKit;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using MimeKit;
|
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.MailItem
|
namespace Wino.Core.Domain.Models.MailItem;
|
||||||
|
|
||||||
|
public class DraftCreationOptions
|
||||||
{
|
{
|
||||||
public class DraftCreationOptions
|
public DraftCreationReason Reason { get; set; }
|
||||||
{
|
|
||||||
[JsonIgnore]
|
|
||||||
public MimeMessage ReferenceMimeMessage { get; set; }
|
|
||||||
public MailCopy ReferenceMailCopy { get; set; }
|
|
||||||
public DraftCreationReason Reason { get; set; }
|
|
||||||
|
|
||||||
#region Mailto Protocol Related Stuff
|
/// <summary>
|
||||||
|
/// Used for forward/reply
|
||||||
|
/// </summary>
|
||||||
|
public ReferencedMessage ReferencedMessage { get; set; }
|
||||||
|
|
||||||
public const string MailtoSubjectParameterKey = "subject";
|
/// <summary>
|
||||||
public const string MailtoBodyParameterKey = "body";
|
/// Used to create mails from Mailto links
|
||||||
public const string MailtoToParameterKey = "mailto";
|
/// </summary>
|
||||||
public const string MailtoCCParameterKey = "cc";
|
public MailToUri MailToUri { get; set; }
|
||||||
public const string MailtoBCCParameterKey = "bcc";
|
}
|
||||||
|
|
||||||
public NameValueCollection MailtoParameters { get; set; }
|
public class ReferencedMessage
|
||||||
|
{
|
||||||
private bool IsMailtoParameterExists(string parameterKey)
|
public MailCopy MailCopy { get; set; }
|
||||||
=> MailtoParameters != null
|
public MimeMessage MimeMessage { get; set; }
|
||||||
&& MailtoParameters.AllKeys.Contains(parameterKey);
|
|
||||||
|
|
||||||
public bool TryGetMailtoValue(string key, out string value)
|
|
||||||
{
|
|
||||||
bool valueExists = IsMailtoParameterExists(key);
|
|
||||||
|
|
||||||
value = valueExists ? MailtoParameters[key] : string.Empty;
|
|
||||||
|
|
||||||
return valueExists;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
48
Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs
Normal file
48
Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using MimeKit;
|
||||||
|
using Wino.Core.Domain.Entities;
|
||||||
|
using Wino.Core.Domain.Extensions;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.MailItem;
|
||||||
|
|
||||||
|
public class DraftPreparationRequest
|
||||||
|
{
|
||||||
|
public DraftPreparationRequest(MailAccount account, MailCopy createdLocalDraftCopy, string base64EncodedMimeMessage, MailCopy referenceMailCopy = null)
|
||||||
|
{
|
||||||
|
Account = account ?? throw new ArgumentNullException(nameof(account));
|
||||||
|
|
||||||
|
CreatedLocalDraftCopy = createdLocalDraftCopy ?? throw new ArgumentNullException(nameof(createdLocalDraftCopy));
|
||||||
|
ReferenceMailCopy = referenceMailCopy;
|
||||||
|
|
||||||
|
// MimeMessage is not serializable with System.Text.Json. Convert to base64 string.
|
||||||
|
// This is additional work when deserialization needed, but not much to do atm.
|
||||||
|
|
||||||
|
Base64LocalDraftMimeMessage = base64EncodedMimeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
private DraftPreparationRequest() { }
|
||||||
|
|
||||||
|
public MailCopy CreatedLocalDraftCopy { get; set; }
|
||||||
|
|
||||||
|
public MailCopy ReferenceMailCopy { get; set; }
|
||||||
|
|
||||||
|
public string Base64LocalDraftMimeMessage { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private MimeMessage createdLocalDraftMimeMessage;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public MimeMessage CreatedLocalDraftMimeMessage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
createdLocalDraftMimeMessage ??= Base64LocalDraftMimeMessage.GetMimeMessageFromBase64();
|
||||||
|
|
||||||
|
return createdLocalDraftMimeMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailAccount Account { get; }
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using MimeKit;
|
|
||||||
using Wino.Core.Domain.Entities;
|
|
||||||
using Wino.Core.Domain.Extensions;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.MailItem
|
|
||||||
{
|
|
||||||
public class DraftPreperationRequest : DraftCreationOptions
|
|
||||||
{
|
|
||||||
public DraftPreperationRequest(MailAccount account, MailCopy createdLocalDraftCopy, string base64EncodedMimeMessage)
|
|
||||||
{
|
|
||||||
Account = account ?? throw new ArgumentNullException(nameof(account));
|
|
||||||
|
|
||||||
CreatedLocalDraftCopy = createdLocalDraftCopy ?? throw new ArgumentNullException(nameof(createdLocalDraftCopy));
|
|
||||||
|
|
||||||
// MimeMessage is not serializable with System.Text.Json. Convert to base64 string.
|
|
||||||
// This is additional work when deserialization needed, but not much to do atm.
|
|
||||||
|
|
||||||
Base64LocalDraftMimeMessage = base64EncodedMimeMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonConstructor]
|
|
||||||
private DraftPreperationRequest() { }
|
|
||||||
|
|
||||||
public MailCopy CreatedLocalDraftCopy { get; set; }
|
|
||||||
|
|
||||||
public string Base64LocalDraftMimeMessage { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
private MimeMessage createdLocalDraftMimeMessage;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public MimeMessage CreatedLocalDraftMimeMessage
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (createdLocalDraftMimeMessage == null)
|
|
||||||
{
|
|
||||||
createdLocalDraftMimeMessage = Base64LocalDraftMimeMessage.GetMimeMessageFromBase64();
|
|
||||||
}
|
|
||||||
|
|
||||||
return createdLocalDraftMimeMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MailAccount Account { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
Wino.Core.Domain/Models/Reader/ImageInfo.cs
Normal file
12
Wino.Core.Domain/Models/Reader/ImageInfo.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Reader;
|
||||||
|
|
||||||
|
public class ImageInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public string Data { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Newtonsoft.Json;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Reader
|
namespace Wino.Core.Domain.Models.Reader
|
||||||
{
|
{
|
||||||
@@ -7,10 +7,10 @@ namespace Wino.Core.Domain.Models.Reader
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebViewMessage
|
public class WebViewMessage
|
||||||
{
|
{
|
||||||
[JsonProperty("type")]
|
[JsonPropertyName("type")]
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
|
||||||
[JsonProperty("value")]
|
[JsonPropertyName("value")]
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ namespace Wino.Core.Domain.Models.Server
|
|||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public T Data { get; set; }
|
public T Data { get; set; }
|
||||||
|
|
||||||
// protected WinoServerResponse() { }
|
|
||||||
|
|
||||||
public static WinoServerResponse<T> CreateSuccessResponse(T data)
|
public static WinoServerResponse<T> CreateSuccessResponse(T data)
|
||||||
{
|
{
|
||||||
return new WinoServerResponse<T>
|
return new WinoServerResponse<T>
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MimeKit" Version="4.7.1" />
|
<PackageReference Include="MimeKit" Version="4.7.1" />
|
||||||
<PackageReference Include="MailKit" Version="4.7.1.1" />
|
<PackageReference Include="MailKit" Version="4.7.1.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
||||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||||
<PackageReference Include="System.Text.Json" Version="8.0.4" />
|
<PackageReference Include="System.Text.Json" Version="8.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
using Serilog;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Serilog;
|
|
||||||
using Windows.ApplicationModel.Background;
|
using Windows.ApplicationModel.Background;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
@@ -8,12 +6,7 @@ namespace Wino.Core.UWP.Services
|
|||||||
{
|
{
|
||||||
public class BackgroundTaskService : IBackgroundTaskService
|
public class BackgroundTaskService : IBackgroundTaskService
|
||||||
{
|
{
|
||||||
private const string Is180BackgroundTasksRegisteredKey = nameof(Is180BackgroundTasksRegisteredKey);
|
private const string IsBackgroundTasksUnregisteredKey = nameof(IsBackgroundTasksUnregisteredKey);
|
||||||
|
|
||||||
public const string ToastActivationTaskEx = nameof(ToastActivationTaskEx);
|
|
||||||
|
|
||||||
private const string SessionConnectedTaskEntryPoint = "Wino.BackgroundTasks.SessionConnectedTask";
|
|
||||||
private const string SessionConnectedTaskName = "SessionConnectedTask";
|
|
||||||
|
|
||||||
private readonly IConfigurationService _configurationService;
|
private readonly IConfigurationService _configurationService;
|
||||||
|
|
||||||
@@ -22,48 +15,18 @@ namespace Wino.Core.UWP.Services
|
|||||||
_configurationService = configurationService;
|
_configurationService = configurationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HandleBackgroundTaskRegistrations()
|
|
||||||
{
|
|
||||||
bool is180BackgroundTaskRegistered = _configurationService.Get<bool>(Is180BackgroundTasksRegisteredKey);
|
|
||||||
|
|
||||||
// Don't re-register tasks.
|
|
||||||
if (is180BackgroundTaskRegistered) return;
|
|
||||||
|
|
||||||
var response = await BackgroundExecutionManager.RequestAccessAsync();
|
|
||||||
|
|
||||||
if (response != BackgroundAccessStatus.DeniedBySystemPolicy ||
|
|
||||||
response != BackgroundAccessStatus.DeniedByUser)
|
|
||||||
{
|
|
||||||
// Unregister all tasks and register new ones.
|
|
||||||
|
|
||||||
UnregisterAllBackgroundTask();
|
|
||||||
RegisterSessionConnectedTask();
|
|
||||||
|
|
||||||
_configurationService.Set(Is180BackgroundTasksRegisteredKey, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnregisterAllBackgroundTask()
|
public void UnregisterAllBackgroundTask()
|
||||||
{
|
{
|
||||||
foreach (var task in BackgroundTaskRegistration.AllTasks)
|
if (!_configurationService.Get(IsBackgroundTasksUnregisteredKey, false))
|
||||||
{
|
{
|
||||||
task.Value.Unregister(true);
|
foreach (var task in BackgroundTaskRegistration.AllTasks)
|
||||||
|
{
|
||||||
|
task.Value.Unregister(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information("Unregistered all background tasks.");
|
||||||
|
_configurationService.Set(IsBackgroundTasksUnregisteredKey, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("Unregistered all background tasks.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private BackgroundTaskRegistration RegisterSessionConnectedTask()
|
|
||||||
{
|
|
||||||
var builder = new BackgroundTaskBuilder
|
|
||||||
{
|
|
||||||
Name = SessionConnectedTaskName,
|
|
||||||
TaskEntryPoint = SessionConnectedTaskEntryPoint
|
|
||||||
};
|
|
||||||
|
|
||||||
builder.SetTrigger(new SystemTrigger(SystemTriggerType.SessionConnected, false));
|
|
||||||
|
|
||||||
return builder.Register();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Toolkit.Uwp.Helpers;
|
using Microsoft.Toolkit.Uwp.Helpers;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Windows.UI;
|
using Windows.UI;
|
||||||
using Windows.UI.ViewManagement;
|
using Windows.UI.ViewManagement;
|
||||||
@@ -406,7 +406,7 @@ namespace Wino.Services
|
|||||||
// Save metadata.
|
// Save metadata.
|
||||||
var metadataFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.json", CreationCollisionOption.ReplaceExisting);
|
var metadataFile = await themeFolder.CreateFileAsync($"{newTheme.Id}.json", CreationCollisionOption.ReplaceExisting);
|
||||||
|
|
||||||
var serialized = JsonConvert.SerializeObject(newTheme);
|
var serialized = JsonSerializer.Serialize(newTheme);
|
||||||
await FileIO.WriteTextAsync(metadataFile, serialized);
|
await FileIO.WriteTextAsync(metadataFile, serialized);
|
||||||
|
|
||||||
return newTheme;
|
return newTheme;
|
||||||
@@ -438,7 +438,7 @@ namespace Wino.Services
|
|||||||
{
|
{
|
||||||
var fileContent = await FileIO.ReadTextAsync(file);
|
var fileContent = await FileIO.ReadTextAsync(file);
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<CustomThemeMetadata>(fileContent);
|
return JsonSerializer.Deserialize<CustomThemeMetadata>(fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetSystemAccentColorHex()
|
public string GetSystemAccentColorHex()
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ namespace Wino.Core.UWP.Services
|
|||||||
{
|
{
|
||||||
public class WinoServerConnectionManager :
|
public class WinoServerConnectionManager :
|
||||||
IWinoServerConnectionManager<AppServiceConnection>,
|
IWinoServerConnectionManager<AppServiceConnection>,
|
||||||
IRecipient<WinoServerConnectionEstrablished>
|
IRecipient<WinoServerConnectionEstablished>
|
||||||
{
|
{
|
||||||
private const int ServerConnectionTimeoutMs = 5000;
|
private const int ServerConnectionTimeoutMs = 10000;
|
||||||
|
|
||||||
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
|
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
|
||||||
private TaskCompletionSource<bool> _connectionTaskCompletionSource;
|
|
||||||
|
public TaskCompletionSource<bool> ConnectingHandle { get; private set; }
|
||||||
|
|
||||||
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
|
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ namespace Wino.Core.UWP.Services
|
|||||||
get { return status; }
|
get { return status; }
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
|
Log.Information("Server connection status changed to {Status}.", value);
|
||||||
status = value;
|
status = value;
|
||||||
StatusChanged?.Invoke(this, value);
|
StatusChanged?.Invoke(this, value);
|
||||||
}
|
}
|
||||||
@@ -85,52 +87,85 @@ namespace Wino.Core.UWP.Services
|
|||||||
|
|
||||||
public async Task<bool> ConnectAsync()
|
public async Task<bool> ConnectAsync()
|
||||||
{
|
{
|
||||||
if (Status == WinoServerConnectionStatus.Connected) return true;
|
if (Status == WinoServerConnectionStatus.Connected)
|
||||||
|
{
|
||||||
|
Log.Information("Server is already connected.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Status == WinoServerConnectionStatus.Connecting)
|
||||||
|
{
|
||||||
|
// A connection is already being established at the moment.
|
||||||
|
// No need to run another connection establishment process.
|
||||||
|
// Await the connecting handler if possible.
|
||||||
|
|
||||||
|
if (ConnectingHandle != null)
|
||||||
|
{
|
||||||
|
return await ConnectingHandle.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
|
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_connectionTaskCompletionSource ??= new TaskCompletionSource<bool>();
|
ConnectingHandle = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
|
|
||||||
|
|
||||||
Status = WinoServerConnectionStatus.Connecting;
|
Status = WinoServerConnectionStatus.Connecting;
|
||||||
|
|
||||||
|
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
|
||||||
|
|
||||||
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
||||||
|
|
||||||
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
|
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
|
||||||
// Once the connection is established, the handler will set the Connection property
|
// Once the connection is established, the handler will set the Connection property
|
||||||
// and WinoServerConnectionEstrablished will be fired by the messenger.
|
// and WinoServerConnectionEstablished will be fired by the messenger.
|
||||||
|
|
||||||
await _connectionTaskCompletionSource.Task.WaitAsync(connectionCancellationToken.Token);
|
await ConnectingHandle.Task.WaitAsync(connectionCancellationToken.Token);
|
||||||
|
|
||||||
|
Log.Information("Server connection established successfully.");
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (OperationCanceledException canceledException)
|
||||||
{
|
{
|
||||||
|
Log.Error(canceledException, $"Server process did not start in {ServerConnectionTimeoutMs} ms. Operation is canceled.");
|
||||||
|
|
||||||
|
ConnectingHandle?.TrySetException(canceledException);
|
||||||
|
|
||||||
|
Status = WinoServerConnectionStatus.Failed;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to connect to the server.");
|
||||||
|
|
||||||
|
ConnectingHandle?.TrySetException(ex);
|
||||||
|
|
||||||
Status = WinoServerConnectionStatus.Failed;
|
Status = WinoServerConnectionStatus.Failed;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Information("FullTrustAppContract is not present in the system. Server connection is not possible.");
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DisconnectAsync()
|
|
||||||
{
|
|
||||||
if (Connection == null || Status == WinoServerConnectionStatus.Disconnected) return true;
|
|
||||||
|
|
||||||
// TODO: Send disconnect message to the fulltrust process.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
public async Task InitializeAsync()
|
||||||
{
|
{
|
||||||
var isConnectionSuccessfull = await ConnectAsync();
|
var isConnectionSuccessfull = await ConnectAsync();
|
||||||
|
|
||||||
// TODO: Log connection status
|
if (isConnectionSuccessfull)
|
||||||
|
{
|
||||||
|
Log.Information("ServerConnectionManager initialized successfully.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error("ServerConnectionManager initialization failed.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
|
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
|
||||||
@@ -222,7 +257,7 @@ namespace Wino.Core.UWP.Services
|
|||||||
|
|
||||||
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
||||||
{
|
{
|
||||||
// TODO: Handle server disconnection.
|
Log.Information("Server disconnected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
public async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
@@ -242,8 +277,8 @@ namespace Wino.Core.UWP.Services
|
|||||||
|
|
||||||
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message, Dictionary<string, object> parameters = null)
|
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message, Dictionary<string, object> parameters = null)
|
||||||
{
|
{
|
||||||
if (Connection == null)
|
if (Status != WinoServerConnectionStatus.Connected)
|
||||||
return WinoServerResponse<TResponse>.CreateErrorResponse("Server connection is not established.");
|
await ConnectAsync();
|
||||||
|
|
||||||
string serializedMessage = string.Empty;
|
string serializedMessage = string.Empty;
|
||||||
|
|
||||||
@@ -305,12 +340,7 @@ namespace Wino.Core.UWP.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Receive(WinoServerConnectionEstrablished message)
|
public void Receive(WinoServerConnectionEstablished message)
|
||||||
{
|
=> ConnectingHandle?.TrySetResult(true);
|
||||||
if (_connectionTaskCompletionSource != null)
|
|
||||||
{
|
|
||||||
_connectionTaskCompletionSource.TrySetResult(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
@@ -58,14 +58,14 @@ namespace Wino.Core.Authenticators
|
|||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthorizationCodeExchangeFailed);
|
throw new GoogleAuthenticationException(Translator.Exception_GoogleAuthorizationCodeExchangeFailed);
|
||||||
|
|
||||||
var parsed = JObject.Parse(responseString);
|
var parsed = JsonNode.Parse(responseString).AsObject();
|
||||||
|
|
||||||
if (parsed.ContainsKey("error"))
|
if (parsed.ContainsKey("error"))
|
||||||
throw new GoogleAuthenticationException(parsed["error"]["message"].Value<string>());
|
throw new GoogleAuthenticationException(parsed["error"]["message"].GetValue<string>());
|
||||||
|
|
||||||
var accessToken = parsed["access_token"].Value<string>();
|
var accessToken = parsed["access_token"].GetValue<string>();
|
||||||
var refreshToken = parsed["refresh_token"].Value<string>();
|
var refreshToken = parsed["refresh_token"].GetValue<string>();
|
||||||
var expiresIn = parsed["expires_in"].Value<long>();
|
var expiresIn = parsed["expires_in"].GetValue<long>();
|
||||||
|
|
||||||
var expirationDate = DateTime.UtcNow.AddSeconds(expiresIn);
|
var expirationDate = DateTime.UtcNow.AddSeconds(expiresIn);
|
||||||
|
|
||||||
@@ -76,12 +76,12 @@ namespace Wino.Core.Authenticators
|
|||||||
var userinfoResponse = await client.GetAsync(UserInfoEndpoint);
|
var userinfoResponse = await client.GetAsync(UserInfoEndpoint);
|
||||||
string userinfoResponseContent = await userinfoResponse.Content.ReadAsStringAsync();
|
string userinfoResponseContent = await userinfoResponse.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
var parsedUserInfo = JObject.Parse(userinfoResponseContent);
|
var parsedUserInfo = JsonNode.Parse(userinfoResponseContent).AsObject();
|
||||||
|
|
||||||
if (parsedUserInfo.ContainsKey("error"))
|
if (parsedUserInfo.ContainsKey("error"))
|
||||||
throw new GoogleAuthenticationException(parsedUserInfo["error"]["message"].Value<string>());
|
throw new GoogleAuthenticationException(parsedUserInfo["error"]["message"].GetValue<string>());
|
||||||
|
|
||||||
var username = parsedUserInfo["emailAddress"].Value<string>();
|
var username = parsedUserInfo["emailAddress"].GetValue<string>();
|
||||||
|
|
||||||
return new TokenInformation()
|
return new TokenInformation()
|
||||||
{
|
{
|
||||||
@@ -166,13 +166,13 @@ namespace Wino.Core.Authenticators
|
|||||||
|
|
||||||
string responseString = await response.Content.ReadAsStringAsync();
|
string responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
var parsed = JObject.Parse(responseString);
|
var parsed = JsonNode.Parse(responseString).AsObject();
|
||||||
|
|
||||||
// TODO: Error parsing is incorrect.
|
// TODO: Error parsing is incorrect.
|
||||||
if (parsed.ContainsKey("error"))
|
if (parsed.ContainsKey("error"))
|
||||||
throw new GoogleAuthenticationException(parsed["error_description"].Value<string>());
|
throw new GoogleAuthenticationException(parsed["error_description"].GetValue<string>());
|
||||||
|
|
||||||
var accessToken = parsed["access_token"].Value<string>();
|
var accessToken = parsed["access_token"].GetValue<string>();
|
||||||
|
|
||||||
string activeRefreshToken = refresh_token;
|
string activeRefreshToken = refresh_token;
|
||||||
|
|
||||||
@@ -182,10 +182,10 @@ namespace Wino.Core.Authenticators
|
|||||||
|
|
||||||
if (parsed.ContainsKey("refresh_token"))
|
if (parsed.ContainsKey("refresh_token"))
|
||||||
{
|
{
|
||||||
activeRefreshToken = parsed["refresh_token"].Value<string>();
|
activeRefreshToken = parsed["refresh_token"].GetValue<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var expiresIn = parsed["expires_in"].Value<long>();
|
var expiresIn = parsed["expires_in"].GetValue<long>();
|
||||||
var expirationDate = DateTime.UtcNow.AddSeconds(expiresIn);
|
var expirationDate = DateTime.UtcNow.AddSeconds(expiresIn);
|
||||||
|
|
||||||
return new TokenInformationBase()
|
return new TokenInformationBase()
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Google.Apis.Gmail.v1.Data;
|
using Google.Apis.Gmail.v1.Data;
|
||||||
|
using HtmlAgilityPack;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using MimeKit.IO;
|
using MimeKit.IO;
|
||||||
using MimeKit.IO.Filters;
|
using MimeKit.IO.Filters;
|
||||||
|
using MimeKit.Utils;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
|
|
||||||
@@ -48,5 +51,71 @@ namespace Wino.Core.Extensions
|
|||||||
|
|
||||||
return new AddressInformation() { Name = address.Name, Address = address.Address };
|
return new AddressInformation() { Name = address.Name, Address = address.Address };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets html body replacing base64 images with cid linked resources.
|
||||||
|
/// Updates text body based on html.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bodyBuilder">Body builder.</param>
|
||||||
|
/// <param name="htmlContent">Html content that can have embedded images.</param>
|
||||||
|
/// <returns>Body builder with set HtmlBody.</returns>
|
||||||
|
public static BodyBuilder SetHtmlBody(this BodyBuilder bodyBuilder, string htmlContent)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(htmlContent)) return bodyBuilder;
|
||||||
|
|
||||||
|
var doc = new HtmlDocument();
|
||||||
|
doc.LoadHtml(htmlContent);
|
||||||
|
|
||||||
|
var imgNodes = doc.DocumentNode.SelectNodes("//img");
|
||||||
|
|
||||||
|
if (imgNodes != null)
|
||||||
|
{
|
||||||
|
foreach (var node in imgNodes)
|
||||||
|
{
|
||||||
|
var src = node.GetAttributeValue("src", string.Empty);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(src)) continue;
|
||||||
|
|
||||||
|
if (!src.StartsWith("data:image"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = src.Substring(11).Split([";base64,"], StringSplitOptions.None);
|
||||||
|
|
||||||
|
string mimeType = parts[0];
|
||||||
|
string base64Content = parts[1];
|
||||||
|
|
||||||
|
var alt = node.GetAttributeValue("alt", $"Embedded_Image.{mimeType}");
|
||||||
|
|
||||||
|
// Convert the base64 content to binary data
|
||||||
|
byte[] imageData = Convert.FromBase64String(base64Content);
|
||||||
|
|
||||||
|
// Create a new linked resource as MimePart
|
||||||
|
var image = new MimePart("image", mimeType)
|
||||||
|
{
|
||||||
|
ContentId = MimeUtils.GenerateMessageId(),
|
||||||
|
Content = new MimeContent(new MemoryStream(imageData)),
|
||||||
|
ContentDisposition = new ContentDisposition(ContentDisposition.Inline),
|
||||||
|
ContentDescription = alt.Replace(" ", "_"),
|
||||||
|
FileName = alt,
|
||||||
|
ContentTransferEncoding = ContentEncoding.Base64
|
||||||
|
};
|
||||||
|
|
||||||
|
bodyBuilder.LinkedResources.Add(image);
|
||||||
|
|
||||||
|
node.SetAttributeValue("src", $"cid:{image.ContentId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBuilder.HtmlBody = doc.DocumentNode.InnerHtml;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(bodyBuilder.HtmlBody))
|
||||||
|
{
|
||||||
|
bodyBuilder.TextBody = HtmlAgilityPackExtensions.GetPreviewText(bodyBuilder.HtmlBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bodyBuilder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Wino.Core.Http
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// We need to generate HttpRequestMessage for batch requests, and sometimes we need to
|
|
||||||
/// serialize content as json. However, some of the fields like 'ODataType' must be ignored
|
|
||||||
/// in order PATCH requests to succeed. Therefore Microsoft account synchronizer uses
|
|
||||||
/// special JsonSerializerSettings for ignoring some of the properties.
|
|
||||||
/// </summary>
|
|
||||||
public class MicrosoftJsonContractResolver : DefaultContractResolver
|
|
||||||
{
|
|
||||||
private readonly HashSet<string> ignoreProps = new HashSet<string>()
|
|
||||||
{
|
|
||||||
"ODataType"
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
|
||||||
{
|
|
||||||
JsonProperty property = base.CreateProperty(member, memberSerialization);
|
|
||||||
|
|
||||||
if (ignoreProps.Contains(property.PropertyName))
|
|
||||||
{
|
|
||||||
property.ShouldSerialize = _ => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Requests
|
namespace Wino.Core.Domain.Models.Requests
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bundle that encapsulates batch request and native request without a response.
|
/// Bundle that encapsulates batch request and native request without a response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -43,7 +41,7 @@ namespace Wino.Core.Domain.Models.Requests
|
|||||||
{
|
{
|
||||||
var content = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
var content = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<TResponse>(content) ?? throw new InvalidOperationException("Invalid Http Response Deserialization");
|
return JsonSerializer.Deserialize<TResponse>(content) ?? throw new InvalidOperationException("Invalid Http Response Deserialization");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using Wino.Messaging.UI;
|
|||||||
|
|
||||||
namespace Wino.Core.Requests
|
namespace Wino.Core.Requests
|
||||||
{
|
{
|
||||||
public record CreateDraftRequest(DraftPreperationRequest DraftPreperationRequest)
|
public record CreateDraftRequest(DraftPreparationRequest DraftPreperationRequest)
|
||||||
: RequestBase<BatchCreateDraftRequest>(DraftPreperationRequest.CreatedLocalDraftCopy, MailSynchronizerOperation.CreateDraft),
|
: RequestBase<BatchCreateDraftRequest>(DraftPreperationRequest.CreatedLocalDraftCopy, MailSynchronizerOperation.CreateDraft),
|
||||||
ICustomFolderSynchronizationRequest
|
ICustomFolderSynchronizationRequest
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@ namespace Wino.Core.Requests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public record class BatchCreateDraftRequest(IEnumerable<IRequest> Items, DraftPreperationRequest DraftPreperationRequest)
|
public record class BatchCreateDraftRequest(IEnumerable<IRequest> Items, DraftPreparationRequest DraftPreperationRequest)
|
||||||
: BatchRequestBase(Items, MailSynchronizerOperation.CreateDraft)
|
: BatchRequestBase(Items, MailSynchronizerOperation.CreateDraft)
|
||||||
{
|
{
|
||||||
public override void ApplyUIChanges()
|
public override void ApplyUIChanges()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.AutoDiscovery;
|
using Wino.Core.Domain.Models.AutoDiscovery;
|
||||||
@@ -43,7 +43,7 @@ namespace Wino.Core.Services
|
|||||||
{
|
{
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<AutoDiscoverySettings>(content);
|
return JsonSerializer.Deserialize<AutoDiscoverySettings>(content);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
10
Wino.Core/Services/LaunchProtocolService.cs
Normal file
10
Wino.Core/Services/LaunchProtocolService.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
|
namespace Wino.Core.Services;
|
||||||
|
|
||||||
|
public class LaunchProtocolService : ILaunchProtocolService
|
||||||
|
{
|
||||||
|
public object LaunchParameter { get; set; }
|
||||||
|
public MailToUri MailToUri { get; set; }
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Kiota.Abstractions.Extensions;
|
using Microsoft.Kiota.Abstractions.Extensions;
|
||||||
@@ -32,7 +31,6 @@ namespace Wino.Core.Services
|
|||||||
private readonly IMimeFileService _mimeFileService;
|
private readonly IMimeFileService _mimeFileService;
|
||||||
private readonly IPreferencesService _preferencesService;
|
private readonly IPreferencesService _preferencesService;
|
||||||
|
|
||||||
|
|
||||||
private readonly ILogger _logger = Log.ForContext<MailService>();
|
private readonly ILogger _logger = Log.ForContext<MailService>();
|
||||||
|
|
||||||
public MailService(IDatabaseService databaseService,
|
public MailService(IDatabaseService databaseService,
|
||||||
@@ -53,18 +51,10 @@ namespace Wino.Core.Services
|
|||||||
_preferencesService = preferencesService;
|
_preferencesService = preferencesService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MailCopy> CreateDraftAsync(MailAccount composerAccount,
|
public async Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(Guid accountId, DraftCreationOptions draftCreationOptions)
|
||||||
string generatedReplyMimeMessageBase64,
|
|
||||||
MimeMessage replyingMimeMessage = null,
|
|
||||||
IMailItem replyingMailItem = null)
|
|
||||||
{
|
{
|
||||||
var createdDraftMimeMessage = generatedReplyMimeMessageBase64.GetMimeMessageFromBase64();
|
var composerAccount = await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
|
||||||
|
var createdDraftMimeMessage = await CreateDraftMimeAsync(composerAccount, draftCreationOptions);
|
||||||
bool isImapAccount = composerAccount.ServerInformation != null;
|
|
||||||
|
|
||||||
string fromName;
|
|
||||||
|
|
||||||
fromName = composerAccount.SenderName;
|
|
||||||
|
|
||||||
var draftFolder = await _folderService.GetSpecialFolderByAccountIdAsync(composerAccount.Id, SpecialFolderType.Draft);
|
var draftFolder = await _folderService.GetSpecialFolderByAccountIdAsync(composerAccount.Id, SpecialFolderType.Draft);
|
||||||
|
|
||||||
@@ -78,7 +68,7 @@ namespace Wino.Core.Services
|
|||||||
Id = Guid.NewGuid().ToString(), // This will be replaced after network call with the remote draft id.
|
Id = Guid.NewGuid().ToString(), // This will be replaced after network call with the remote draft id.
|
||||||
CreationDate = DateTime.UtcNow,
|
CreationDate = DateTime.UtcNow,
|
||||||
FromAddress = composerAccount.Address,
|
FromAddress = composerAccount.Address,
|
||||||
FromName = fromName,
|
FromName = composerAccount.SenderName,
|
||||||
HasAttachments = false,
|
HasAttachments = false,
|
||||||
Importance = MailImportance.Normal,
|
Importance = MailImportance.Normal,
|
||||||
Subject = createdDraftMimeMessage.Subject,
|
Subject = createdDraftMimeMessage.Subject,
|
||||||
@@ -93,28 +83,25 @@ namespace Wino.Core.Services
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If replying, add In-Reply-To, ThreadId and References.
|
// If replying, add In-Reply-To, ThreadId and References.
|
||||||
bool isReplying = replyingMimeMessage != null;
|
if (draftCreationOptions.ReferencedMessage != null)
|
||||||
|
|
||||||
if (isReplying)
|
|
||||||
{
|
{
|
||||||
if (replyingMimeMessage.References != null)
|
if (draftCreationOptions.ReferencedMessage.MimeMessage.References != null)
|
||||||
copy.References = string.Join(",", replyingMimeMessage.References);
|
copy.References = string.Join(",", draftCreationOptions.ReferencedMessage.MimeMessage.References);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(replyingMimeMessage.MessageId))
|
if (!string.IsNullOrEmpty(draftCreationOptions.ReferencedMessage.MimeMessage.MessageId))
|
||||||
copy.InReplyTo = replyingMimeMessage.MessageId;
|
copy.InReplyTo = draftCreationOptions.ReferencedMessage.MimeMessage.MessageId;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(replyingMailItem?.ThreadId))
|
if (!string.IsNullOrEmpty(draftCreationOptions.ReferencedMessage.MailCopy?.ThreadId))
|
||||||
copy.ThreadId = replyingMailItem.ThreadId;
|
copy.ThreadId = draftCreationOptions.ReferencedMessage.MailCopy.ThreadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Connection.InsertAsync(copy);
|
await Connection.InsertAsync(copy);
|
||||||
|
|
||||||
|
|
||||||
await _mimeFileService.SaveMimeMessageAsync(copy.FileId, createdDraftMimeMessage, composerAccount.Id);
|
await _mimeFileService.SaveMimeMessageAsync(copy.FileId, createdDraftMimeMessage, composerAccount.Id);
|
||||||
|
|
||||||
ReportUIChange(new DraftCreated(copy, composerAccount));
|
ReportUIChange(new DraftCreated(copy, composerAccount));
|
||||||
|
|
||||||
return copy;
|
return (copy, createdDraftMimeMessage.GetBase64MimeMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MailCopy>> GetMailsByFolderIdAsync(Guid folderId)
|
public async Task<List<MailCopy>> GetMailsByFolderIdAsync(Guid folderId)
|
||||||
@@ -629,85 +616,42 @@ namespace Wino.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions draftCreationOptions)
|
private async Task<MimeMessage> CreateDraftMimeAsync(MailAccount account, DraftCreationOptions draftCreationOptions)
|
||||||
{
|
{
|
||||||
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
|
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
|
||||||
// Same unique id will be used for the local copy as well.
|
// Same unique id will be used for the local copy as well.
|
||||||
// Synchronizer will map this unique id to the local draft copy after synchronization.
|
// Synchronizer will map this unique id to the local draft copy after synchronization.
|
||||||
|
|
||||||
var messageUniqueId = Guid.NewGuid();
|
|
||||||
|
|
||||||
var message = new MimeMessage()
|
var message = new MimeMessage()
|
||||||
{
|
{
|
||||||
Headers = { { Constants.WinoLocalDraftHeader, messageUniqueId.ToString() } }
|
Headers = { { Constants.WinoLocalDraftHeader, Guid.NewGuid().ToString() } },
|
||||||
|
From = { new MailboxAddress(account.SenderName, account.Address) }
|
||||||
};
|
};
|
||||||
|
|
||||||
var builder = new BodyBuilder();
|
var builder = new BodyBuilder();
|
||||||
|
|
||||||
var account = await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
|
var signature = await GetSignature(account, draftCreationOptions.Reason);
|
||||||
|
|
||||||
if (account == null)
|
_ = draftCreationOptions.Reason switch
|
||||||
{
|
{
|
||||||
_logger.Warning("Can't create draft mime message because account {AccountId} does not exist.", accountId);
|
DraftCreationReason.Empty => CreateEmptyDraft(builder, message, draftCreationOptions, signature),
|
||||||
|
_ => CreateReferencedDraft(builder, message, draftCreationOptions, account, signature),
|
||||||
|
};
|
||||||
|
|
||||||
return null;
|
builder.SetHtmlBody(builder.HtmlBody);
|
||||||
}
|
|
||||||
|
|
||||||
var reason = draftCreationOptions.Reason;
|
message.Body = builder.ToMessageBody();
|
||||||
var referenceMessage = draftCreationOptions.ReferenceMimeMessage;
|
|
||||||
|
|
||||||
message.From.Add(new MailboxAddress(account.SenderName, account.Address));
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
// It contains empty blocks with inlined font, to make sure when users starts typing,it will follow selected font.
|
private string CreateHtmlGap()
|
||||||
var gapHtml = CreateHtmlGap();
|
{
|
||||||
|
var template = $"""<div style="font-family: '{_preferencesService.ComposerFont}', Arial, sans-serif; font-size: {_preferencesService.ComposerFontSize}px"><br></div>""";
|
||||||
|
return string.Concat(Enumerable.Repeat(template, 2));
|
||||||
|
}
|
||||||
|
|
||||||
// Manage "To"
|
private async Task<string> GetSignature(MailAccount account, DraftCreationReason reason)
|
||||||
if (reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll)
|
{
|
||||||
{
|
|
||||||
// Reply to the sender of the message
|
|
||||||
|
|
||||||
if (referenceMessage.ReplyTo.Count > 0)
|
|
||||||
message.To.AddRange(referenceMessage.ReplyTo);
|
|
||||||
else if (referenceMessage.From.Count > 0)
|
|
||||||
message.To.AddRange(referenceMessage.From);
|
|
||||||
else if (referenceMessage.Sender != null)
|
|
||||||
message.To.Add(referenceMessage.Sender);
|
|
||||||
|
|
||||||
if (reason == DraftCreationReason.ReplyAll)
|
|
||||||
{
|
|
||||||
// Include all of the other original recipients
|
|
||||||
message.To.AddRange(referenceMessage.To);
|
|
||||||
|
|
||||||
// Find self and remove
|
|
||||||
var self = message.To.FirstOrDefault(a => a is MailboxAddress mailboxAddress && mailboxAddress.Address == account.Address);
|
|
||||||
|
|
||||||
if (self != null)
|
|
||||||
message.To.Remove(self);
|
|
||||||
|
|
||||||
message.Cc.AddRange(referenceMessage.Cc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manage "ThreadId-ConversationId"
|
|
||||||
if (!string.IsNullOrEmpty(referenceMessage.MessageId))
|
|
||||||
{
|
|
||||||
message.InReplyTo = referenceMessage.MessageId;
|
|
||||||
|
|
||||||
message.References.AddRange(referenceMessage.References);
|
|
||||||
|
|
||||||
message.References.Add(referenceMessage.MessageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
message.Headers.Add("Thread-Topic", referenceMessage.Subject);
|
|
||||||
|
|
||||||
builder.HtmlBody = CreateHtmlForReferencingMessage(referenceMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reason == DraftCreationReason.Forward)
|
|
||||||
{
|
|
||||||
builder.HtmlBody = CreateHtmlForReferencingMessage(referenceMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append signatures if needed.
|
|
||||||
if (account.Preferences.IsSignatureEnabled)
|
if (account.Preferences.IsSignatureEnabled)
|
||||||
{
|
{
|
||||||
var signatureId = reason == DraftCreationReason.Empty ?
|
var signatureId = reason == DraftCreationReason.Empty ?
|
||||||
@@ -718,26 +662,88 @@ namespace Wino.Core.Services
|
|||||||
{
|
{
|
||||||
var signature = await _signatureService.GetSignatureAsync(signatureId.Value);
|
var signature = await _signatureService.GetSignatureAsync(signatureId.Value);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(builder.HtmlBody))
|
return signature.HtmlBody;
|
||||||
{
|
|
||||||
builder.HtmlBody = $"{gapHtml}{signature.HtmlBody}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.HtmlBody = $"{gapHtml}{signature.HtmlBody}{gapHtml}{builder.HtmlBody}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MimeMessage CreateEmptyDraft(BodyBuilder builder, MimeMessage message, DraftCreationOptions draftCreationOptions, string signature)
|
||||||
|
{
|
||||||
|
builder.HtmlBody = CreateHtmlGap();
|
||||||
|
if (draftCreationOptions.MailToUri != null)
|
||||||
{
|
{
|
||||||
builder.HtmlBody = $"{gapHtml}{builder.HtmlBody}";
|
if (draftCreationOptions.MailToUri.Subject != null)
|
||||||
|
message.Subject = draftCreationOptions.MailToUri.Subject;
|
||||||
|
|
||||||
|
if (draftCreationOptions.MailToUri.Body != null)
|
||||||
|
{
|
||||||
|
builder.HtmlBody = $"""<div style="font-family: '{_preferencesService.ComposerFont}', Arial, sans-serif; font-size: {_preferencesService.ComposerFontSize}px">{draftCreationOptions.MailToUri.Body}</div>""" + builder.HtmlBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draftCreationOptions.MailToUri.To.Any())
|
||||||
|
message.To.AddRange(draftCreationOptions.MailToUri.To.Select(x => new MailboxAddress(x, x)));
|
||||||
|
|
||||||
|
if (draftCreationOptions.MailToUri.Cc.Any())
|
||||||
|
message.Cc.AddRange(draftCreationOptions.MailToUri.Cc.Select(x => new MailboxAddress(x, x)));
|
||||||
|
|
||||||
|
if (draftCreationOptions.MailToUri.Bcc.Any())
|
||||||
|
message.Bcc.AddRange(draftCreationOptions.MailToUri.Bcc.Select(x => new MailboxAddress(x, x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signature != null)
|
||||||
|
builder.HtmlBody += signature;
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MimeMessage CreateReferencedDraft(BodyBuilder builder, MimeMessage message, DraftCreationOptions draftCreationOptions, MailAccount account, string signature)
|
||||||
|
{
|
||||||
|
var reason = draftCreationOptions.Reason;
|
||||||
|
var referenceMessage = draftCreationOptions.ReferencedMessage.MimeMessage;
|
||||||
|
|
||||||
|
var gap = CreateHtmlGap();
|
||||||
|
builder.HtmlBody = gap + CreateHtmlForReferencingMessage(referenceMessage);
|
||||||
|
|
||||||
|
if (signature != null)
|
||||||
|
{
|
||||||
|
builder.HtmlBody = gap + signature + builder.HtmlBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage "To"
|
||||||
|
if (reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll)
|
||||||
|
{
|
||||||
|
// Reply to the sender of the message
|
||||||
|
if (referenceMessage.ReplyTo.Count > 0)
|
||||||
|
message.To.AddRange(referenceMessage.ReplyTo);
|
||||||
|
else if (referenceMessage.From.Count > 0)
|
||||||
|
message.To.AddRange(referenceMessage.From);
|
||||||
|
else if (referenceMessage.Sender != null)
|
||||||
|
message.To.Add(referenceMessage.Sender);
|
||||||
|
|
||||||
|
if (reason == DraftCreationReason.ReplyAll)
|
||||||
|
{
|
||||||
|
// Include all of the other original recipients
|
||||||
|
message.To.AddRange(referenceMessage.To.Where(x => x is MailboxAddress mailboxAddress && !mailboxAddress.Address.Equals(account.Address, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
message.Cc.AddRange(referenceMessage.Cc.Where(x => x is MailboxAddress mailboxAddress && !mailboxAddress.Address.Equals(account.Address, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage "ThreadId-ConversationId"
|
||||||
|
if (!string.IsNullOrEmpty(referenceMessage.MessageId))
|
||||||
|
{
|
||||||
|
message.InReplyTo = referenceMessage.MessageId;
|
||||||
|
message.References.AddRange(referenceMessage.References);
|
||||||
|
message.References.Add(referenceMessage.MessageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Headers.Add("Thread-Topic", referenceMessage.Subject);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manage Subject
|
// Manage Subject
|
||||||
if (reason == DraftCreationReason.Forward && !referenceMessage.Subject.StartsWith("FW: ", StringComparison.OrdinalIgnoreCase))
|
if (reason == DraftCreationReason.Forward && !referenceMessage.Subject.StartsWith("FW: ", StringComparison.OrdinalIgnoreCase))
|
||||||
message.Subject = $"FW: {referenceMessage.Subject}";
|
message.Subject = $"FW: {referenceMessage.Subject}";
|
||||||
else if ((reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll) &&
|
else if ((reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll) && !referenceMessage.Subject.StartsWith("RE: ", StringComparison.OrdinalIgnoreCase))
|
||||||
!referenceMessage.Subject.StartsWith("RE: ", StringComparison.OrdinalIgnoreCase))
|
|
||||||
message.Subject = $"RE: {referenceMessage.Subject}";
|
message.Subject = $"RE: {referenceMessage.Subject}";
|
||||||
else if (referenceMessage != null)
|
else if (referenceMessage != null)
|
||||||
message.Subject = referenceMessage.Subject;
|
message.Subject = referenceMessage.Subject;
|
||||||
@@ -751,63 +757,7 @@ namespace Wino.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(builder.HtmlBody))
|
return message;
|
||||||
{
|
|
||||||
builder.TextBody = HtmlAgilityPackExtensions.GetPreviewText(builder.HtmlBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
message.Body = builder.ToMessageBody();
|
|
||||||
|
|
||||||
// Apply mail-to protocol parameters if exists.
|
|
||||||
|
|
||||||
if (draftCreationOptions.MailtoParameters != null)
|
|
||||||
{
|
|
||||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoSubjectParameterKey, out string subjectParameter))
|
|
||||||
message.Subject = subjectParameter;
|
|
||||||
|
|
||||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoBodyParameterKey, out string bodyParameter))
|
|
||||||
{
|
|
||||||
builder.TextBody = bodyParameter;
|
|
||||||
builder.HtmlBody = bodyParameter;
|
|
||||||
|
|
||||||
message.Body = builder.ToMessageBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
static InternetAddressList ExtractRecipients(string parameterValue)
|
|
||||||
{
|
|
||||||
var list = new InternetAddressList();
|
|
||||||
|
|
||||||
var splittedRecipients = parameterValue.Split(',');
|
|
||||||
|
|
||||||
foreach (var recipient in splittedRecipients)
|
|
||||||
list.Add(new MailboxAddress(recipient, recipient));
|
|
||||||
|
|
||||||
return list;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoToParameterKey, out string toParameter))
|
|
||||||
message.To.AddRange(ExtractRecipients(toParameter));
|
|
||||||
|
|
||||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoCCParameterKey, out string ccParameter))
|
|
||||||
message.Cc.AddRange(ExtractRecipients(ccParameter));
|
|
||||||
|
|
||||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoBCCParameterKey, out string bccParameter))
|
|
||||||
message.Bcc.AddRange(ExtractRecipients(bccParameter));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Update TextBody from existing HtmlBody if exists.
|
|
||||||
}
|
|
||||||
|
|
||||||
using MemoryStream memoryStream = new();
|
|
||||||
message.WriteTo(FormatOptions.Default, memoryStream);
|
|
||||||
byte[] buffer = memoryStream.GetBuffer();
|
|
||||||
int count = (int)memoryStream.Length;
|
|
||||||
|
|
||||||
return Convert.ToBase64String(buffer);
|
|
||||||
|
|
||||||
// return message;
|
|
||||||
|
|
||||||
// Generates html representation of To/Cc/From/Time and so on from referenced message.
|
// Generates html representation of To/Cc/From/Time and so on from referenced message.
|
||||||
string CreateHtmlForReferencingMessage(MimeMessage referenceMessage)
|
string CreateHtmlForReferencingMessage(MimeMessage referenceMessage)
|
||||||
@@ -820,28 +770,22 @@ namespace Wino.Core.Services
|
|||||||
visitor.Visit(referenceMessage);
|
visitor.Visit(referenceMessage);
|
||||||
|
|
||||||
htmlMimeInfo += $"""
|
htmlMimeInfo += $"""
|
||||||
<div id="divRplyFwdMsg" dir="ltr">
|
<div id="divRplyFwdMsg" dir="ltr">
|
||||||
<font face="Calibri, sans-serif" style="font-size: 11pt;" color="#000000">
|
<font face="Calibri, sans-serif" style="font-size: 11pt;" color="#000000">
|
||||||
<b>From:</b> {ParticipantsToHtml(referenceMessage.From)}<br>
|
<b>From:</b> {ParticipantsToHtml(referenceMessage.From)}<br>
|
||||||
<b>Sent:</b> {referenceMessage.Date.ToLocalTime()}<br>
|
<b>Sent:</b> {referenceMessage.Date.ToLocalTime()}<br>
|
||||||
<b>To:</b> {ParticipantsToHtml(referenceMessage.To)}<br>
|
<b>To:</b> {ParticipantsToHtml(referenceMessage.To)}<br>
|
||||||
{(referenceMessage.Cc.Count > 0 ? $"<b>Cc:</b> {ParticipantsToHtml(referenceMessage.Cc)}<br>" : string.Empty)}
|
{(referenceMessage.Cc.Count > 0 ? $"<b>Cc:</b> {ParticipantsToHtml(referenceMessage.Cc)}<br>" : string.Empty)}
|
||||||
<b>Subject:</b> {referenceMessage.Subject}
|
<b>Subject:</b> {referenceMessage.Subject}
|
||||||
</font>
|
</font>
|
||||||
<div> </div>
|
<div> </div>
|
||||||
{visitor.HtmlBody}
|
{visitor.HtmlBody}
|
||||||
</div>
|
</div>
|
||||||
""";
|
""";
|
||||||
|
|
||||||
return htmlMimeInfo;
|
return htmlMimeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
string CreateHtmlGap()
|
|
||||||
{
|
|
||||||
var template = $"""<div style="font-family: '{_preferencesService.ComposerFont}', Arial, sans-serif; font-size: {_preferencesService.ComposerFontSize}px"><br></div>""";
|
|
||||||
return string.Concat(Enumerable.Repeat(template, 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
static string ParticipantsToHtml(InternetAddressList internetAddresses) =>
|
static string ParticipantsToHtml(InternetAddressList internetAddresses) =>
|
||||||
string.Join("; ", internetAddresses.Mailboxes
|
string.Join("; ", internetAddresses.Mailboxes
|
||||||
.Select(x => $"{x.Name ?? Translator.UnknownSender} <<a href=\"mailto:{x.Address ?? Translator.UnknownAddress}\">{x.Address ?? Translator.UnknownAddress}</a>>"));
|
.Select(x => $"{x.Name ?? Translator.UnknownSender} <<a href=\"mailto:{x.Address ?? Translator.UnknownAddress}\">{x.Address ?? Translator.UnknownAddress}</a>>"));
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
@@ -44,7 +44,7 @@ namespace Wino.Core.Services
|
|||||||
|
|
||||||
var stremValue = await new StreamReader(resourceStream).ReadToEndAsync().ConfigureAwait(false);
|
var stremValue = await new StreamReader(resourceStream).ReadToEndAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var translationLookups = JsonConvert.DeserializeObject<Dictionary<string, string>>(stremValue);
|
var translationLookups = JsonSerializer.Deserialize<Dictionary<string, string>>(stremValue);
|
||||||
|
|
||||||
// Insert new translation key-value pairs.
|
// Insert new translation key-value pairs.
|
||||||
// Overwrite existing values for the same keys.
|
// Overwrite existing values for the same keys.
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace Wino.Core.Services
|
|||||||
await QueueRequestAsync(accountRequest, accountId.Key);
|
await QueueRequestAsync(accountRequest, accountId.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QueueSynchronization(accountId.Key);
|
await QueueSynchronizationAsync(accountId.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,15 +108,15 @@ namespace Wino.Core.Services
|
|||||||
if (request == null) return;
|
if (request == null) return;
|
||||||
|
|
||||||
await QueueRequestAsync(request, accountId);
|
await QueueRequestAsync(request, accountId);
|
||||||
QueueSynchronization(accountId);
|
await QueueSynchronizationAsync(accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
|
public async Task ExecuteAsync(DraftPreparationRequest draftPreperationRequest)
|
||||||
{
|
{
|
||||||
var request = new CreateDraftRequest(draftPreperationRequest);
|
var request = new CreateDraftRequest(draftPreperationRequest);
|
||||||
|
|
||||||
await QueueRequestAsync(request, draftPreperationRequest.Account.Id);
|
await QueueRequestAsync(request, draftPreperationRequest.Account.Id);
|
||||||
QueueSynchronization(draftPreperationRequest.Account.Id);
|
await QueueSynchronizationAsync(draftPreperationRequest.Account.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
|
public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
|
||||||
@@ -124,23 +124,26 @@ namespace Wino.Core.Services
|
|||||||
var request = new SendDraftRequest(sendDraftPreperationRequest);
|
var request = new SendDraftRequest(sendDraftPreperationRequest);
|
||||||
|
|
||||||
await QueueRequestAsync(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
await QueueRequestAsync(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
||||||
QueueSynchronization(sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
await QueueSynchronizationAsync(sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await EnsureServerConnectedAsync();
|
||||||
await _winoServerConnectionManager.QueueRequestAsync(request, accountId);
|
await _winoServerConnectionManager.QueueRequestAsync(request, accountId);
|
||||||
}
|
}
|
||||||
catch (WinoServerException serverException)
|
catch (WinoServerException serverException)
|
||||||
{
|
{
|
||||||
_dialogService.InfoBarMessage("", serverException.Message, InfoBarMessageType.Error);
|
_dialogService.InfoBarMessage("Wino Server Exception", serverException.Message, InfoBarMessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueSynchronization(Guid accountId)
|
private async Task QueueSynchronizationAsync(Guid accountId)
|
||||||
{
|
{
|
||||||
|
await EnsureServerConnectedAsync();
|
||||||
|
|
||||||
var options = new SynchronizationOptions()
|
var options = new SynchronizationOptions()
|
||||||
{
|
{
|
||||||
AccountId = accountId,
|
AccountId = accountId,
|
||||||
@@ -149,5 +152,12 @@ namespace Wino.Core.Services
|
|||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
|
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EnsureServerConnectedAsync()
|
||||||
|
{
|
||||||
|
if (_winoServerConnectionManager.Status == WinoServerConnectionStatus.Connected) return;
|
||||||
|
|
||||||
|
await _winoServerConnectionManager.ConnectAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -922,7 +922,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In case of the high input, we'll batch them by 50 to reflect changes quickly.
|
// In case of the high input, we'll batch them by 50 to reflect changes quickly.
|
||||||
var batchedMissingMailIds = missingMailIds.Batch(50).Select(a => new UniqueIdSet(a, SortOrder.Descending));
|
var batchedMissingMailIds = missingMailIds.Batch(50).Select(a => new UniqueIdSet(a, SortOrder.Ascending));
|
||||||
|
|
||||||
foreach (var batchMissingMailIds in batchedMissingMailIds)
|
foreach (var batchMissingMailIds in batchedMissingMailIds)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.62.0" />
|
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.62.0" />
|
||||||
<PackageReference Include="MimeKit" Version="4.7.1" />
|
<PackageReference Include="MimeKit" Version="4.7.1" />
|
||||||
<PackageReference Include="morelinq" Version="4.1.0" />
|
<PackageReference Include="morelinq" Version="4.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
||||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
||||||
|
|||||||
@@ -236,14 +236,14 @@ namespace Wino.Mail.ViewModels
|
|||||||
await ProcessLaunchOptionsAsync();
|
await ProcessLaunchOptionsAsync();
|
||||||
|
|
||||||
await ForceAllAccountSynchronizationsAsync();
|
await ForceAllAccountSynchronizationsAsync();
|
||||||
await ConfigureBackgroundTasksAsync();
|
ConfigureBackgroundTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ConfigureBackgroundTasksAsync()
|
private void ConfigureBackgroundTasks()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _backgroundTaskService.HandleBackgroundTaskRegistrations();
|
_backgroundTaskService.UnregisterAllBackgroundTask();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -302,7 +302,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool hasMailtoActivation = _launchProtocolService.MailtoParameters != null;
|
bool hasMailtoActivation = _launchProtocolService.MailToUri != null;
|
||||||
|
|
||||||
if (hasMailtoActivation)
|
if (hasMailtoActivation)
|
||||||
{
|
{
|
||||||
@@ -774,16 +774,13 @@ namespace Wino.Mail.ViewModels
|
|||||||
var draftOptions = new DraftCreationOptions
|
var draftOptions = new DraftCreationOptions
|
||||||
{
|
{
|
||||||
Reason = DraftCreationReason.Empty,
|
Reason = DraftCreationReason.Empty,
|
||||||
|
MailToUri = _launchProtocolService.MailToUri
|
||||||
// Include mail to parameters for parsing mailto if any.
|
|
||||||
MailtoParameters = _launchProtocolService.MailtoParameters
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var createdBase64EncodedMimeMessage = await _mailService.CreateDraftMimeBase64Async(account.Id, draftOptions).ConfigureAwait(false);
|
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account.Id, draftOptions).ConfigureAwait(false);
|
||||||
var createdDraftMailMessage = await _mailService.CreateDraftAsync(account, createdBase64EncodedMimeMessage).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var draftPreperationRequest = new DraftPreperationRequest(account, createdDraftMailMessage, createdBase64EncodedMimeMessage);
|
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage);
|
||||||
await _winoRequestDelegator.ExecuteAsync(draftPreperationRequest);
|
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async void OnAccountUpdated(MailAccount updatedAccount)
|
protected override async void OnAccountUpdated(MailAccount updatedAccount)
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
|
using MimeKit.Utils;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
@@ -58,7 +58,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
private MessageImportance selectedMessageImportance;
|
private MessageImportance selectedMessageImportance;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool isCCBCCVisible = true;
|
private bool isCCBCCVisible;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string subject;
|
private string subject;
|
||||||
@@ -77,21 +77,20 @@ namespace Wino.Mail.ViewModels
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool isDraggingOverImagesDropZone;
|
private bool isDraggingOverImagesDropZone;
|
||||||
|
|
||||||
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = new ObservableCollection<MailAttachmentViewModel>();
|
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = [];
|
||||||
|
public ObservableCollection<MailAccount> Accounts { get; set; } = [];
|
||||||
public ObservableCollection<MailAccount> Accounts { get; set; } = new ObservableCollection<MailAccount>();
|
public ObservableCollection<AddressInformation> ToItems { get; set; } = [];
|
||||||
public ObservableCollection<AddressInformation> ToItems { get; set; } = new ObservableCollection<AddressInformation>();
|
public ObservableCollection<AddressInformation> CCItems { get; set; } = [];
|
||||||
public ObservableCollection<AddressInformation> CCItemsItems { get; set; } = new ObservableCollection<AddressInformation>();
|
public ObservableCollection<AddressInformation> BCCItems { get; set; } = [];
|
||||||
public ObservableCollection<AddressInformation> BCCItems { get; set; } = new ObservableCollection<AddressInformation>();
|
|
||||||
|
|
||||||
|
|
||||||
public List<EditorToolbarSection> ToolbarSections { get; set; } = new List<EditorToolbarSection>()
|
public List<EditorToolbarSection> ToolbarSections { get; set; } =
|
||||||
{
|
[
|
||||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Format },
|
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Format },
|
||||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Insert },
|
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Insert },
|
||||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Draw },
|
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Draw },
|
||||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Options }
|
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Options }
|
||||||
};
|
];
|
||||||
|
|
||||||
private EditorToolbarSection selectedToolbarSection;
|
private EditorToolbarSection selectedToolbarSection;
|
||||||
|
|
||||||
@@ -190,7 +189,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
// Save recipients.
|
// Save recipients.
|
||||||
|
|
||||||
SaveAddressInfo(ToItems, CurrentMimeMessage.To);
|
SaveAddressInfo(ToItems, CurrentMimeMessage.To);
|
||||||
SaveAddressInfo(CCItemsItems, CurrentMimeMessage.Cc);
|
SaveAddressInfo(CCItems, CurrentMimeMessage.Cc);
|
||||||
SaveAddressInfo(BCCItems, CurrentMimeMessage.Bcc);
|
SaveAddressInfo(BCCItems, CurrentMimeMessage.Bcc);
|
||||||
|
|
||||||
SaveImportance();
|
SaveImportance();
|
||||||
@@ -239,17 +238,9 @@ namespace Wino.Mail.ViewModels
|
|||||||
{
|
{
|
||||||
if (GetHTMLBodyFunction != null)
|
if (GetHTMLBodyFunction != null)
|
||||||
{
|
{
|
||||||
var htmlBody = await GetHTMLBodyFunction();
|
bodyBuilder.SetHtmlBody(await GetHTMLBodyFunction());
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(htmlBody))
|
|
||||||
{
|
|
||||||
bodyBuilder.HtmlBody = Regex.Unescape(htmlBody);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(bodyBuilder.HtmlBody))
|
|
||||||
bodyBuilder.TextBody = HtmlAgilityPackExtensions.GetPreviewText(bodyBuilder.HtmlBody);
|
|
||||||
|
|
||||||
if (bodyBuilder.HtmlBody != null && bodyBuilder.TextBody != null)
|
if (bodyBuilder.HtmlBody != null && bodyBuilder.TextBody != null)
|
||||||
CurrentMimeMessage.Body = bodyBuilder.ToMessageBody();
|
CurrentMimeMessage.Body = bodyBuilder.ToMessageBody();
|
||||||
}
|
}
|
||||||
@@ -309,7 +300,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
|
|
||||||
// Check if there is any delivering mail address from protocol launch.
|
// Check if there is any delivering mail address from protocol launch.
|
||||||
|
|
||||||
if (_launchProtocolService.MailtoParameters != null)
|
if (_launchProtocolService.MailToUri != null)
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
//var requestedMailContact = await GetAddressInformationAsync(_launchProtocolService.MailtoParameters, ToItems);
|
//var requestedMailContact = await GetAddressInformationAsync(_launchProtocolService.MailtoParameters, ToItems);
|
||||||
@@ -322,7 +313,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
// DialogService.InfoBarMessage("Invalid Address", "Address is not a valid e-mail address.", InfoBarMessageType.Warning);
|
// DialogService.InfoBarMessage("Invalid Address", "Address is not a valid e-mail address.", InfoBarMessageType.Warning);
|
||||||
|
|
||||||
// Clear the address.
|
// Clear the address.
|
||||||
_launchProtocolService.MailtoParameters = null;
|
_launchProtocolService.MailToUri = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,7 +399,6 @@ namespace Wino.Mail.ViewModels
|
|||||||
{
|
{
|
||||||
DialogService.InfoBarMessage("Busy", "Mail is being processed. Please wait a moment and try again.", InfoBarMessageType.Warning);
|
DialogService.InfoBarMessage("Busy", "Mail is being processed. Please wait a moment and try again.", InfoBarMessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (ComposerMimeNotFoundException)
|
catch (ComposerMimeNotFoundException)
|
||||||
{
|
{
|
||||||
DialogService.InfoBarMessage(Translator.Info_ComposerMissingMIMETitle, Translator.Info_ComposerMissingMIMEMessage, InfoBarMessageType.Error);
|
DialogService.InfoBarMessage(Translator.Info_ComposerMissingMIMETitle, Translator.Info_ComposerMissingMIMEMessage, InfoBarMessageType.Error);
|
||||||
@@ -427,15 +417,18 @@ namespace Wino.Mail.ViewModels
|
|||||||
// Extract information
|
// Extract information
|
||||||
|
|
||||||
ToItems.Clear();
|
ToItems.Clear();
|
||||||
CCItemsItems.Clear();
|
CCItems.Clear();
|
||||||
BCCItems.Clear();
|
BCCItems.Clear();
|
||||||
|
|
||||||
LoadAddressInfo(replyingMime.To, ToItems);
|
LoadAddressInfo(replyingMime.To, ToItems);
|
||||||
LoadAddressInfo(replyingMime.Cc, CCItemsItems);
|
LoadAddressInfo(replyingMime.Cc, CCItems);
|
||||||
LoadAddressInfo(replyingMime.Bcc, BCCItems);
|
LoadAddressInfo(replyingMime.Bcc, BCCItems);
|
||||||
|
|
||||||
LoadAttachments(replyingMime.Attachments);
|
LoadAttachments(replyingMime.Attachments);
|
||||||
|
|
||||||
|
if (replyingMime.Cc.Any() || replyingMime.Bcc.Any())
|
||||||
|
IsCCBCCVisible = true;
|
||||||
|
|
||||||
Subject = replyingMime.Subject;
|
Subject = replyingMime.Subject;
|
||||||
|
|
||||||
CurrentMimeMessage = replyingMime;
|
CurrentMimeMessage = replyingMime;
|
||||||
|
|||||||
@@ -610,6 +610,9 @@ namespace Wino.Mail.ViewModels
|
|||||||
|
|
||||||
if (ActiveFolder == null) return;
|
if (ActiveFolder == null) return;
|
||||||
|
|
||||||
|
// At least accounts must match.
|
||||||
|
if (ActiveFolder.HandlingFolders.Any(a => a.MailAccountId != addedMail.AssignedAccount.Id)) return;
|
||||||
|
|
||||||
// Messages coming to sent or draft folder must be inserted regardless of the filter.
|
// Messages coming to sent or draft folder must be inserted regardless of the filter.
|
||||||
bool shouldPreventIgnoringFilter = addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Draft ||
|
bool shouldPreventIgnoringFilter = addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Draft ||
|
||||||
addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Sent;
|
addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Sent;
|
||||||
@@ -617,6 +620,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
// Item does not belong to this folder and doesn't have special type to be inserted.
|
// Item does not belong to this folder and doesn't have special type to be inserted.
|
||||||
if (!shouldPreventIgnoringFilter && !ActiveFolder.HandlingFolders.Any(a => a.Id == addedMail.AssignedFolder.Id)) return;
|
if (!shouldPreventIgnoringFilter && !ActiveFolder.HandlingFolders.Any(a => a.Id == addedMail.AssignedFolder.Id)) return;
|
||||||
|
|
||||||
|
// Item should be prevented from being added to the list due to filter.
|
||||||
if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return;
|
if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return;
|
||||||
|
|
||||||
await MailCollection.AddAsync(addedMail);
|
await MailCollection.AddAsync(addedMail);
|
||||||
@@ -845,9 +849,6 @@ namespace Wino.Mail.ViewModels
|
|||||||
trackingSynchronizationId = null;
|
trackingSynchronizationId = null;
|
||||||
completedTrackingSynchronizationCount = 0;
|
completedTrackingSynchronizationCount = 0;
|
||||||
|
|
||||||
// Check whether the account synchronizer that this folder belongs to is already in synchronization.
|
|
||||||
await CheckIfAccountIsSynchronizingAsync();
|
|
||||||
|
|
||||||
// Notify change for archive-unarchive app bar button.
|
// Notify change for archive-unarchive app bar button.
|
||||||
OnPropertyChanged(nameof(IsArchiveSpecialFolder));
|
OnPropertyChanged(nameof(IsArchiveSpecialFolder));
|
||||||
|
|
||||||
@@ -865,6 +866,9 @@ namespace Wino.Mail.ViewModels
|
|||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check whether the account synchronizer that this folder belongs to is already in synchronization.
|
||||||
|
await CheckIfAccountIsSynchronizingAsync();
|
||||||
|
|
||||||
// Let awaiters know about the completion of mail init.
|
// Let awaiters know about the completion of mail init.
|
||||||
message.FolderInitLoadAwaitTask?.TrySetResult(true);
|
message.FolderInitLoadAwaitTask?.TrySetResult(true);
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public INativeAppService NativeAppService { get; }
|
public INativeAppService NativeAppService { get; }
|
||||||
public IStatePersistanceService StatePersistanceService { get; }
|
public IStatePersistanceService StatePersistenceService { get; }
|
||||||
public IPreferencesService PreferencesService { get; }
|
public IPreferencesService PreferencesService { get; }
|
||||||
|
|
||||||
public MailRenderingPageViewModel(IDialogService dialogService,
|
public MailRenderingPageViewModel(IDialogService dialogService,
|
||||||
@@ -127,14 +127,14 @@ namespace Wino.Mail.ViewModels
|
|||||||
Core.Domain.Interfaces.IMailService mailService,
|
Core.Domain.Interfaces.IMailService mailService,
|
||||||
IFileService fileService,
|
IFileService fileService,
|
||||||
IWinoRequestDelegator requestDelegator,
|
IWinoRequestDelegator requestDelegator,
|
||||||
IStatePersistanceService statePersistanceService,
|
IStatePersistanceService statePersistenceService,
|
||||||
IClipboardService clipboardService,
|
IClipboardService clipboardService,
|
||||||
IUnsubscriptionService unsubscriptionService,
|
IUnsubscriptionService unsubscriptionService,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService,
|
||||||
IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
|
IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
|
||||||
{
|
{
|
||||||
NativeAppService = nativeAppService;
|
NativeAppService = nativeAppService;
|
||||||
StatePersistanceService = statePersistanceService;
|
StatePersistenceService = statePersistenceService;
|
||||||
PreferencesService = preferencesService;
|
PreferencesService = preferencesService;
|
||||||
_winoServerConnectionManager = winoServerConnectionManager;
|
_winoServerConnectionManager = winoServerConnectionManager;
|
||||||
_clipboardService = clipboardService;
|
_clipboardService = clipboardService;
|
||||||
@@ -255,37 +255,27 @@ namespace Wino.Mail.ViewModels
|
|||||||
if (initializedMailItemViewModel == null) return;
|
if (initializedMailItemViewModel == null) return;
|
||||||
|
|
||||||
// Create new draft.
|
// Create new draft.
|
||||||
var draftOptions = new DraftCreationOptions();
|
var draftOptions = new DraftCreationOptions()
|
||||||
|
|
||||||
if (operation == MailOperation.Reply)
|
|
||||||
draftOptions.Reason = DraftCreationReason.Reply;
|
|
||||||
else if (operation == MailOperation.ReplyAll)
|
|
||||||
draftOptions.Reason = DraftCreationReason.ReplyAll;
|
|
||||||
else if (operation == MailOperation.Forward)
|
|
||||||
draftOptions.Reason = DraftCreationReason.Forward;
|
|
||||||
|
|
||||||
// TODO: Separate mailto related stuff out of DraftCreationOptions and provide better
|
|
||||||
// model for draft preperation request. Right now it's a mess.
|
|
||||||
|
|
||||||
draftOptions.ReferenceMailCopy = initializedMailItemViewModel.MailCopy;
|
|
||||||
draftOptions.ReferenceMimeMessage = initializedMimeMessageInformation.MimeMessage;
|
|
||||||
|
|
||||||
var createdMimeMessage = await _mailService.CreateDraftMimeBase64Async(initializedMailItemViewModel.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var createdDraftMailMessage = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount,
|
|
||||||
createdMimeMessage,
|
|
||||||
initializedMimeMessageInformation.MimeMessage,
|
|
||||||
initializedMailItemViewModel).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var draftPreperationRequest = new DraftPreperationRequest(initializedMailItemViewModel.AssignedAccount,
|
|
||||||
createdDraftMailMessage,
|
|
||||||
createdMimeMessage)
|
|
||||||
{
|
{
|
||||||
ReferenceMimeMessage = initializedMimeMessageInformation.MimeMessage,
|
Reason = operation switch
|
||||||
ReferenceMailCopy = initializedMailItemViewModel.MailCopy
|
{
|
||||||
|
MailOperation.Reply => DraftCreationReason.Reply,
|
||||||
|
MailOperation.ReplyAll => DraftCreationReason.ReplyAll,
|
||||||
|
MailOperation.Forward => DraftCreationReason.Forward,
|
||||||
|
_ => DraftCreationReason.Empty
|
||||||
|
},
|
||||||
|
ReferencedMessage = new ReferencedMessage()
|
||||||
|
{
|
||||||
|
MimeMessage = initializedMimeMessageInformation.MimeMessage,
|
||||||
|
MailCopy = initializedMailItemViewModel.MailCopy
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await _requestDelegator.ExecuteAsync(draftPreperationRequest);
|
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, initializedMailItemViewModel.MailCopy);
|
||||||
|
|
||||||
|
await _requestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (initializedMailItemViewModel != null)
|
else if (initializedMailItemViewModel != null)
|
||||||
@@ -453,7 +443,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
|
|
||||||
OnPropertyChanged(nameof(IsImageRenderingDisabled));
|
OnPropertyChanged(nameof(IsImageRenderingDisabled));
|
||||||
|
|
||||||
StatePersistanceService.IsReadingMail = true;
|
StatePersistenceService.IsReadingMail = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,7 +467,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
Attachments.Clear();
|
Attachments.Clear();
|
||||||
MenuItems.Clear();
|
MenuItems.Clear();
|
||||||
|
|
||||||
StatePersistanceService.IsReadingMail = false;
|
StatePersistenceService.IsReadingMail = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadAddressInfo(InternetAddressList list, ObservableCollection<AddressInformation> collection)
|
private void LoadAddressInfo(InternetAddressList list, ObservableCollection<AddressInformation> collection)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Toolkit.Uwp.Helpers;
|
||||||
using Windows.ApplicationModel.Activation;
|
using Windows.ApplicationModel.Activation;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
@@ -9,7 +10,6 @@ using Windows.UI.Xaml.Controls;
|
|||||||
using Windows.UI.Xaml.Media.Animation;
|
using Windows.UI.Xaml.Media.Animation;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Helpers;
|
|
||||||
using Wino.Views;
|
using Wino.Views;
|
||||||
|
|
||||||
namespace Wino.Activation
|
namespace Wino.Activation
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Threading.Tasks;
|
using System;
|
||||||
using System.Web;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Windows.ApplicationModel.Activation;
|
using Windows.ApplicationModel.Activation;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
using Wino.Messaging.Client.Authorization;
|
using Wino.Messaging.Client.Authorization;
|
||||||
using Wino.Messaging.Client.Shell;
|
using Wino.Messaging.Client.Shell;
|
||||||
|
|
||||||
@@ -36,11 +37,7 @@ namespace Wino.Activation
|
|||||||
else if (protocolString.StartsWith(MailtoProtocolTag))
|
else if (protocolString.StartsWith(MailtoProtocolTag))
|
||||||
{
|
{
|
||||||
// mailto activation. Try to parse params.
|
// mailto activation. Try to parse params.
|
||||||
|
_launchProtocolService.MailToUri = new MailToUri(protocolString);
|
||||||
var replaced = protocolString.Replace(MailtoProtocolTag, "mailto=");
|
|
||||||
replaced = Wino.Core.Extensions.StringExtensions.ReplaceFirst(replaced, "?", "&");
|
|
||||||
|
|
||||||
_launchProtocolService.MailtoParameters = HttpUtility.ParseQueryString(replaced);
|
|
||||||
|
|
||||||
if (_nativeAppService.IsAppRunning())
|
if (_nativeAppService.IsAppRunning())
|
||||||
{
|
{
|
||||||
@@ -51,5 +48,21 @@ namespace Wino.Activation
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
// Validate the URI scheme.
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var uriGet = args.Uri;
|
||||||
|
}
|
||||||
|
catch (UriFormatException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CanHandleInternal(args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Microsoft.AppCenter;
|
|||||||
using Microsoft.AppCenter.Analytics;
|
using Microsoft.AppCenter.Analytics;
|
||||||
using Microsoft.AppCenter.Crashes;
|
using Microsoft.AppCenter.Crashes;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Nito.AsyncEx;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Windows.ApplicationModel;
|
using Windows.ApplicationModel;
|
||||||
using Windows.ApplicationModel.Activation;
|
using Windows.ApplicationModel.Activation;
|
||||||
@@ -66,7 +67,6 @@ namespace Wino
|
|||||||
private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>()
|
private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>()
|
||||||
{
|
{
|
||||||
_databaseService,
|
_databaseService,
|
||||||
_appServiceConnectionManager,
|
|
||||||
_translationService,
|
_translationService,
|
||||||
_themeService,
|
_themeService,
|
||||||
};
|
};
|
||||||
@@ -76,8 +76,6 @@ namespace Wino
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
UnhandledException += OnAppUnhandledException;
|
UnhandledException += OnAppUnhandledException;
|
||||||
EnteredBackground += OnEnteredBackground;
|
|
||||||
LeavingBackground += OnLeavingBackground;
|
|
||||||
|
|
||||||
Resuming += OnResuming;
|
Resuming += OnResuming;
|
||||||
Suspending += OnSuspending;
|
Suspending += OnSuspending;
|
||||||
@@ -125,8 +123,6 @@ namespace Wino
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}");
|
private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}");
|
||||||
private void OnLeavingBackground(object sender, LeavingBackgroundEventArgs e) => LogActivation($"Wino went foreground.");
|
|
||||||
private void OnEnteredBackground(object sender, EnteredBackgroundEventArgs e) => LogActivation($"Wino went background.");
|
|
||||||
private IServiceProvider ConfigureServices()
|
private IServiceProvider ConfigureServices()
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
@@ -146,7 +142,6 @@ namespace Wino
|
|||||||
private void RegisterActivationHandlers(IServiceCollection services)
|
private void RegisterActivationHandlers(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddTransient<ProtocolActivationHandler>();
|
services.AddTransient<ProtocolActivationHandler>();
|
||||||
// services.AddTransient<BackgroundActivationHandler>();
|
|
||||||
services.AddTransient<ToastNotificationActivationHandler>();
|
services.AddTransient<ToastNotificationActivationHandler>();
|
||||||
services.AddTransient<FileActivationHandler>();
|
services.AddTransient<FileActivationHandler>();
|
||||||
}
|
}
|
||||||
@@ -283,19 +278,18 @@ namespace Wino
|
|||||||
|
|
||||||
_appServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
|
_appServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstrablished());
|
WeakReferenceMessenger.Default.Send(new WinoServerConnectionEstablished());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail toastNotificationActionTriggerDetail)
|
else if (args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail toastNotificationActionTriggerDetail)
|
||||||
{
|
{
|
||||||
await InitializeServicesAsync();
|
|
||||||
|
|
||||||
// Notification action is triggered and the app is not running.
|
// Notification action is triggered and the app is not running.
|
||||||
|
|
||||||
toastActionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
|
toastActionBackgroundTaskDeferral = args.TaskInstance.GetDeferral();
|
||||||
|
|
||||||
args.TaskInstance.Canceled += OnToastActionClickedBackgroundTaskCanceled;
|
args.TaskInstance.Canceled += OnToastActionClickedBackgroundTaskCanceled;
|
||||||
|
|
||||||
|
await InitializeServicesAsync();
|
||||||
|
|
||||||
var toastArguments = ToastArguments.Parse(toastNotificationActionTriggerDetail.Argument);
|
var toastArguments = ToastArguments.Parse(toastNotificationActionTriggerDetail.Argument);
|
||||||
|
|
||||||
// All toast activation mail actions are handled here like mark as read or delete.
|
// All toast activation mail actions are handled here like mark as read or delete.
|
||||||
@@ -364,13 +358,7 @@ namespace Wino
|
|||||||
|
|
||||||
private bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs;
|
private bool IsInteractiveLaunchArgs(object args) => args is IActivatedEventArgs;
|
||||||
|
|
||||||
private async Task InitializeServicesAsync()
|
private Task InitializeServicesAsync() => initializeServices.Select(a => a.InitializeAsync()).WhenAll();
|
||||||
{
|
|
||||||
foreach (var service in initializeServices)
|
|
||||||
{
|
|
||||||
await service.InitializeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ActivateWinoAsync(object args)
|
private async Task ActivateWinoAsync(object args)
|
||||||
{
|
{
|
||||||
@@ -424,13 +412,11 @@ namespace Wino
|
|||||||
yield return Services.GetService<FileActivationHandler>();
|
yield return Services.GetService<FileActivationHandler>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||||
{
|
{
|
||||||
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
|
sender.Canceled -= OnConnectionBackgroundTaskCanceled;
|
||||||
|
|
||||||
Log.Information($"Background task {sender.Task.Name} was canceled. Reason: {reason}");
|
Log.Information($"Server connection background task was canceled. Reason: {reason}");
|
||||||
|
|
||||||
await _appServiceConnectionManager.DisconnectAsync();
|
|
||||||
|
|
||||||
connectionBackgroundTaskDeferral?.Complete();
|
connectionBackgroundTaskDeferral?.Complete();
|
||||||
connectionBackgroundTaskDeferral = null;
|
connectionBackgroundTaskDeferral = null;
|
||||||
|
|||||||
@@ -197,10 +197,7 @@ namespace Wino.Controls.Advanced
|
|||||||
private void ReconnectClicked(object sender, RoutedEventArgs e)
|
private void ReconnectClicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
// Close the popup for reconnect button.
|
// Close the popup for reconnect button.
|
||||||
if (sender is Button senderButton && senderButton.Flyout is Flyout senderButtonFlyout)
|
ReconnectFlyout.Hide();
|
||||||
{
|
|
||||||
senderButtonFlyout.Hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the reconnect command.
|
// Execute the reconnect command.
|
||||||
ReconnectCommand?.Execute(null);
|
ReconnectCommand?.Execute(null);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Web.WebView2.Core;
|
using Microsoft.Web.WebView2.Core;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Windows.UI.ViewManagement.Core;
|
using Windows.UI.ViewManagement.Core;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
@@ -78,7 +78,7 @@ namespace Wino.Dialogs
|
|||||||
{
|
{
|
||||||
var editorContent = await InvokeScriptSafeAsync("GetHTMLContent();");
|
var editorContent = await InvokeScriptSafeAsync("GetHTMLContent();");
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<string>(editorContent);
|
return JsonSerializer.Deserialize<string>(editorContent);
|
||||||
});
|
});
|
||||||
|
|
||||||
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
|
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
|
||||||
@@ -193,7 +193,7 @@ namespace Wino.Dialogs
|
|||||||
string script = functionName + "(";
|
string script = functionName + "(";
|
||||||
for (int i = 0; i < parameters.Length; i++)
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
script += JsonConvert.SerializeObject(parameters[i]);
|
script += JsonSerializer.Serialize(parameters[i]);
|
||||||
if (i < parameters.Length - 1)
|
if (i < parameters.Length - 1)
|
||||||
{
|
{
|
||||||
script += ", ";
|
script += ", ";
|
||||||
@@ -327,7 +327,7 @@ namespace Wino.Dialogs
|
|||||||
|
|
||||||
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
|
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
|
||||||
{
|
{
|
||||||
var change = JsonConvert.DeserializeObject<WebViewMessage>(args.WebMessageAsJson);
|
var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson);
|
||||||
|
|
||||||
if (change.Type == "bold")
|
if (change.Type == "bold")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Wino.Helpers
|
|
||||||
{
|
|
||||||
public static class JsonHelpers
|
|
||||||
{
|
|
||||||
public static async Task<T> ToObjectAsync<T>(string value)
|
|
||||||
{
|
|
||||||
return await Task.Run<T>(() =>
|
|
||||||
{
|
|
||||||
return JsonConvert.DeserializeObject<T>(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> StringifyAsync(object value)
|
|
||||||
{
|
|
||||||
return await Task.Run<string>(() =>
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Windows.Storage;
|
|
||||||
using Windows.Storage.Streams;
|
|
||||||
|
|
||||||
namespace Wino.Helpers
|
|
||||||
{
|
|
||||||
// Use these extension methods to store and retrieve local and roaming app data
|
|
||||||
// More details regarding storing and retrieving app data at https://docs.microsoft.com/windows/uwp/app-settings/store-and-retrieve-app-data
|
|
||||||
public static class SettingsStorageExtensions
|
|
||||||
{
|
|
||||||
private const string FileExtension = ".json";
|
|
||||||
|
|
||||||
public static bool IsRoamingStorageAvailable(this ApplicationData appData)
|
|
||||||
{
|
|
||||||
return appData.RoamingStorageQuota == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SaveAsync<T>(this StorageFolder folder, string name, T content)
|
|
||||||
{
|
|
||||||
var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting);
|
|
||||||
var fileContent = await JsonHelpers.StringifyAsync(content);
|
|
||||||
|
|
||||||
await FileIO.WriteTextAsync(file, fileContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<T> ReadAsync<T>(this StorageFolder folder, string name)
|
|
||||||
{
|
|
||||||
if (!File.Exists(Path.Combine(folder.Path, GetFileName(name))))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = await folder.GetFileAsync($"{name}.json");
|
|
||||||
var fileContent = await FileIO.ReadTextAsync(file);
|
|
||||||
|
|
||||||
return await JsonHelpers.ToObjectAsync<T>(fileContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SaveAsync<T>(this ApplicationDataContainer settings, string key, T value)
|
|
||||||
{
|
|
||||||
settings.SaveString(key, await JsonHelpers.StringifyAsync(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SaveString(this ApplicationDataContainer settings, string key, string value)
|
|
||||||
{
|
|
||||||
settings.Values[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<T> ReadAsync<T>(this ApplicationDataContainer settings, string key)
|
|
||||||
{
|
|
||||||
object obj = null;
|
|
||||||
|
|
||||||
if (settings.Values.TryGetValue(key, out obj))
|
|
||||||
{
|
|
||||||
return await JsonHelpers.ToObjectAsync<T>((string)obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<StorageFile> SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting)
|
|
||||||
{
|
|
||||||
if (content == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(fileName))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("File name is null or empty. Specify a valid file name", nameof(fileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
var storageFile = await folder.CreateFileAsync(fileName, options);
|
|
||||||
await FileIO.WriteBytesAsync(storageFile, content);
|
|
||||||
return storageFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<byte[]> ReadFileAsync(this StorageFolder folder, string fileName)
|
|
||||||
{
|
|
||||||
var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
|
|
||||||
|
|
||||||
if ((item != null) && item.IsOfType(StorageItemTypes.File))
|
|
||||||
{
|
|
||||||
var storageFile = await folder.GetFileAsync(fileName);
|
|
||||||
byte[] content = await storageFile.ReadBytesAsync();
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<byte[]> ReadBytesAsync(this StorageFile file)
|
|
||||||
{
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
using (IRandomAccessStream stream = await file.OpenReadAsync())
|
|
||||||
{
|
|
||||||
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
|
|
||||||
{
|
|
||||||
await reader.LoadAsync((uint)stream.Size);
|
|
||||||
var bytes = new byte[stream.Size];
|
|
||||||
reader.ReadBytes(bytes);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetFileName(string name)
|
|
||||||
{
|
|
||||||
return string.Concat(name, FileExtension);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -48,7 +48,7 @@ function initializeJodit(fonts, defaultComposerFont, defaultComposerFontSize, de
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (event) {
|
reader.onload = function (event) {
|
||||||
const base64Image = event.target.result;
|
const base64Image = event.target.result;
|
||||||
insertImages([base64Image]);
|
insertImages([{ data: base64Image, name: file.name }]);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
@@ -121,8 +121,8 @@ function toggleToolbar(enable) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertImages(images) {
|
function insertImages(imagesInfo) {
|
||||||
images.forEach(image => {
|
imagesInfo.forEach(imageInfo => {
|
||||||
editor.selection.insertHTML(`<img src="${image}" alt="Embedded Image">`);
|
editor.selection.insertHTML(`<img src="${imageInfo.data}" alt="${imageInfo.name}">`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,20 +50,13 @@
|
|||||||
Description="Mail client designed for Windows 11"
|
Description="Mail client designed for Windows 11"
|
||||||
BackgroundColor="transparent">
|
BackgroundColor="transparent">
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png"/>
|
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png"/>
|
||||||
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="transparent"/>
|
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="transparent" uap5:Optional="true" />
|
||||||
<uap:LockScreen BadgeLogo="Assets\BadgeLogo.png" Notification="badgeAndTileText"/>
|
<uap:LockScreen BadgeLogo="Assets\BadgeLogo.png" Notification="badgeAndTileText"/>
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<!-- App updated task. Notifies about new version after each Store update. -->
|
<!-- App updated task. Notifies about new version after each Store update. -->
|
||||||
<Extension Category="windows.updateTask" EntryPoint="Wino.BackgroundTasks.AppUpdatedTask" />
|
<Extension Category="windows.updateTask" EntryPoint="Wino.BackgroundTasks.AppUpdatedTask" />
|
||||||
|
|
||||||
<!-- SessionConnected task for background synchronization on startup. -->
|
|
||||||
<Extension Category="windows.backgroundTasks" EntryPoint="Wino.BackgroundTasks.SessionConnectedTask">
|
|
||||||
<BackgroundTasks>
|
|
||||||
<Task Type="systemEvent" />
|
|
||||||
</BackgroundTasks>
|
|
||||||
</Extension>
|
|
||||||
|
|
||||||
<!-- Protocol activation: mailto -->
|
<!-- Protocol activation: mailto -->
|
||||||
<uap:Extension Category="windows.protocol">
|
<uap:Extension Category="windows.protocol">
|
||||||
<uap:Protocol Name="mailto" />
|
<uap:Protocol Name="mailto" />
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Collections.Specialized;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
namespace Wino.Core.UWP.Services
|
|
||||||
{
|
|
||||||
public class LaunchProtocolService : ILaunchProtocolService
|
|
||||||
{
|
|
||||||
public object LaunchParameter { get; set; }
|
|
||||||
public NameValueCollection MailtoParameters { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -493,7 +493,7 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Click="ShowCCBCCClicked"
|
Click="ShowCCBCCClicked"
|
||||||
GotFocus="CCBBCGotFocus"
|
GotFocus="CCBBCGotFocus"
|
||||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}">
|
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsCCBCCVisible), Mode=OneWay}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
<PathIcon
|
<PathIcon
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
@@ -512,13 +512,14 @@
|
|||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="Cc: "
|
Text="Cc: "
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
<controls1:TokenizingTextBox
|
<controls1:TokenizingTextBox
|
||||||
x:Name="CCBox"
|
x:Name="CCBox"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
ItemsSource="{x:Bind ViewModel.CCItems, Mode=OneTime}"
|
||||||
LostFocus="AddressBoxLostFocus"
|
LostFocus="AddressBoxLostFocus"
|
||||||
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
||||||
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
||||||
@@ -526,7 +527,7 @@
|
|||||||
TokenDelimiter=";"
|
TokenDelimiter=";"
|
||||||
TokenItemAdding="TokenItemAdding"
|
TokenItemAdding="TokenItemAdding"
|
||||||
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="BccTextBlock"
|
x:Name="BccTextBlock"
|
||||||
@@ -534,13 +535,14 @@
|
|||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="Bcc: "
|
Text="Bcc: "
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
<controls1:TokenizingTextBox
|
<controls1:TokenizingTextBox
|
||||||
x:Name="BccBox"
|
x:Name="BccBox"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
ItemsSource="{x:Bind ViewModel.BCCItems, Mode=OneTime}"
|
||||||
LostFocus="AddressBoxLostFocus"
|
LostFocus="AddressBoxLostFocus"
|
||||||
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
||||||
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
||||||
@@ -548,7 +550,7 @@
|
|||||||
TokenDelimiter=";"
|
TokenDelimiter=";"
|
||||||
TokenItemAdding="TokenItemAdding"
|
TokenItemAdding="TokenItemAdding"
|
||||||
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
<!-- Subject -->
|
<!-- Subject -->
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
@@ -13,7 +14,6 @@ using Microsoft.Toolkit.Uwp.Helpers;
|
|||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.Web.WebView2.Core;
|
using Microsoft.Web.WebView2.Core;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Windows.ApplicationModel.DataTransfer;
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
@@ -167,7 +167,7 @@ namespace Wino.Views
|
|||||||
|
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
if (ValidateImageFile(file))
|
if (IsValidImageFile(file))
|
||||||
{
|
{
|
||||||
isValid = true;
|
isValid = true;
|
||||||
}
|
}
|
||||||
@@ -200,15 +200,21 @@ namespace Wino.Views
|
|||||||
var storageItems = await e.DataView.GetStorageItemsAsync();
|
var storageItems = await e.DataView.GetStorageItemsAsync();
|
||||||
var files = storageItems.OfType<StorageFile>();
|
var files = storageItems.OfType<StorageFile>();
|
||||||
|
|
||||||
var imageDataURLs = new List<string>();
|
var imagesInformation = new List<ImageInfo>();
|
||||||
|
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
if (ValidateImageFile(file))
|
if (IsValidImageFile(file))
|
||||||
imageDataURLs.Add(await GetDataURL(file));
|
{
|
||||||
|
imagesInformation.Add(new ImageInfo
|
||||||
|
{
|
||||||
|
Data = await GetDataURL(file),
|
||||||
|
Name = file.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await InvokeScriptSafeAsync($"insertImages({JsonConvert.SerializeObject(imageDataURLs)});");
|
await InvokeScriptSafeAsync($"insertImages({JsonSerializer.Serialize(imagesInformation)});");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// State should be reset even when an exception occurs, otherwise the UI will be stuck in a dragging state.
|
// State should be reset even when an exception occurs, otherwise the UI will be stuck in a dragging state.
|
||||||
@@ -240,7 +246,7 @@ namespace Wino.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateImageFile(StorageFile file)
|
private bool IsValidImageFile(StorageFile file)
|
||||||
{
|
{
|
||||||
string[] allowedTypes = new string[] { ".jpg", ".jpeg", ".png" };
|
string[] allowedTypes = new string[] { ".jpg", ".jpeg", ".png" };
|
||||||
var fileType = file.FileType.ToLower();
|
var fileType = file.FileType.ToLower();
|
||||||
@@ -321,7 +327,7 @@ namespace Wino.Views
|
|||||||
string script = functionName + "(";
|
string script = functionName + "(";
|
||||||
for (int i = 0; i < parameters.Length; i++)
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
script += JsonConvert.SerializeObject(parameters[i]);
|
script += JsonSerializer.Serialize(parameters[i]);
|
||||||
if (i < parameters.Length - 1)
|
if (i < parameters.Length - 1)
|
||||||
{
|
{
|
||||||
script += ", ";
|
script += ", ";
|
||||||
@@ -463,7 +469,7 @@ namespace Wino.Views
|
|||||||
{
|
{
|
||||||
var editorContent = await InvokeScriptSafeAsync("GetHTMLContent();");
|
var editorContent = await InvokeScriptSafeAsync("GetHTMLContent();");
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<string>(editorContent);
|
return JsonSerializer.Deserialize<string>(editorContent);
|
||||||
});
|
});
|
||||||
|
|
||||||
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
|
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
|
||||||
@@ -487,7 +493,7 @@ namespace Wino.Views
|
|||||||
|
|
||||||
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
|
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
|
||||||
{
|
{
|
||||||
var change = JsonConvert.DeserializeObject<WebViewMessage>(args.WebMessageAsJson);
|
var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson);
|
||||||
|
|
||||||
if (change.Type == "bold")
|
if (change.Type == "bold")
|
||||||
{
|
{
|
||||||
@@ -562,12 +568,7 @@ namespace Wino.Views
|
|||||||
|
|
||||||
private void ShowCCBCCClicked(object sender, RoutedEventArgs e)
|
private void ShowCCBCCClicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
CCBCCShowButton.Visibility = Visibility.Collapsed;
|
ViewModel.IsCCBCCVisible = true;
|
||||||
|
|
||||||
CCTextBlock.Visibility = Visibility.Visible;
|
|
||||||
CCBox.Visibility = Visibility.Visible;
|
|
||||||
BccTextBlock.Visibility = Visibility.Visible;
|
|
||||||
BccBox.Visibility = Visibility.Visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void TokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
|
private async void TokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
|
||||||
@@ -591,7 +592,7 @@ namespace Wino.Views
|
|||||||
if (boxTag == "ToBox")
|
if (boxTag == "ToBox")
|
||||||
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.ToItems);
|
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.ToItems);
|
||||||
else if (boxTag == "CCBox")
|
else if (boxTag == "CCBox")
|
||||||
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.CCItemsItems);
|
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.CCItems);
|
||||||
else if (boxTag == "BCCBox")
|
else if (boxTag == "BCCBox")
|
||||||
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.BCCItems);
|
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.BCCItems);
|
||||||
|
|
||||||
@@ -660,7 +661,7 @@ namespace Wino.Views
|
|||||||
if (boxTag == "ToBox")
|
if (boxTag == "ToBox")
|
||||||
addressCollection = ViewModel.ToItems;
|
addressCollection = ViewModel.ToItems;
|
||||||
else if (boxTag == "CCBox")
|
else if (boxTag == "CCBox")
|
||||||
addressCollection = ViewModel.CCItemsItems;
|
addressCollection = ViewModel.CCItems;
|
||||||
else if (boxTag == "BCCBox")
|
else if (boxTag == "BCCBox")
|
||||||
addressCollection = ViewModel.BCCItems;
|
addressCollection = ViewModel.BCCItems;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.AppCenter.Crashes;
|
using Microsoft.AppCenter.Crashes;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.Web.WebView2.Core;
|
using Microsoft.Web.WebView2.Core;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Windows.System;
|
using Windows.System;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
@@ -70,7 +70,7 @@ namespace Wino.Views
|
|||||||
string script = functionName + "(";
|
string script = functionName + "(";
|
||||||
for (int i = 0; i < parameters.Length; i++)
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
script += JsonConvert.SerializeObject(parameters[i]);
|
script += JsonSerializer.Serialize(parameters[i]);
|
||||||
if (i < parameters.Length - 1)
|
if (i < parameters.Length - 1)
|
||||||
{
|
{
|
||||||
script += ", ";
|
script += ", ";
|
||||||
@@ -174,7 +174,7 @@ namespace Wino.Views
|
|||||||
// We don't have shell initialized here. It's only standalone EML viewing.
|
// We don't have shell initialized here. It's only standalone EML viewing.
|
||||||
// Shift command bar from top to adjust the design.
|
// Shift command bar from top to adjust the design.
|
||||||
|
|
||||||
if (ViewModel.StatePersistanceService.ShouldShiftMailRenderingDesign)
|
if (ViewModel.StatePersistenceService.ShouldShiftMailRenderingDesign)
|
||||||
RendererGridFrame.Margin = new Thickness(0, 24, 0, 0);
|
RendererGridFrame.Margin = new Thickness(0, 24, 0, 0);
|
||||||
else
|
else
|
||||||
RendererGridFrame.Margin = new Thickness(0, 0, 0, 0);
|
RendererGridFrame.Margin = new Thickness(0, 0, 0, 0);
|
||||||
|
|||||||
@@ -298,14 +298,12 @@
|
|||||||
<Compile Include="Dialogs\AccountCreationDialog.xaml.cs">
|
<Compile Include="Dialogs\AccountCreationDialog.xaml.cs">
|
||||||
<DependentUpon>AccountCreationDialog.xaml</DependentUpon>
|
<DependentUpon>AccountCreationDialog.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Helpers\JsonHelpers.cs" />
|
|
||||||
<Compile Include="Extensions\AnimationExtensions.cs" />
|
<Compile Include="Extensions\AnimationExtensions.cs" />
|
||||||
<Compile Include="Extensions\CompositionExtensions.Implicit.cs" />
|
<Compile Include="Extensions\CompositionExtensions.Implicit.cs" />
|
||||||
<Compile Include="Extensions\CompositionExtensions.Size.cs" />
|
<Compile Include="Extensions\CompositionExtensions.Size.cs" />
|
||||||
<Compile Include="Extensions\CompositionEnums.cs" />
|
<Compile Include="Extensions\CompositionEnums.cs" />
|
||||||
<Compile Include="Extensions\EnumerableExtensions.cs" />
|
<Compile Include="Extensions\EnumerableExtensions.cs" />
|
||||||
<Compile Include="Extensions\UtilExtensions.cs" />
|
<Compile Include="Extensions\UtilExtensions.cs" />
|
||||||
<Compile Include="Helpers\SettingsStorageExtensions.cs" />
|
|
||||||
<Compile Include="MenuFlyouts\FilterMenuFlyout.cs" />
|
<Compile Include="MenuFlyouts\FilterMenuFlyout.cs" />
|
||||||
<Compile Include="Controls\ImagePreviewControl.cs" />
|
<Compile Include="Controls\ImagePreviewControl.cs" />
|
||||||
<Compile Include="Controls\MailItemDisplayInformationControl.xaml.cs">
|
<Compile Include="Controls\MailItemDisplayInformationControl.xaml.cs">
|
||||||
@@ -335,7 +333,6 @@
|
|||||||
<Compile Include="Selectors\RendererCommandBarItemTemplateSelector.cs" />
|
<Compile Include="Selectors\RendererCommandBarItemTemplateSelector.cs" />
|
||||||
<Compile Include="Services\ApplicationResourceManager.cs" />
|
<Compile Include="Services\ApplicationResourceManager.cs" />
|
||||||
<Compile Include="Services\DialogService.cs" />
|
<Compile Include="Services\DialogService.cs" />
|
||||||
<Compile Include="Services\LaunchProtocolService.cs" />
|
|
||||||
<Compile Include="Services\WinoNavigationService.cs" />
|
<Compile Include="Services\WinoNavigationService.cs" />
|
||||||
<Compile Include="Styles\CommandBarItems.xaml.cs">
|
<Compile Include="Styles\CommandBarItems.xaml.cs">
|
||||||
<DependentUpon>CommandBarItems.xaml</DependentUpon>
|
<DependentUpon>CommandBarItems.xaml</DependentUpon>
|
||||||
@@ -892,4 +889,4 @@
|
|||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// When client established a healthy connection to the server.
|
/// When client established a healthy connection to the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record WinoServerConnectionEstrablished;
|
public record WinoServerConnectionEstablished;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="58272BurakKSE.WinoMailPreview"
|
Name="58272BurakKSE.WinoMailPreview"
|
||||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||||
Version="1.8.0.0" />
|
Version="1.8.1.0" />
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<!-- Publisher Cache Folders -->
|
<!-- Publisher Cache Folders -->
|
||||||
@@ -48,20 +48,13 @@
|
|||||||
Square150x150Logo="Images\Square150x150Logo.png"
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
Square44x44Logo="Images\Square44x44Logo.png">
|
Square44x44Logo="Images\Square44x44Logo.png">
|
||||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
|
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
|
||||||
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
<uap:SplashScreen Image="Images\SplashScreen.png" uap5:Optional="true" />
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<!-- App updated task. Notifies about new version after each Store update. -->
|
<!-- App updated task. Notifies about new version after each Store update. -->
|
||||||
<Extension Category="windows.updateTask" EntryPoint="Wino.BackgroundTasks.AppUpdatedTask" />
|
<Extension Category="windows.updateTask" EntryPoint="Wino.BackgroundTasks.AppUpdatedTask" />
|
||||||
|
|
||||||
<!-- SessionConnected task for background synchronization on startup. -->
|
|
||||||
<Extension Category="windows.backgroundTasks" EntryPoint="Wino.BackgroundTasks.SessionConnectedTask">
|
|
||||||
<BackgroundTasks>
|
|
||||||
<Task Type="systemEvent" />
|
|
||||||
</BackgroundTasks>
|
|
||||||
</Extension>
|
|
||||||
|
|
||||||
<!-- Protocol activation: mailto -->
|
<!-- Protocol activation: mailto -->
|
||||||
<uap:Extension Category="windows.protocol">
|
<uap:Extension Category="windows.protocol">
|
||||||
<uap:Protocol Name="mailto" />
|
<uap:Protocol Name="mailto" />
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ namespace Wino.Server
|
|||||||
|
|
||||||
if (status != AppServiceConnectionStatus.Success)
|
if (status != AppServiceConnectionStatus.Success)
|
||||||
{
|
{
|
||||||
// TODO: Handle connection error
|
Log.Error("Opening server connection failed. Status: {status}", status);
|
||||||
|
|
||||||
DisposeConnection();
|
DisposeConnection();
|
||||||
}
|
}
|
||||||
@@ -219,13 +219,24 @@ namespace Wino.Server
|
|||||||
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
|
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
|
||||||
};
|
};
|
||||||
|
|
||||||
await connection.SendMessageAsync(set);
|
try
|
||||||
|
{
|
||||||
|
await connection.SendMessageAsync(set);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// Connection might've been disposed during the SendMessageAsync call.
|
||||||
|
// This is a safe way to handle the exception.
|
||||||
|
// We don't lock the connection since this request may take sometime to complete.
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Log.Error(exception, "SendMessageAsync threw an exception");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
||||||
{
|
{
|
||||||
// TODO: Handle connection closed.
|
|
||||||
|
|
||||||
// UWP app might've been terminated or suspended.
|
// UWP app might've been terminated or suspended.
|
||||||
// At this point, we must keep active synchronizations going, but connection is lost.
|
// At this point, we must keep active synchronizations going, but connection is lost.
|
||||||
// As long as this process is alive, database will be kept updated, but no messages will be sent.
|
// As long as this process is alive, database will be kept updated, but no messages will be sent.
|
||||||
|
|||||||
Reference in New Issue
Block a user