merge communication branch

This commit is contained in:
Burak Kaan Köse
2024-07-18 22:21:52 +02:00
183 changed files with 2283 additions and 700 deletions

View File

@@ -1,13 +1,4 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Windows.ApplicationModel.Background;
using Windows.Storage;
using Wino.Core;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Services;
using Wino.Core.UWP;
using Wino.Services;
using Windows.ApplicationModel.Background;
namespace Wino.BackgroundTasks
{
@@ -17,32 +8,34 @@ namespace Wino.BackgroundTasks
{
var def = taskInstance.GetDeferral();
try
{
var services = new ServiceCollection();
//try
//{
// var services = new ServiceCollection();
services.RegisterCoreServices();
services.RegisterCoreUWPServices();
// services.RegisterCoreServices();
// services.RegisterCoreUWPServices();
var providere = services.BuildServiceProvider();
// var providere = services.BuildServiceProvider();
var backgroundTaskService = providere.GetService<IBackgroundSynchronizer>();
var dbService = providere.GetService<IDatabaseService>();
var logInitializer = providere.GetService<ILogInitializer>();
// var backgroundTaskService = providere.GetService<IBackgroundSynchronizer>();
// var dbService = providere.GetService<IDatabaseService>();
// var logInitializer = providere.GetService<ILogInitializer>();
logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
// logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
await dbService.InitializeAsync();
await backgroundTaskService.RunBackgroundSynchronizationAsync(Core.Domain.Enums.BackgroundSynchronizationReason.SessionConnected);
}
catch (Exception ex)
{
Log.Error(ex, "Background synchronization failed from background task.");
}
finally
{
def.Complete();
}
// await dbService.InitializeAsync();
// await backgroundTaskService.RunBackgroundSynchronizationAsync(Core.Domain.Enums.BackgroundSynchronizationReason.SessionConnected);
//}
//catch (Exception ex)
//{
// Log.Error(ex, "Background synchronization failed from background task.");
//}
//finally
//{
// def.Complete();
//}
def.Complete();
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using SQLite;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
@@ -140,7 +141,7 @@ namespace Wino.Core.Domain.Entities
/// </summary>
[Ignore]
public MailAccount AssignedAccount { get; set; }
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
public override string ToString() => $"{Subject} <-> {Id}";
}
}

View File

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

View File

@@ -1,12 +0,0 @@
using System.Threading.Tasks;
namespace Wino.Core.Domain.Interfaces
{
public interface IAppInitializerService
{
string GetApplicationDataFolder();
string GetPublisherSharedFolder();
Task MigrateAsync();
}
}

View File

@@ -0,0 +1,21 @@
namespace Wino.Core.Domain.Interfaces
{
/// <summary>
/// Singleton object that holds the application data folder path and the publisher shared folder path.
/// Load the values before calling any service.
/// App data folder is used for storing files.
/// Pubhlisher cache folder is only used for database file so other apps can access it in the same package by same publisher.
/// </summary>
public interface IApplicationConfiguration
{
/// <summary>
/// Application data folder.
/// </summary>
string ApplicationDataFolderPath { get; set; }
/// <summary>
/// Publisher shared folder path.
/// </summary>
string PublisherSharedFolderPath { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
using System.Threading;
using System.Threading.Tasks;
using MailKit;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
namespace Wino.Core.Domain.Interfaces
{
public interface IBaseSynchronizer
{
/// <summary>
/// Account that is assigned for this synchronizer.
/// </summary>
MailAccount Account { get; }
/// <summary>
/// Synchronizer state.
/// </summary>
AccountSynchronizerState State { get; }
/// <summary>
/// Queues a single request to be executed in the next synchronization.
/// </summary>
/// <param name="request">Request to queue.</param>
void QueueRequest(IRequestBase request);
/// <summary>
/// TODO
/// </summary>
/// <returns>Whether active synchronization is stopped or not.</returns>
bool CancelActiveSynchronization();
/// <summary>
/// Performs a full synchronization with the server with given options.
/// This will also prepares batch requests for execution.
/// Requests are executed in the order they are queued and happens before the synchronization.
/// Result of the execution queue is processed during the synchronization.
/// </summary>
/// <param name="options">Options for synchronization.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Result summary of synchronization.</returns>
Task<SynchronizationResult> SynchronizeAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Downloads a single MIME message from the server and saves it to disk.
/// </summary>
/// <param name="mailItem">Mail item to download from server.</param>
/// <param name="transferProgress">Optional progress reporting for download operation.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task DownloadMissingMimeMessageAsync(IMailItem mailItem, ITransferProgress transferProgress, CancellationToken cancellationToken = default);
}
}

View File

@@ -11,6 +11,7 @@ namespace Wino.Core.Domain.Interfaces
Task<string> GetEditorBundlePathAsync();
Task LaunchFileAsync(string filePath);
Task LaunchUriAsync(Uri uri);
bool IsAppRunning();
string GetFullAppVersion();

View File

@@ -1,4 +1,4 @@
namespace Wino.Core.Domain.Models.Requests
namespace Wino.Core.Domain.Interfaces
{
/// <summary>
/// Interface for all messages to report UI changes from synchronizers to UI.
@@ -6,5 +6,6 @@
/// They are sent either from processor or view models to signal some other
/// parts of the application.
/// </summary>
public interface IUIMessage;
public interface IServerMessage;
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Threading.Tasks;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Interfaces
{
public interface IWinoServerConnectionManager
{
Task<bool> ConnectAsync();
Task<bool> DisconnectAsync();
WinoServerConnectionStatus Status { get; }
event EventHandler<WinoServerConnectionStatus> StatusChanged;
void DisposeConnection();
void QueueRequest(IRequestBase request, Guid accountId);
}
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync
{
TAppServiceConnection Connection { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Interfaces
{
public interface IWinoSynchronizerFactory : IInitializeAsync
{
IBaseSynchronizer GetAccountSynchronizer(Guid accountId);
IBaseSynchronizer CreateNewSynchronizer(MailAccount account);
}
}

View File

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

View File

@@ -6,7 +6,7 @@ namespace Wino.Core.Domain.Models.MailItem
/// <summary>
/// Interface of simplest representation of a MailCopy.
/// </summary>
public interface IMailItem
public interface IMailItem : IMailHashContainer
{
Guid UniqueId { get; }
string Id { get; }

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Wino.Core.Domain.Entities;
@@ -40,6 +41,8 @@ namespace Wino.Core.Domain.Models.MailItem
}
}
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;
#region IMailItem
public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty;

View File

@@ -1,8 +1,7 @@
using System;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Requests
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Defines a single rule for toggling user actions if needed.

View File

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

View File

@@ -1,9 +0,0 @@
namespace Wino.Core.Domain.Models.Requests
{
// Used to pass messages from the webview to the app.
public class WebViewMessage
{
public string type { get; set; }
public string value { get; set; }
}
}

View File

@@ -6,6 +6,10 @@
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Interfaces\IWinoSynchronizerFactory.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="Translations\ca_ES\resources.json" />
<None Remove="Translations\cs_CZ\resources.json" />
@@ -49,7 +53,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MimeKit" Version="4.4.0" />
<PackageReference Include="MimeKit" Version="4.7.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="System.Text.Json" Version="8.0.4" />
</ItemGroup>
@@ -69,4 +75,7 @@
<LastGenOutput>Translator.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Models\Communication\" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Windows.ApplicationModel.AppService;
using Wino.Core.Domain.Interfaces;
using Wino.Core.UWP.Services;
using Wino.Core.WinUI.Services;
@@ -10,18 +11,22 @@ namespace Wino.Core.UWP
{
public static void RegisterCoreUWPServices(this IServiceCollection services)
{
var serverConnectionManager = new WinoServerConnectionManager();
services.AddSingleton<IWinoServerConnectionManager>(serverConnectionManager);
services.AddSingleton<IWinoServerConnectionManager<AppServiceConnection>>(serverConnectionManager);
services.AddSingleton<IUnderlyingThemeService, UnderlyingThemeService>();
services.AddSingleton<INativeAppService, NativeAppService>();
services.AddSingleton<IStoreManagementService, StoreManagementService>();
services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>();
services.AddSingleton<IAppShellService, AppShellService>();
services.AddTransient<IAppInitializerService, AppInitializerService>();
services.AddTransient<IConfigurationService, ConfigurationService>();
services.AddTransient<IFileService, FileService>();
services.AddTransient<IStoreRatingService, StoreRatingService>();
services.AddTransient<IKeyPressService, KeyPressService>();
services.AddTransient<IBackgroundSynchronizer, BackgroundSynchronizer>();
services.AddTransient<INotificationBuilder, NotificationBuilder>();
services.AddTransient<IClipboardService, ClipboardService>();
}

View File

@@ -1,50 +0,0 @@
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.UWP.Services
{
public class AppInitializerService : IAppInitializerService
{
private readonly IBackgroundTaskService _backgroundTaskService;
public AppInitializerService(IBackgroundTaskService backgroundTaskService)
{
_backgroundTaskService = backgroundTaskService;
}
public string GetPublisherSharedFolder() => ApplicationData.Current.GetPublisherCacheFolder("WinoShared").Path;
public string GetApplicationDataFolder() => ApplicationData.Current.LocalFolder.Path;
public Task MigrateAsync()
{
UnregisterAllBackgroundTasks();
return Task.CompletedTask;
}
#region 1.6.8 -> 1.6.9
private void UnregisterAllBackgroundTasks()
{
_backgroundTaskService.UnregisterAllBackgroundTask();
}
#endregion
#region 1.7.0
/// <summary>
/// We decided to use publisher cache folder as a database going forward.
/// This migration will move the file from application local folder and delete it.
/// Going forward database will be initialized from publisher cache folder.
/// </summary>
private async Task MoveExistingDatabaseToSharedCacheFolderAsync()
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -1,144 +0,0 @@
using System;
using System.Threading.Tasks;
using Serilog;
using Windows.Storage;
using Wino.Core;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Synchronizers;
namespace Wino.Services
{
public interface IBackgroundSynchronizer
{
Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason);
void CreateLock();
void ReleaseLock();
bool IsBackgroundSynchronizationLocked();
}
/// <summary>
/// Service responsible for handling background synchronization on timer and session connected events.
/// </summary>
public class BackgroundSynchronizer : IBackgroundSynchronizer
{
private const string BackgroundSynchronizationLock = nameof(BackgroundSynchronizationLock);
private readonly IAccountService _accountService;
private readonly IFolderService _folderService;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
public BackgroundSynchronizer(IAccountService accountService,
IFolderService folderService,
IWinoSynchronizerFactory winoSynchronizerFactory)
{
_accountService = accountService;
_folderService = folderService;
_winoSynchronizerFactory = winoSynchronizerFactory;
}
public void CreateLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = true;
public void ReleaseLock() => ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] = false;
public bool IsBackgroundSynchronizationLocked()
=> ApplicationData.Current.LocalSettings.Values.ContainsKey(BackgroundSynchronizationLock)
&& ApplicationData.Current.LocalSettings.Values[BackgroundSynchronizationLock] is bool boolValue && boolValue;
public async Task RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason reason)
{
Log.Information($"{reason} background synchronization is kicked in.");
// This should never crash.
// We might be in-process or out-of-process.
//if (IsBackgroundSynchronizationLocked())
//{
// Log.Warning("Background synchronization is locked. Hence another background synchronization is canceled.");
// return;
//}
try
{
CreateLock();
var accounts = await _accountService.GetAccountsAsync();
foreach (var account in accounts)
{
// We can't sync broken account.
if (account.AttentionReason != AccountAttentionReason.None)
continue;
// TODO
// We can't synchronize without system folder setup is done.
//var isSystemFolderSetupDone = await _folderService.CheckSystemFolderSetupDoneAsync(account.Id);
//// No need to throw here. It's a background process.
//if (!isSystemFolderSetupDone)
// continue;
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(account.Id);
if (synchronizer.State != AccountSynchronizerState.Idle)
{
Log.Information("Skipping background synchronization for {Name} since current state is {State}", synchronizer.Account.Name, synchronizer.State);
return;
}
await HandleSynchronizationAsync(synchronizer, reason);
}
}
catch (Exception ex)
{
Log.Error($"[BackgroundSynchronization] Failed with message {ex.Message}");
}
finally
{
ReleaseLock();
}
}
private async Task HandleSynchronizationAsync(IBaseSynchronizer synchronizer, BackgroundSynchronizationReason reason)
{
if (synchronizer.State != AccountSynchronizerState.Idle) return;
var account = synchronizer.Account;
try
{
// SessionConnected will do Full synchronization for logon, Timer task will do Inbox only.
var syncType = reason == BackgroundSynchronizationReason.SessionConnected ? SynchronizationType.Full : SynchronizationType.Inbox;
var options = new SynchronizationOptions()
{
AccountId = account.Id,
Type = syncType,
};
await synchronizer.SynchronizeAsync(options);
}
catch (AuthenticationAttentionException authenticationAttentionException)
{
Log.Error(authenticationAttentionException, $"[BackgroundSync] Invalid credentials for account {account.Address}");
account.AttentionReason = AccountAttentionReason.InvalidCredentials;
await _accountService.UpdateAccountAsync(account);
}
catch (SystemFolderConfigurationMissingException configMissingException)
{
Log.Error(configMissingException, $"[BackgroundSync] Missing system folder configuration for account {account.Address}");
account.AttentionReason = AccountAttentionReason.MissingSystemFolderConfiguration;
await _accountService.UpdateAccountAsync(account);
}
catch (Exception ex)
{
Log.Error(ex, "[BackgroundSync] Synchronization failed.");
}
}
}
}

View File

@@ -106,7 +106,15 @@ namespace Wino.Services
return _editorBundlePath;
}
public bool IsAppRunning() => (Window.Current?.Content as Frame)?.Content != null;
public bool IsAppRunning()
{
#if WINDOWS_UWP
return (Window.Current?.Content as Frame)?.Content != null;
#endif
return true;
}
public async Task LaunchFileAsync(string filePath)
{

View File

@@ -7,7 +7,7 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Reader;
using Wino.Core.Services;
namespace Wino.Services
namespace Wino.Core.UWP.Services
{
public class PreferencesService : ObservableObject, IPreferencesService
{

View File

@@ -2,7 +2,6 @@
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.AppCenter.Crashes;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Messages.Shell;
@@ -116,17 +115,10 @@ namespace Wino.Services
private void UpdateAppCoreWindowTitle()
{
try
{
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
if (appView != null)
appView.Title = CoreWindowTitle;
}
catch (System.Exception ex)
{
Crashes.TrackError(ex);
}
if (appView != null)
appView.Title = CoreWindowTitle;
}
}
}

View File

@@ -182,6 +182,7 @@ namespace Wino.Services
await ApplyCustomThemeAsync(true);
// Registering to color changes, thus we notice when user changes theme system wide
uiSettings.ColorValuesChanged -= UISettingsColorChanged;
uiSettings.ColorValuesChanged += UISettingsColorChanged;
}

View File

@@ -0,0 +1,207 @@
using System;
using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppService;
using Windows.Foundation.Metadata;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Messaging;
using Wino.Messaging.Enums;
using Wino.Messaging.Server;
namespace Wino.Core.UWP.Services
{
public class WinoServerConnectionManager : IWinoServerConnectionManager<AppServiceConnection>
{
private WinoServerConnectionStatus status;
public WinoServerConnectionStatus Status
{
get { return status; }
private set
{
status = value;
StatusChanged?.Invoke(this, value);
}
}
private AppServiceConnection _connection;
public event EventHandler<WinoServerConnectionStatus> StatusChanged;
public AppServiceConnection Connection
{
get { return _connection; }
set
{
if (_connection != null)
{
_connection.RequestReceived -= ServerMessageReceived;
_connection.ServiceClosed -= ServerDisconnected;
}
_connection = value;
if (value == null)
{
Status = WinoServerConnectionStatus.Disconnected;
}
else
{
value.RequestReceived += ServerMessageReceived;
value.ServiceClosed += ServerDisconnected;
Status = WinoServerConnectionStatus.Connected;
}
}
}
public async Task<bool> ConnectAsync()
{
if (Status == WinoServerConnectionStatus.Connected) return true;
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
try
{
Status = WinoServerConnectionStatus.Connecting;
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
// If the server connection is success, Status will be updated to Connected by BackgroundActivationHandlerEx.
}
catch (Exception)
{
Status = WinoServerConnectionStatus.Failed;
return false;
}
return true;
}
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()
{
var isConnectionSuccessfull = await ConnectAsync();
// TODO: Log connection status
}
private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
if (args.Request.Message.TryGetValue(MessageConstants.MessageTypeKey, out object messageTypeObject) && messageTypeObject is int messageTypeInt)
{
var messageType = (MessageType)messageTypeInt;
if (args.Request.Message.TryGetValue(MessageConstants.MessageDataKey, out object messageDataObject) && messageDataObject is string messageJson)
{
switch (messageType)
{
case MessageType.UIMessage:
if (args.Request.Message.TryGetValue(MessageConstants.MessageDataTypeKey, out object dataTypeObject) && dataTypeObject is string dataTypeName)
{
HandleUIMessage(messageJson, dataTypeName);
}
else
throw new ArgumentException("Message data type is missing.");
break;
case MessageType.ServerAction:
HandleServerAction(messageJson);
break;
default:
break;
}
}
}
}
private void HandleServerAction(string messageJson)
{
}
/// <summary>
/// Unpacks IServerMessage objects and delegate it to Messenger for UI to process.
/// </summary>
/// <param name="messageJson">Message data in json format.</param>
private void HandleUIMessage(string messageJson, string typeName)
{
Debug.WriteLine($"C: UImessage ({typeName})");
switch (typeName)
{
case nameof(MailAddedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailAddedMessage>(messageJson));
break;
case nameof(MailDownloadedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailDownloadedMessage>(messageJson));
break;
case nameof(MailRemovedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailRemovedMessage>(messageJson));
break;
case nameof(MailUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MailUpdatedMessage>(messageJson));
break;
case nameof(AccountCreatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountCreatedMessage>(messageJson));
break;
case nameof(AccountRemovedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountRemovedMessage>(messageJson));
break;
case nameof(AccountUpdatedMessage):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<AccountUpdatedMessage>(messageJson));
break;
case nameof(DraftCreated):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftCreated>(messageJson));
break;
case nameof(DraftFailed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftFailed>(messageJson));
break;
case nameof(DraftMapped):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<DraftMapped>(messageJson));
break;
case nameof(FolderRenamed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderRenamed>(messageJson));
break;
case nameof(FolderSynchronizationEnabled):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<FolderSynchronizationEnabled>(messageJson));
break;
case nameof(MergedInboxRenamed):
WeakReferenceMessenger.Default.Send(JsonSerializer.Deserialize<MergedInboxRenamed>(messageJson));
break;
default:
throw new Exception("Invalid data type name passed to client.");
}
}
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
{
// TODO: Handle server disconnection.
}
public void DisposeConnection()
{
if (Connection == null) return;
}
public void QueueRequest(IRequestBase request, Guid accountId)
{
// TODO: Queue this request to corresponding account's synchronizer request queue.
}
}
}

View File

@@ -118,6 +118,7 @@
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="CoreUWPContainerSetup.cs" />
@@ -127,9 +128,9 @@
<Compile Include="Models\Personalization\PreDefinedAppTheme.cs" />
<Compile Include="Models\Personalization\SystemAppTheme.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\AppInitializerService.cs" />
<Compile Include="Services\AppShellService.cs" />
<Compile Include="Services\BackgroundSynchronizer.cs" />
<Compile Include="Services\PreferencesService.cs" />
<Compile Include="Services\StatePersistenceService.cs" />
<Compile Include="Services\WinoServerConnectionManager.cs" />
<Compile Include="Services\BackgroundTaskService.cs" />
<Compile Include="Services\ClipboardService.cs" />
<Compile Include="Services\ConfigurationService.cs" />
@@ -169,8 +170,16 @@
<Project>{e6b1632a-8901-41e8-9ddf-6793c7698b0b}</Project>
<Name>Wino.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj">
<Project>{0c307d7e-256f-448c-8265-5622a812fbcc}</Project>
<Name>Wino.Messaging</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<SDKReference Include="WindowsDesktop, Version=10.0.22621.0">
<Name>Windows Desktop Extensions for the UWP</Name>
</SDKReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>

View File

@@ -29,10 +29,17 @@ namespace Wino.Core.Authenticators
{
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
_publicClientApplication = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(Authority)
.WithRedirectUri(authenticationRedirectUri)
.Build();
var outlookAppBuilder = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(Authority);
#if WINDOWS_UWP
outlookAppBuilder.WithRedirectUri(authenticationRedirectUri);
#else
outlookAppBuilder.WithDefaultRedirectUri();
#endif
_publicClientApplication = outlookAppBuilder.Build();
}
#pragma warning disable S1133 // Deprecated code should be removed

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Serilog.Core;
using Wino.Core.Authenticators;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Integration.Processors;
using Wino.Core.Integration.Threading;
@@ -16,9 +17,9 @@ namespace Wino.Core
services.AddSingleton(loggerLevelSwitcher);
services.AddSingleton<ILogInitializer, LogInitializer>();
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
services.AddSingleton<ITranslationService, TranslationService>();
services.AddSingleton<IDatabaseService, DatabaseService>();
services.AddSingleton<IWinoSynchronizerFactory, WinoSynchronizerFactory>();
services.AddSingleton<IThreadingStrategyProvider, ThreadingStrategyProvider>();
services.AddSingleton<IMimeFileService, MimeFileService>();
@@ -42,6 +43,10 @@ namespace Wino.Core
services.AddTransient<IFontService, FontService>();
services.AddTransient<IUnsubscriptionService, UnsubscriptionService>();
services.AddTransient<OutlookAuthenticator>();
services.AddTransient<GmailAuthenticator>();
services.AddTransient<CustomAuthenticator>();
services.AddTransient<OutlookThreadingStrategy>();
services.AddTransient<GmailThreadingStrategy>();
services.AddTransient<ImapThreadStrategy>();

View File

@@ -1,5 +1,5 @@
using Wino.Core.Domain.Enums;
using Wino.Core.Synchronizers;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Messages.Synchronization
{

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -5,6 +5,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -5,6 +5,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -2,6 +2,7 @@
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -7,6 +7,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Requests
{

View File

@@ -1,25 +0,0 @@
using System;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Requests;
namespace Wino.Core.Requests
{
public record MailAddedMessage(MailCopy AddedMail) : IUIMessage;
public record MailRemovedMessage(MailCopy RemovedMail) : IUIMessage;
public record MailUpdatedMessage(MailCopy UpdatedMail) : IUIMessage;
public record MailDownloadedMessage(MailCopy DownloadedMail) : IUIMessage;
public record AccountCreatedMessage(MailAccount Account) : IUIMessage;
public record AccountRemovedMessage(MailAccount Account) : IUIMessage;
public record AccountUpdatedMessage(MailAccount Account) : IUIMessage;
public record DraftCreated(MailCopy DraftMail, MailAccount Account) : IUIMessage;
public record DraftFailed(MailCopy DraftMail, MailAccount Account) : IUIMessage;
public record DraftMapped(string LocalDraftCopyId, string RemoteDraftCopyId) : IUIMessage;
public record MergedInboxRenamed(Guid MergedInboxId, string NewName) : IUIMessage;
public record FolderRenamed(IMailItemFolder MailItemFolder) : IUIMessage;
public record FolderSynchronizationEnabled(IMailItemFolder MailItemFolder) : IUIMessage;
}

View File

@@ -12,7 +12,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Extensions;
using Wino.Core.Messages.Accounts;
using Wino.Core.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Services
{

View File

@@ -0,0 +1,13 @@
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Services
{
public class ApplicationConfiguration : IApplicationConfiguration
{
public const string SharedFolderName = "WinoShared";
public string ApplicationDataFolderPath { get; set; }
public string PublisherSharedFolderPath { get; set; }
}
}

View File

@@ -1,6 +1,6 @@
using CommunityToolkit.Mvvm.Messaging;
using SQLite;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Services
{
@@ -16,7 +16,7 @@ namespace Wino.Core.Services
_databaseService = databaseService;
}
public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IUIMessage
public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IServerMessage
=> Messenger.Send(message);
}
}

View File

@@ -14,16 +14,16 @@ namespace Wino.Core.Services
public class DatabaseService : IDatabaseService
{
private string DatabaseName => "Wino172.db";
private const string DatabaseName = "Wino172.db";
private bool _isInitialized = false;
private readonly IAppInitializerService _appInitializerService;
private readonly IApplicationConfiguration _folderConfiguration;
public SQLiteAsyncConnection Connection { get; private set; }
public DatabaseService(IAppInitializerService appInitializerService)
public DatabaseService(IApplicationConfiguration folderConfiguration)
{
_appInitializerService = appInitializerService;
_folderConfiguration = folderConfiguration;
}
public async Task InitializeAsync()
@@ -31,8 +31,8 @@ namespace Wino.Core.Services
if (_isInitialized)
return;
var applicationData = _appInitializerService.GetPublisherSharedFolder();
var databaseFileName = Path.Combine(applicationData, DatabaseName);
var publisherCacheFolder = _folderConfiguration.PublisherSharedFolderPath;
var databaseFileName = Path.Combine(publisherCacheFolder, DatabaseName);
Connection = new SQLiteAsyncConnection(databaseFileName)
{
@@ -45,7 +45,6 @@ namespace Wino.Core.Services
})
};
await CreateTablesAsync();
_isInitialized = true;

View File

@@ -16,7 +16,7 @@ using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Extensions;
using Wino.Core.MenuItems;
using Wino.Core.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Services
{

View File

@@ -11,11 +11,11 @@ namespace Wino.Core.Services
public const string ProtocolLogFileName = "ImapProtocolLog.log";
private readonly IPreferencesService _preferencesService;
private readonly IAppInitializerService _appInitializerService;
private readonly IApplicationConfiguration _appInitializerService;
private Stream _protocolLogStream;
public ImapTestService(IPreferencesService preferencesService, IAppInitializerService appInitializerService)
public ImapTestService(IPreferencesService preferencesService, IApplicationConfiguration appInitializerService)
{
_preferencesService = preferencesService;
_appInitializerService = appInitializerService;
@@ -24,7 +24,7 @@ namespace Wino.Core.Services
private void EnsureProtocolLogFileExists()
{
// Create new file for protocol logger.
var localAppFolderPath = _appInitializerService.GetApplicationDataFolder();
var localAppFolderPath = _appInitializerService.ApplicationDataFolderPath;
var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName);

View File

@@ -14,7 +14,7 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Comparers;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Extensions;
using Wino.Core.Requests;
using Wino.Messaging.Server;
namespace Wino.Core.Services
{

View File

@@ -19,18 +19,18 @@ namespace Wino.Core.Services
public class WinoRequestDelegator : IWinoRequestDelegator
{
private readonly IWinoRequestProcessor _winoRequestProcessor;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
private readonly IFolderService _folderService;
private readonly IDialogService _dialogService;
private readonly ILogger _logger = Log.ForContext<WinoRequestDelegator>();
public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor,
IWinoSynchronizerFactory winoSynchronizerFactory,
IWinoServerConnectionManager winoServerConnectionManager,
IFolderService folderService,
IDialogService dialogService)
{
_winoRequestProcessor = winoRequestProcessor;
_winoSynchronizerFactory = winoSynchronizerFactory;
_winoServerConnectionManager = winoServerConnectionManager;
_folderService = folderService;
_dialogService = dialogService;
}
@@ -132,19 +132,7 @@ namespace Wino.Core.Services
}
private void QueueRequest(IRequestBase request, Guid accountId)
{
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
if (synchronizer == null)
{
_logger.Warning("Synchronizer not found for account {AccountId}.", accountId);
_logger.Warning("Skipping queueing request {Operation}.", request.Operation);
return;
}
synchronizer.QueueRequest(request);
}
=> _winoServerConnectionManager.QueueRequest(request, accountId);
private void QueueSynchronization(Guid accountId)
{

View File

@@ -9,7 +9,6 @@ using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Requests;
namespace Wino.Core.Services

View File

@@ -21,50 +21,6 @@ using Wino.Core.Requests;
namespace Wino.Core.Synchronizers
{
public interface IBaseSynchronizer
{
/// <summary>
/// Account that is assigned for this synchronizer.
/// </summary>
MailAccount Account { get; }
/// <summary>
/// Synchronizer state.
/// </summary>
AccountSynchronizerState State { get; }
/// <summary>
/// Queues a single request to be executed in the next synchronization.
/// </summary>
/// <param name="request">Request to queue.</param>
void QueueRequest(IRequestBase request);
/// <summary>
/// TODO
/// </summary>
/// <returns>Whether active synchronization is stopped or not.</returns>
bool CancelActiveSynchronization();
/// <summary>
/// Performs a full synchronization with the server with given options.
/// This will also prepares batch requests for execution.
/// Requests are executed in the order they are queued and happens before the synchronization.
/// Result of the execution queue is processed during the synchronization.
/// </summary>
/// <param name="options">Options for synchronization.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Result summary of synchronization.</returns>
Task<SynchronizationResult> SynchronizeAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Downloads a single MIME message from the server and saves it to disk.
/// </summary>
/// <param name="mailItem">Mail item to download from server.</param>
/// <param name="transferProgress">Optional progress reporting for download operation.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task DownloadMissingMimeMessageAsync(IMailItem mailItem, ITransferProgress transferProgress, CancellationToken cancellationToken = default);
}
public abstract class BaseSynchronizer<TBaseRequest, TMessageType> : BaseMailIntegrator<TBaseRequest>, IBaseSynchronizer
{
private SemaphoreSlim synchronizationSemaphore = new(1);

View File

@@ -7,6 +7,10 @@
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="WinoSynchronizerFactory.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Diagnostics" Version="8.2.2" />
@@ -18,11 +22,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MailKit" Version="4.6.0" />
<PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Graph" Version="5.55.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.47.2" />
<PackageReference Include="MimeKit" Version="4.6.0" />
<PackageReference Include="MimeKit" Version="4.7.1" />
<PackageReference Include="morelinq" Version="4.1.0" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="Serilog" Version="3.1.1" />
@@ -35,5 +39,6 @@
<ItemGroup>
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,13 +10,6 @@ using Wino.Core.Synchronizers;
namespace Wino.Core
{
public interface IWinoSynchronizerFactory : IInitializeAsync
{
IBaseSynchronizer GetAccountSynchronizer(Guid accountId);
IBaseSynchronizer CreateNewSynchronizer(MailAccount account);
void DeleteSynchronizer(MailAccount account);
}
/// <summary>
/// Factory that keeps track of all integrator with associated mail accounts.
/// Synchronizer per-account makes sense because re-generating synchronizers are not ideal.
@@ -82,7 +75,6 @@ namespace Wino.Core
return new OutlookSynchronizer(mailAccount, outlookAuthenticator, _outlookChangeProcessor);
case Domain.Enums.MailProviderType.Gmail:
var gmailAuthenticator = new GmailAuthenticator(_tokenService, _nativeAppService);
return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor);
case Domain.Enums.MailProviderType.Office365:
break;

View File

@@ -12,7 +12,7 @@ namespace Wino.Mail.ViewModels
{
private readonly IStoreRatingService _storeRatingService;
private readonly INativeAppService _nativeAppService;
private readonly IAppInitializerService _appInitializerService;
private readonly IApplicationConfiguration _appInitializerService;
private readonly IFileService _fileService;
private readonly ILogInitializer _logInitializer;
@@ -31,7 +31,7 @@ namespace Wino.Mail.ViewModels
IDialogService dialogService,
INativeAppService nativeAppService,
IPreferencesService preferencesService,
IAppInitializerService appInitializerService,
IApplicationConfiguration appInitializerService,
IFileService fileService,
ILogInitializer logInitializer) : base(dialogService)
{
@@ -77,7 +77,7 @@ namespace Wino.Mail.ViewModels
private async Task SaveLogInternalAsync(string sourceFileName)
{
var appDataFolder = _appInitializerService.GetApplicationDataFolder();
var appDataFolder = _appInitializerService.ApplicationDataFolderPath;
var logFile = Path.Combine(appDataFolder, sourceFileName);

View File

@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Wino.Core;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
@@ -13,13 +12,12 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Messages.Navigation;
using Wino.Core.Requests;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
{
public partial class AccountDetailsPageViewModel : BaseViewModel
{
private readonly IWinoSynchronizerFactory _synchronizerFactory;
private readonly IAccountService _accountService;
private readonly IFolderService _folderService;
@@ -45,11 +43,9 @@ namespace Wino.Mail.ViewModels
public AccountDetailsPageViewModel(IDialogService dialogService,
IWinoSynchronizerFactory synchronizerFactory,
IAccountService accountService,
IFolderService folderService) : base(dialogService)
{
_synchronizerFactory = synchronizerFactory;
_accountService = accountService;
_folderService = folderService;
}
@@ -99,10 +95,7 @@ namespace Wino.Mail.ViewModels
await _accountService.DeleteAccountAsync(Account);
_synchronizerFactory.DeleteSynchronizer(Account);
// TODO: Clear existing requests.
// _synchronizationWorker.ClearRequests(Account.Id);
// TODO: Server: Cancel ongoing calls from server for this account.
DialogService.InfoBarMessage(Translator.Info_AccountDeletedTitle, string.Format(Translator.Info_AccountDeletedMessage, Account.Name), InfoBarMessageType.Success);

View File

@@ -16,11 +16,10 @@ using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Store;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Messages.Authorization;
using Wino.Core.Messages.Navigation;
using Wino.Core.Requests;
using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
{
@@ -35,7 +34,6 @@ namespace Wino.Mail.ViewModels
private readonly IStoreManagementService _storeManagementService;
private readonly IPreferencesService _preferencesService;
private readonly IAuthenticationProvider _authenticationProvider;
private readonly IWinoSynchronizerFactory _synchronizerFactory;
public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; set; } = [];
@@ -60,7 +58,6 @@ namespace Wino.Mail.ViewModels
public AccountManagementViewModel(IDialogService dialogService,
IWinoNavigationService navigationService,
IWinoSynchronizerFactory synchronizerFactory,
IAccountService accountService,
IProviderService providerService,
IFolderService folderService,
@@ -69,7 +66,6 @@ namespace Wino.Mail.ViewModels
IAuthenticationProvider authenticationProvider) : base(dialogService)
{
_accountService = accountService;
_synchronizerFactory = synchronizerFactory;
_dialogService = dialogService;
_providerService = providerService;
_folderService = folderService;
@@ -205,29 +201,31 @@ namespace Wino.Mail.ViewModels
// Local account has been created.
// Create new synchronizer and start synchronization.
var synchronizer = _synchronizerFactory.CreateNewSynchronizer(createdAccount);
// TODO: Server: Make sure that server synchronizes folders and sends back the result.
if (creationDialog is ICustomServerAccountCreationDialog customServerAccountCreationDialog)
customServerAccountCreationDialog.ShowPreparingFolders();
else
creationDialog.State = AccountCreationDialogState.PreparingFolders;
//var synchronizer = _synchronizerFactory.CreateNewSynchronizer(createdAccount);
var options = new SynchronizationOptions()
{
AccountId = createdAccount.Id,
Type = SynchronizationType.FoldersOnly
};
//if (creationDialog is ICustomServerAccountCreationDialog customServerAccountCreationDialog)
// customServerAccountCreationDialog.ShowPreparingFolders();
//else
// creationDialog.State = AccountCreationDialogState.PreparingFolders;
var synchronizationResult = await synchronizer.SynchronizeAsync(options);
//var options = new SynchronizationOptions()
//{
// AccountId = createdAccount.Id,
// Type = SynchronizationType.FoldersOnly
//};
if (synchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
//var synchronizationResult = await synchronizer.SynchronizeAsync(options);
// Check if Inbox folder is available for the account after synchronization.
var isInboxAvailable = await _folderService.IsInboxAvailableForAccountAsync(createdAccount.Id);
//if (synchronizationResult.CompletedState != SynchronizationCompletedState.Success)
// throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
if (!isInboxAvailable)
throw new Exception(Translator.Exception_InboxNotAvailable);
//// Check if Inbox folder is available for the account after synchronization.
//var isInboxAvailable = await _folderService.IsInboxAvailableForAccountAsync(createdAccount.Id);
//if (!isInboxAvailable)
// throw new Exception(Translator.Exception_InboxNotAvailable);
// Send changes to listeners.
ReportUIChange(new AccountCreatedMessage(createdAccount));

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.AppCenter.Crashes;
using MoreLinq;
@@ -25,8 +26,8 @@ using Wino.Core.Messages.Mails;
using Wino.Core.Messages.Navigation;
using Wino.Core.Messages.Shell;
using Wino.Core.Messages.Synchronization;
using Wino.Core.Requests;
using Wino.Core.Services;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
{
@@ -62,6 +63,7 @@ namespace Wino.Mail.ViewModels
#endregion
public IStatePersistanceService StatePersistenceService { get; }
public IWinoServerConnectionManager ServerConnectionManager { get; }
public IPreferencesService PreferencesService { get; }
public IWinoNavigationService NavigationService { get; }
@@ -73,7 +75,6 @@ namespace Wino.Mail.ViewModels
private readonly INotificationBuilder _notificationBuilder;
private readonly IWinoRequestDelegator _winoRequestDelegator;
private readonly IWinoSynchronizerFactory _synchronizerFactory;
private readonly IBackgroundTaskService _backgroundTaskService;
private readonly IMimeFileService _mimeFileService;
@@ -82,9 +83,11 @@ namespace Wino.Mail.ViewModels
private readonly SemaphoreSlim accountInitFolderUpdateSlim = new SemaphoreSlim(1);
[ObservableProperty]
private string _activeConnectionStatus = WinoServerConnectionStatus.None.ToString();
public AppShellViewModel(IDialogService dialogService,
IWinoNavigationService navigationService,
IWinoSynchronizerFactory synchronizerFactory,
IBackgroundTaskService backgroundTaskService,
IMimeFileService mimeFileService,
INativeAppService nativeAppService,
@@ -97,13 +100,23 @@ namespace Wino.Mail.ViewModels
INotificationBuilder notificationBuilder,
IWinoRequestDelegator winoRequestDelegator,
IFolderService folderService,
IStatePersistanceService statePersistanceService) : base(dialogService)
IStatePersistanceService statePersistanceService,
IWinoServerConnectionManager serverConnectionManager) : base(dialogService)
{
StatePersistenceService = statePersistanceService;
ServerConnectionManager = serverConnectionManager;
ServerConnectionManager.StatusChanged += async (sender, status) =>
{
await ExecuteUIThread(() =>
{
ActiveConnectionStatus = status.ToString();
});
};
PreferencesService = preferencesService;
NavigationService = navigationService;
_synchronizerFactory = synchronizerFactory;
_backgroundTaskService = backgroundTaskService;
_mimeFileService = mimeFileService;
_nativeAppService = nativeAppService;
@@ -117,6 +130,9 @@ namespace Wino.Mail.ViewModels
_winoRequestDelegator = winoRequestDelegator;
}
[RelayCommand]
private Task ReconnectServerAsync() => ServerConnectionManager.ConnectAsync();
protected override void OnDispatcherAssigned()
{
base.OnDispatcherAssigned();
@@ -776,65 +792,65 @@ namespace Wino.Mail.ViewModels
await _winoRequestDelegator.ExecuteAsync(draftPreperationRequest);
}
public async void Receive(NewSynchronizationRequested message)
{
// TODO: Queue new synchronization for an account.
// Don't send message for sync completion when we execute requests.
// People are usually interested in seeing the notification after they trigger the synchronization.
bool shouldReportSynchronizationResult = message.Options.Type != SynchronizationType.ExecuteRequests;
//bool shouldReportSynchronizationResult = message.Options.Type != SynchronizationType.ExecuteRequests;
var synchronizer = _synchronizerFactory.GetAccountSynchronizer(message.Options.AccountId);
//var synchronizer = _synchronizerFactory.GetAccountSynchronizer(message.Options.AccountId);
if (synchronizer == null) return;
//if (synchronizer == null) return;
var accountId = message.Options.AccountId;
//var accountId = message.Options.AccountId;
message.Options.ProgressListener = this;
//message.Options.ProgressListener = this;
bool isSynchronizationSucceeded = false;
//bool isSynchronizationSucceeded = false;
try
{
// TODO: Cancellation Token
var synchronizationResult = await synchronizer.SynchronizeAsync(message.Options);
//try
//{
// // TODO: Cancellation Token
// var synchronizationResult = await synchronizer.SynchronizeAsync(message.Options);
isSynchronizationSucceeded = synchronizationResult.CompletedState == SynchronizationCompletedState.Success;
// isSynchronizationSucceeded = synchronizationResult.CompletedState == SynchronizationCompletedState.Success;
// Create notification for synchronization result.
if (synchronizationResult.DownloadedMessages.Any())
{
var accountInboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(message.Options.AccountId, SpecialFolderType.Inbox);
// // Create notification for synchronization result.
// if (synchronizationResult.DownloadedMessages.Any())
// {
// var accountInboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(message.Options.AccountId, SpecialFolderType.Inbox);
if (accountInboxFolder == null) return;
// if (accountInboxFolder == null) return;
await _notificationBuilder.CreateNotificationsAsync(accountInboxFolder.Id, synchronizationResult.DownloadedMessages);
}
}
catch (AuthenticationAttentionException)
{
await SetAccountAttentionAsync(accountId, AccountAttentionReason.InvalidCredentials);
}
catch (SystemFolderConfigurationMissingException)
{
await SetAccountAttentionAsync(accountId, AccountAttentionReason.MissingSystemFolderConfiguration);
}
catch (OperationCanceledException)
{
DialogService.InfoBarMessage(Translator.Info_SyncCanceledMessage, Translator.Info_SyncCanceledMessage, InfoBarMessageType.Warning);
}
catch (Exception ex)
{
DialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, ex.Message, InfoBarMessageType.Error);
}
finally
{
if (shouldReportSynchronizationResult)
Messenger.Send(new AccountSynchronizationCompleted(accountId,
isSynchronizationSucceeded ? SynchronizationCompletedState.Success : SynchronizationCompletedState.Failed,
message.Options.GroupedSynchronizationTrackingId));
}
// await _notificationBuilder.CreateNotificationsAsync(accountInboxFolder.Id, synchronizationResult.DownloadedMessages);
// }
//}
//catch (AuthenticationAttentionException)
//{
// await SetAccountAttentionAsync(accountId, AccountAttentionReason.InvalidCredentials);
//}
//catch (SystemFolderConfigurationMissingException)
//{
// await SetAccountAttentionAsync(accountId, AccountAttentionReason.MissingSystemFolderConfiguration);
//}
//catch (OperationCanceledException)
//{
// DialogService.InfoBarMessage(Translator.Info_SyncCanceledMessage, Translator.Info_SyncCanceledMessage, InfoBarMessageType.Warning);
//}
//catch (Exception ex)
//{
// DialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, ex.Message, InfoBarMessageType.Error);
//}
//finally
//{
// if (shouldReportSynchronizationResult)
// Messenger.Send(new AccountSynchronizationCompleted(accountId,
// isSynchronizationSucceeded ? SynchronizationCompletedState.Success : SynchronizationCompletedState.Failed,
// message.Options.GroupedSynchronizationTrackingId));
//}
}

View File

@@ -6,8 +6,7 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Requests;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
{
@@ -72,9 +71,7 @@ namespace Wino.Mail.ViewModels
protected virtual void OnFolderRenamed(IMailItemFolder mailItemFolder) { }
protected virtual void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder) { }
public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IUIMessage
=> Messenger.Send(message);
public void ReportUIChange<TMessage>(TMessage message) where TMessage : class, IServerMessage => Messenger.Send(message);
void IRecipient<AccountCreatedMessage>.Receive(AccountCreatedMessage message) => OnAccountCreated(message.Account);
void IRecipient<AccountRemovedMessage>.Receive(AccountRemovedMessage message) => OnAccountRemoved(message.Account);
void IRecipient<AccountUpdatedMessage>.Receive(AccountUpdatedMessage message) => OnAccountUpdated(message.Account);

View File

@@ -18,7 +18,9 @@ namespace Wino.Mail.ViewModels.Collections
// If the item provider here for update or removal doesn't exist here
// we can ignore the operation.
public HashSet<Guid> MailCopyIdHashSet = new HashSet<Guid>();
public HashSet<Guid> MailCopyIdHashSet = [];
public event EventHandler<IMailItem> MailItemRemoved;
private ListItemComparer listComparer = new ListItemComparer();
@@ -61,45 +63,51 @@ namespace Wino.Mail.ViewModels.Collections
return mailItem.FromName;
}
private async Task InsertItemInternalAsync(object groupKey, IMailItem mailItem)
=> await ExecuteUIThread(() =>
{
if (mailItem is MailCopy mailCopy)
{
MailCopyIdHashSet.Add(mailCopy.UniqueId);
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
}
else if (mailItem is ThreadMailItem threadMailItem)
{
foreach (var item in threadMailItem.ThreadItems)
{
MailCopyIdHashSet.Add(item.UniqueId);
}
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
}
else if (mailItem is MailItemViewModel)
{
MailCopyIdHashSet.Add(mailItem.UniqueId);
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
}
});
private async Task RemoveItemInternalAsync(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
private void UpdateUniqueIdHashes(IMailHashContainer itemContainer, bool isAdd)
{
MailCopyIdHashSet.Remove(mailItem.UniqueId);
await ExecuteUIThread(() =>
foreach (var item in itemContainer.GetContainingIds())
{
group.Remove(mailItem);
if (group.Count == 0)
if (isAdd)
{
_mailItemSource.RemoveGroup(group.Key);
MailCopyIdHashSet.Add(item);
}
});
else
{
MailCopyIdHashSet.Remove(item);
}
}
}
private void InsertItemInternal(object groupKey, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, true);
if (mailItem is MailCopy mailCopy)
{
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
}
else if (mailItem is ThreadMailItem threadMailItem)
{
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
}
else
{
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
}
}
private void RemoveItemInternal(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, false);
MailItemRemoved?.Invoke(this, mailItem);
group.Remove(mailItem);
if (group.Count == 0)
{
_mailItemSource.RemoveGroup(group.Key);
}
}
public async Task AddAsync(MailCopy addedItem)
@@ -109,6 +117,9 @@ namespace Wino.Mail.ViewModels.Collections
var groupCount = _mailItemSource.Count;
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
for (int i = 0; i < groupCount; i++)
{
if (shouldExit) break;
@@ -119,10 +130,6 @@ namespace Wino.Mail.ViewModels.Collections
{
var item = group[k];
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false)
{
shouldExit = true;
@@ -140,23 +147,32 @@ namespace Wino.Mail.ViewModels.Collections
var existingGroupKey = GetGroupingKey(threadMailItemViewModel);
threadMailItemViewModel.AddMailItemViewModel(addedItem);
await ExecuteUIThread(() => { threadMailItemViewModel.AddMailItemViewModel(addedItem); });
var newGroupKey = GetGroupingKey(threadMailItemViewModel);
if (!existingGroupKey.Equals(newGroupKey))
{
await RemoveItemInternalAsync(group, threadMailItemViewModel);
await InsertItemInternalAsync(newGroupKey, threadMailItemViewModel);
var mailThreadItems = threadMailItemViewModel.GetThreadMailItem();
await ExecuteUIThread(() =>
{
// Group must be changed for this thread.
// Remove the thread first.
RemoveItemInternal(group, threadMailItemViewModel);
// Insert new view model because the previous one might've been deleted with the group.
InsertItemInternal(newGroupKey, new ThreadMailItemViewModel(mailThreadItems));
});
}
await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); });
if (!MailCopyIdHashSet.Contains(addedItem.UniqueId))
else
{
MailCopyIdHashSet.Add(addedItem.UniqueId);
await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); });
}
UpdateUniqueIdHashes(addedItem, true);
break;
}
else
@@ -177,10 +193,10 @@ namespace Wino.Mail.ViewModels.Collections
if (item is MailItemViewModel itemViewModel)
{
itemViewModel.Update(addedItem);
await ExecuteUIThread(() => { itemViewModel.Update(addedItem); });
MailCopyIdHashSet.Remove(itemViewModel.UniqueId);
MailCopyIdHashSet.Add(addedItem.UniqueId);
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
}
}
else
@@ -189,15 +205,18 @@ namespace Wino.Mail.ViewModels.Collections
var threadMailItem = new ThreadMailItem();
threadMailItem.AddThreadItem(item);
threadMailItem.AddThreadItem(addedItem);
await ExecuteUIThread(() =>
{
threadMailItem.AddThreadItem(item);
threadMailItem.AddThreadItem(addedItem);
if (threadMailItem.ThreadItems.Count == 1) return;
if (threadMailItem.ThreadItems.Count == 1) return;
var newGroupKey = GetGroupingKey(threadMailItem);
var newGroupKey = GetGroupingKey(threadMailItem);
await RemoveItemInternalAsync(group, item);
await InsertItemInternalAsync(newGroupKey, threadMailItem);
RemoveItemInternal(group, item);
InsertItemInternal(newGroupKey, threadMailItem);
});
}
break;
@@ -208,6 +227,9 @@ namespace Wino.Mail.ViewModels.Collections
// Update properties.
if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel)
{
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
await ExecuteUIThread(() => { itemViewModel.Update(addedItem); });
shouldExit = true;
@@ -224,7 +246,7 @@ namespace Wino.Mail.ViewModels.Collections
var groupKey = GetGroupingKey(addedItem);
await InsertItemInternalAsync(groupKey, addedItem);
await ExecuteUIThread(() => { InsertItemInternal(groupKey, addedItem); });
}
}
@@ -268,12 +290,9 @@ namespace Wino.Mail.ViewModels.Collections
}
else
{
foreach (var item in group)
{
existingGroup.Add(item);
// _mailItemSource.InsertItem(existingGroup, item);
}
}
}
@@ -325,11 +344,14 @@ namespace Wino.Mail.ViewModels.Collections
if (itemContainer == null) return;
// mailCopyIdHashSet.Remove(itemContainer.ItemViewModel.UniqueId);
if (itemContainer.ItemViewModel != null)
{
UpdateUniqueIdHashes(itemContainer.ItemViewModel, false);
}
itemContainer.ItemViewModel?.Update(updatedMailCopy);
// mailCopyIdHashSet.Add(updatedMailCopy.UniqueId);
UpdateUniqueIdHashes(updatedMailCopy, true);
// Call thread notifications if possible.
itemContainer.ThreadViewModel?.NotifyPropertyChanges();
@@ -426,6 +448,8 @@ namespace Wino.Mail.ViewModels.Collections
* -> Remove the thread.
*/
var oldGroupKey = GetGroupingKey(threadMailItemViewModel);
await ExecuteUIThread(() => { threadMailItemViewModel.RemoveCopyItem(removalItem); });
if (threadMailItemViewModel.ThreadItems.Count == 1)
@@ -435,25 +459,37 @@ namespace Wino.Mail.ViewModels.Collections
var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel();
var groupKey = GetGroupingKey(singleViewModel);
await RemoveItemInternalAsync(group, threadMailItemViewModel);
await ExecuteUIThread(() =>
{
RemoveItemInternal(group, threadMailItemViewModel);
InsertItemInternal(groupKey, singleViewModel);
});
// If thread->single conversion is being done, we should ignore it for non-draft items.
// eg. Deleting a reply message from draft folder. Single non-draft item should not be re-added.
if (!PruneSingleNonDraftItems || singleViewModel.IsDraft)
if (PruneSingleNonDraftItems && !singleViewModel.IsDraft)
{
await InsertItemInternalAsync(groupKey, singleViewModel);
// This item should not be here anymore.
// It's basically a reply mail in Draft folder.
var newGroup = _mailItemSource.FirstGroupByKeyOrDefault(groupKey);
if (newGroup != null)
{
await ExecuteUIThread(() => { RemoveItemInternal(newGroup, singleViewModel); });
}
}
}
else if (threadMailItemViewModel.ThreadItems.Count == 0)
{
await RemoveItemInternalAsync(group, threadMailItemViewModel);
await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
}
else
{
// Item inside the thread is removed.
await ExecuteUIThread(() => { threadMailItemViewModel.ThreadItems.Remove(removalItem); });
threadMailItemViewModel.ThreadItems.Remove(removalItem);
UpdateUniqueIdHashes(removalItem, false);
}
shouldExit = true;
@@ -461,7 +497,8 @@ namespace Wino.Mail.ViewModels.Collections
}
else if (item.UniqueId == removeItem.UniqueId)
{
await RemoveItemInternalAsync(group, item);
await ExecuteUIThread(() => { RemoveItemInternal(group, item); });
shouldExit = true;
break;

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Models.MailItem;
@@ -94,5 +95,7 @@ namespace Wino.Mail.ViewModels.Data
OnPropertyChanged(nameof(Subject));
OnPropertyChanged(nameof(PreviewText));
}
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
}
}

View File

@@ -12,18 +12,14 @@ namespace Wino.Mail.ViewModels.Data
/// <summary>
/// Thread mail item (multiple IMailItem) view model representation.
/// </summary>
public class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime>
public partial class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime>
{
public ObservableCollection<IMailItem> ThreadItems => ((IMailItemThread)_threadMailItem).ThreadItems;
private readonly ThreadMailItem _threadMailItem;
[ObservableProperty]
private bool isThreadExpanded;
public bool IsThreadExpanded
{
get => isThreadExpanded;
set => SetProperty(ref isThreadExpanded, value);
}
public ThreadMailItemViewModel(ThreadMailItem threadMailItem)
{
@@ -36,6 +32,8 @@ namespace Wino.Mail.ViewModels.Data
}
}
public ThreadMailItem GetThreadMailItem() => _threadMailItem;
public IEnumerable<MailCopy> GetMailCopies()
=> ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy);
@@ -123,5 +121,7 @@ namespace Wino.Mail.ViewModels.Data
// Get single mail item view model out of the only item in thread items.
public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel;
public IEnumerable<Guid> GetContainingIds() => ((IMailItemThread)_threadMailItem).GetContainingIds();
}
}

View File

@@ -72,7 +72,6 @@ namespace Wino.Mail.ViewModels
private readonly IMailService _mailService;
private readonly IFolderService _folderService;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
private readonly IThreadingStrategyProvider _threadingStrategyProvider;
private readonly IContextMenuItemService _contextMenuItemService;
private readonly IWinoRequestDelegator _winoRequestDelegator;
@@ -143,7 +142,6 @@ namespace Wino.Mail.ViewModels
IMailService mailService,
IStatePersistanceService statePersistanceService,
IFolderService folderService,
IWinoSynchronizerFactory winoSynchronizerFactory,
IThreadingStrategyProvider threadingStrategyProvider,
IContextMenuItemService contextMenuItemService,
IWinoRequestDelegator winoRequestDelegator,
@@ -156,7 +154,6 @@ namespace Wino.Mail.ViewModels
_mailService = mailService;
_folderService = folderService;
_winoSynchronizerFactory = winoSynchronizerFactory;
_threadingStrategyProvider = threadingStrategyProvider;
_contextMenuItemService = contextMenuItemService;
_winoRequestDelegator = winoRequestDelegator;
@@ -172,6 +169,24 @@ namespace Wino.Mail.ViewModels
{
await ExecuteUIThread(() => { SelectedItemCollectionUpdated(a.EventArgs); });
});
MailCollection.MailItemRemoved += (c, removedItem) =>
{
if (removedItem is ThreadMailItemViewModel removedThreadViewModelItem)
{
foreach (var viewModel in removedThreadViewModelItem.ThreadItems.Cast<MailItemViewModel>())
{
if (SelectedItems.Contains(viewModel))
{
SelectedItems.Remove(viewModel);
}
}
}
else if (removedItem is MailItemViewModel removedMailItemViewModel && SelectedItems.Contains(removedMailItemViewModel))
{
SelectedItems.Remove(removedMailItemViewModel);
}
};
}
#region Properties
@@ -310,25 +325,6 @@ namespace Wino.Mail.ViewModels
MailCollection.CoreDispatcher = Dispatcher;
}
//protected override async void OnFolderUpdated(MailItemFolder updatedFolder, MailAccount account)
//{
// base.OnFolderUpdated(updatedFolder, account);
// // Don't need to update if the folder update does not belong to the current folder menu item.
// if (ActiveFolder == null || updatedFolder == null || !ActiveFolder.HandlingFolders.Any(a => a.Id == updatedFolder.Id)) return;
// await ExecuteUIThread(() =>
// {
// ActiveFolder.UpdateFolder(updatedFolder);
// OnPropertyChanged(nameof(CanSynchronize));
// OnPropertyChanged(nameof(IsFolderSynchronizationEnabled));
// });
// // Force synchronization after enabling the folder.
// SyncFolder();
//}
private async void UpdateBarMessage(InfoBarMessageType severity, string title, string message)
{
await ExecuteUIThread(() =>
@@ -603,6 +599,8 @@ namespace Wino.Mail.ViewModels
{
base.OnMailAdded(addedMail);
if (addedMail.AssignedAccount == null || addedMail.AssignedFolder == null) return;
try
{
await listManipulationSemepahore.WaitAsync();
@@ -618,12 +616,9 @@ namespace Wino.Mail.ViewModels
if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return;
await ExecuteUIThread(async () =>
{
await MailCollection.AddAsync(addedMail);
await MailCollection.AddAsync(addedMail);
NotifyItemFoundState();
});
await ExecuteUIThread(() => { NotifyItemFoundState(); });
}
catch { }
finally
@@ -693,6 +688,7 @@ namespace Wino.Mail.ViewModels
gmailUnreadFolderMarkedAsReadUniqueIds.Remove(removedMail.UniqueId);
}
}
protected override async void OnDraftCreated(MailCopy draftMail, MailAccount account)
{
base.OnDraftCreated(draftMail, account);
@@ -704,10 +700,10 @@ namespace Wino.Mail.ViewModels
await listManipulationSemepahore.WaitAsync();
// Create the item. Draft folder navigation is already done at this point.
await ExecuteUIThread(async () =>
{
await MailCollection.AddAsync(draftMail);
await MailCollection.AddAsync(draftMail);
await ExecuteUIThread(() =>
{
// New draft is created by user. Select the item.
Messenger.Send(new MailItemNavigationRequested(draftMail.UniqueId, ScrollToItem: true));
@@ -940,8 +936,6 @@ namespace Wino.Mail.ViewModels
if (navigatingMailItem != null)
WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(navigatingMailItem, message.ScrollToItem));
else
Debugger.Break();
}
#endregion
@@ -980,17 +974,19 @@ namespace Wino.Mail.ViewModels
foreach (var accountId in accountIds)
{
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
// TODO: Server: Check whether account is already synchronizing from the server.
if (synchronizer == null) continue;
//var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
bool isAccountSynchronizing = synchronizer.State != AccountSynchronizerState.Idle;
//if (synchronizer == null) continue;
if (isAccountSynchronizing)
{
isAnyAccountSynchronizing = true;
break;
}
//bool isAccountSynchronizing = synchronizer.State != AccountSynchronizerState.Idle;
//if (isAccountSynchronizing)
//{
// isAnyAccountSynchronizing = true;
// break;
//}
}
}

View File

@@ -37,7 +37,6 @@ namespace Wino.Mail.ViewModels
private readonly IMimeFileService _mimeFileService;
private readonly Core.Domain.Interfaces.IMailService _mailService;
private readonly IFileService _fileService;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
private readonly IWinoRequestDelegator _requestDelegator;
private readonly IClipboardService _clipboardService;
private readonly IUnsubscriptionService _unsubscriptionService;
@@ -124,7 +123,6 @@ namespace Wino.Mail.ViewModels
IMimeFileService mimeFileService,
Core.Domain.Interfaces.IMailService mailService,
IFileService fileService,
IWinoSynchronizerFactory winoSynchronizerFactory,
IWinoRequestDelegator requestDelegator,
IStatePersistanceService statePersistanceService,
IClipboardService clipboardService,
@@ -141,7 +139,6 @@ namespace Wino.Mail.ViewModels
_mimeFileService = mimeFileService;
_mailService = mailService;
_fileService = fileService;
_winoSynchronizerFactory = winoSynchronizerFactory;
_requestDelegator = requestDelegator;
}
@@ -344,30 +341,31 @@ namespace Wino.Mail.ViewModels
}
}
private async Task HandleSingleItemDownloadAsync(MailItemViewModel mailItemViewModel)
{
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItemViewModel.AssignedAccount.Id);
// TODO: Server: Download single mime and report back the item here.
try
{
// To show the progress on the UI.
CurrentDownloadPercentage = 1;
//var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItemViewModel.AssignedAccount.Id);
await synchronizer.DownloadMissingMimeMessageAsync(mailItemViewModel.MailCopy, this, renderCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
Log.Information("MIME download is canceled.");
}
catch (Exception ex)
{
DialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, InfoBarMessageType.Error);
}
finally
{
ResetProgress();
}
//try
//{
// // To show the progress on the UI.
// CurrentDownloadPercentage = 1;
// await synchronizer.DownloadMissingMimeMessageAsync(mailItemViewModel.MailCopy, this, renderCancellationTokenSource.Token);
//}
//catch (OperationCanceledException)
//{
// Log.Information("MIME download is canceled.");
//}
//catch (Exception ex)
//{
// DialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, InfoBarMessageType.Error);
//}
//finally
//{
// ResetProgress();
//}
}
private async Task RenderAsync(MailItemViewModel mailItemViewModel, CancellationToken cancellationToken = default)

View File

@@ -9,8 +9,8 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Messages.Navigation;
using Wino.Core.Requests;
using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
{

View File

@@ -17,6 +17,7 @@
<ItemGroup>
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
<ProjectReference Include="..\Wino.Core\Wino.Core.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,18 +1,14 @@
using System.Diagnostics;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Serilog;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Background;
using Windows.UI.Notifications;
using Wino.Core;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.UWP.Services;
using Wino.Services;
#if NET8_0
using CommunityToolkit.WinUI.Notifications;
@@ -27,26 +23,23 @@ namespace Wino.Activation
private const string BackgroundExecutionLogTag = "[BackgroundExecution] ";
private readonly IWinoRequestDelegator _winoRequestDelegator;
private readonly IBackgroundSynchronizer _backgroundSynchronizer;
private readonly INativeAppService _nativeAppService;
private readonly IWinoRequestProcessor _winoRequestProcessor;
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
private readonly IMailService _mailService;
private ToastArguments _toastArguments;
BackgroundTaskDeferral _deferral;
public BackgroundActivationHandler(IWinoRequestDelegator winoRequestDelegator,
IBackgroundSynchronizer backgroundSynchronizer,
INativeAppService nativeAppService,
IWinoRequestProcessor winoRequestProcessor,
IWinoSynchronizerFactory winoSynchronizerFactory,
IWinoServerConnectionManager winoServerConnectionManager,
IMailService mailService)
{
_winoRequestDelegator = winoRequestDelegator;
_backgroundSynchronizer = backgroundSynchronizer;
_nativeAppService = nativeAppService;
_winoRequestProcessor = winoRequestProcessor;
_winoSynchronizerFactory = winoSynchronizerFactory;
_winoServerConnectionManager = winoServerConnectionManager;
_mailService = mailService;
}
@@ -90,38 +83,28 @@ namespace Wino.Activation
{
// We need to synchronize changes without reflection the UI changes.
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItem.AssignedAccount.Id);
// var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(mailItem.AssignedAccount.Id);
var prepRequest = new MailOperationPreperationRequest(action, mailItem);
var requests = await _winoRequestProcessor.PrepareRequestsAsync(prepRequest);
foreach (var request in requests)
{
synchronizer.QueueRequest(request);
_winoServerConnectionManager.QueueRequest(request, mailItem.AssignedAccount.Id);
// synchronizer.QueueRequest(request);
}
var options = new SynchronizationOptions()
{
Type = SynchronizationType.ExecuteRequests,
AccountId = mailItem.AssignedAccount.Id
};
//var options = new SynchronizationOptions()
//{
// Type = SynchronizationType.ExecuteRequests,
// AccountId = mailItem.AssignedAccount.Id
//};
await synchronizer.SynchronizeAsync(options);
//await synchronizer.SynchronizeAsync(options);
}
}
}
else if (taskName == BackgroundTaskService.BackgroundSynchronizationTimerTaskNameEx)
{
var watch = new Stopwatch();
watch.Start();
// Run timer based background synchronization.
await _backgroundSynchronizer.RunBackgroundSynchronizationAsync(BackgroundSynchronizationReason.Timer);
watch.Stop();
Log.Information($"{BackgroundExecutionLogTag}Background synchronization is completed in {watch.Elapsed.TotalSeconds} seconds.");
}
instance.Canceled -= OnBackgroundExecutionCanceled;

View File

@@ -8,6 +8,14 @@ using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.Core;
using Windows.Foundation.Metadata;
using Windows.Storage;
using Windows.System.Profile;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Activation;
@@ -22,6 +30,25 @@ namespace Wino
{
private BackgroundTaskDeferral backgroundTaskDeferral;
private readonly IWinoServerConnectionManager<AppServiceConnection> _appServiceConnectionManager;
private readonly ILogInitializer _logInitializer;
private readonly IThemeService _themeService;
private readonly IDatabaseService _databaseService;
private readonly IApplicationConfiguration _appInitializerService;
private readonly ITranslationService _translationService;
private readonly IApplicationConfiguration _applicationFolderConfiguration;
// Order matters.
private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>()
{
_databaseService,
_appServiceConnectionManager,
_translationService,
_themeService,
};
public App()
{
InitializeComponent();
@@ -30,6 +57,9 @@ namespace Wino
EnteredBackground += OnEnteredBackground;
LeavingBackground += OnLeavingBackground;
Resuming += OnResuming;
Suspending += OnSuspending;
Services = ConfigureServices();
_logInitializer = Services.GetService<ILogInitializer>();
@@ -39,19 +69,135 @@ namespace Wino
ConfigurePrelaunch();
ConfigureXbox();
_applicationFolderConfiguration = Services.GetService<IApplicationConfiguration>();
// Make sure the paths are setup on app start.
_applicationFolderConfiguration.ApplicationDataFolderPath = ApplicationData.Current.LocalFolder.Path;
_applicationFolderConfiguration.PublisherSharedFolderPath = ApplicationData.Current.GetPublisherCacheFolder(ApplicationConfiguration.SharedFolderName).Path;
_appServiceConnectionManager = Services.GetService<IWinoServerConnectionManager<AppServiceConnection>>();
_themeService = Services.GetService<IThemeService>();
_databaseService = Services.GetService<IDatabaseService>();
_appInitializerService = Services.GetService<IAppInitializerService>();
_synchronizerFactory = Services.GetService<IWinoSynchronizerFactory>();
_appInitializerService = Services.GetService<IApplicationConfiguration>();
_translationService = Services.GetService<ITranslationService>();
_appShellService = Services.GetService<IAppShellService>();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
private async void OnResuming(object sender, object e)
{
// App Service connection was lost on suspension.
// We must restore it.
// Server might be running already, but re-launching it will trigger a new connection attempt.
await _appServiceConnectionManager.ConnectAsync();
}
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
deferral.Complete();
}
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()
{
var services = new ServiceCollection();
services.RegisterCoreServices();
services.RegisterCoreUWPServices();
RegisterUWPServices(services);
RegisterViewModels(services);
RegisterActivationHandlers(services);
return services.BuildServiceProvider();
}
#region Dependency Injection
private void RegisterActivationHandlers(IServiceCollection services)
{
services.AddTransient<ProtocolActivationHandler>();
// services.AddTransient<BackgroundActivationHandler>();
services.AddTransient<ToastNotificationActivationHandler>();
services.AddTransient<FileActivationHandler>();
}
private void RegisterUWPServices(IServiceCollection services)
{
services.AddSingleton<IApplicationResourceManager<ResourceDictionary>, ApplicationResourceManager>();
services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<IPreferencesService, PreferencesService>();
services.AddSingleton<IStatePersistanceService, StatePersistenceService>();
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
services.AddSingleton<IWinoNavigationService, WinoNavigationService>();
services.AddSingleton<IDialogService, DialogService>();
}
private void RegisterViewModels(IServiceCollection services)
{
services.AddSingleton(typeof(AppShellViewModel));
services.AddTransient(typeof(SettingsDialogViewModel));
services.AddTransient(typeof(PersonalizationPageViewModel));
services.AddTransient(typeof(SettingOptionsPageViewModel));
services.AddTransient(typeof(MailListPageViewModel));
services.AddTransient(typeof(MailRenderingPageViewModel));
services.AddTransient(typeof(AccountManagementViewModel));
services.AddTransient(typeof(WelcomePageViewModel));
services.AddTransient(typeof(AboutPageViewModel));
services.AddTransient(typeof(ComposePageViewModel));
services.AddTransient(typeof(IdlePageViewModel));
services.AddTransient(typeof(SettingsPageViewModel));
services.AddTransient(typeof(NewAccountManagementPageViewModel));
services.AddTransient(typeof(AccountDetailsPageViewModel));
services.AddTransient(typeof(SignatureManagementPageViewModel));
services.AddTransient(typeof(MessageListPageViewModel));
services.AddTransient(typeof(ReadingPanePageViewModel));
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
services.AddTransient(typeof(LanguageTimePageViewModel));
}
#endregion
#region Misc Configuration
private void ConfigureLogger() => _logInitializer.SetupLogger(ApplicationData.Current.LocalFolder.Path);
private void ConfigureAppCenter() => AppCenter.Start(AppCenterKey, typeof(Analytics), typeof(Crashes));
private void ConfigurePrelaunch()
{
if (ApiInformation.IsMethodPresent("Windows.ApplicationModel.Core.CoreApplication", "EnablePrelaunch"))
CoreApplication.EnablePrelaunch(true);
}
private void ConfigureXbox()
{
// Xbox users should use Reveal focus.
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6))
{
FocusVisualKind = AnalyticsInfo.VersionInfo.DeviceFamily == "Xbox" ? FocusVisualKind.Reveal : FocusVisualKind.HighVisibility;
}
}
private void ConfigureTitleBar()
{
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
var applicationViewTitleBar = ApplicationView.GetForCurrentView().TitleBar;
// Extend shell content into core window to meet design requirements.
coreTitleBar.ExtendViewIntoTitleBar = true;
// Change system buttons and background colors to meet design requirements.
applicationViewTitleBar.ButtonBackgroundColor = Colors.Transparent;
applicationViewTitleBar.BackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
applicationViewTitleBar.ButtonForegroundColor = Colors.White;
}
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
@@ -96,6 +242,20 @@ namespace Wino
{
base.OnBackgroundActivated(args);
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails appServiceTriggerDetails)
{
// Only accept connections from callers in the same package
if (appServiceTriggerDetails.CallerPackageFamilyName == Package.Current.Id.FamilyName)
{
// Connection established from the fulltrust process
backgroundTaskDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnBackgroundTaskCanceled;
_appServiceConnectionManager.Connection = appServiceTriggerDetails.AppServiceConnection;
}
}
LogActivation($"OnBackgroundActivated -> {args.GetType().Name}, TaskInstanceIdName -> {args.TaskInstance?.Task?.Name ?? "NA"}");
await ActivateWinoAsync(args);
@@ -121,7 +281,10 @@ namespace Wino
private async Task ActivateWinoAsync(object args)
{
await PreInitializationAsync();
foreach (var service in initializeServices)
{
await service.InitializeAsync();
}
if (IsInteractiveLaunchArgs(args))
{
@@ -163,5 +326,25 @@ namespace Wino
}
}
}
private IEnumerable<ActivationHandler> GetActivationHandlers()
{
yield return Services.GetService<ProtocolActivationHandler>();
// yield return Services.GetService<BackgroundActivationHandler>(); // Old UWP background task handler.
yield return Services.GetService<ToastNotificationActivationHandler>();
yield return Services.GetService<FileActivationHandler>();
}
public async void OnBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
Log.Information($"Background task {sender.Task.Name} was canceled. Reason: {reason}");
await _appServiceConnectionManager.DisconnectAsync();
backgroundTaskDeferral?.Complete();
backgroundTaskDeferral = null;
_appServiceConnectionManager.Connection = null;
}
}
}

View File

@@ -514,6 +514,14 @@
IsOpen="False"
PreferredPlacement="Bottom"
Target="{x:Bind ShellInfoBar}" />
<Grid
Grid.Column="1"
Padding="14"
HorizontalAlignment="Right"
VerticalAlignment="Bottom">
<Button Content="{x:Bind ViewModel.ActiveConnectionStatus, Mode=OneWay}" Command="{x:Bind ViewModel.ReconnectServerCommand}" />
</Grid>
</Grid>
</muxc:NavigationView>

View File

@@ -8,7 +8,7 @@ using Windows.UI.ViewManagement.Core;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Domain.Models.Reader;
using Wino.Views.Settings;
#if NET8_0
@@ -320,41 +320,41 @@ namespace Wino.Dialogs
{
var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson);
if (change.type == "bold")
if (change.Type == "bold")
{
BoldButton.IsChecked = change.value == "true";
BoldButton.IsChecked = change.Value == "true";
}
else if (change.type == "italic")
else if (change.Type == "italic")
{
ItalicButton.IsChecked = change.value == "true";
ItalicButton.IsChecked = change.Value == "true";
}
else if (change.type == "underline")
else if (change.Type == "underline")
{
UnderlineButton.IsChecked = change.value == "true";
UnderlineButton.IsChecked = change.Value == "true";
}
else if (change.type == "strikethrough")
else if (change.Type == "strikethrough")
{
StrokeButton.IsChecked = change.value == "true";
StrokeButton.IsChecked = change.Value == "true";
}
else if (change.type == "ol")
else if (change.Type == "ol")
{
OrderedListButton.IsChecked = change.value == "true";
OrderedListButton.IsChecked = change.Value == "true";
}
else if (change.type == "ul")
else if (change.Type == "ul")
{
BulletListButton.IsChecked = change.value == "true";
BulletListButton.IsChecked = change.Value == "true";
}
else if (change.type == "indent")
else if (change.Type == "indent")
{
IncreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
IncreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.type == "outdent")
else if (change.Type == "outdent")
{
DecreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
DecreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.type == "alignment")
else if (change.Type == "alignment")
{
var parsedValue = change.value switch
var parsedValue = change.Value switch
{
"jodit-icon_left" => 0,
"jodit-icon_center" => 1,

View File

@@ -92,13 +92,6 @@
Enabled="false"
DisplayName="Wino Startup Service" />
</uap5:Extension>
<!-- Registration of full trust backend application. -->
<!--<uap:Extension Category="windows.appService">
<uap:AppService Name="Wino.AppService" />
</uap:Extension>-->
<!--<desktop:Extension Category="windows.fullTrustProcess" Executable="Wino.AppService.exe" />-->
</Extensions>
</Application>
</Applications>

View File

@@ -16,7 +16,6 @@ using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Messages.Shell;
using Wino.Core.Messages.Synchronization;
using Wino.Core.Requests;
using Wino.Core.UWP.Extensions;
using Wino.Dialogs;
using Wino.Helpers;

View File

@@ -20,7 +20,7 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Domain.Models.Reader;
using Wino.Core.Messages.Mails;
using Wino.Core.Messages.Shell;
using Wino.Extensions;
@@ -486,41 +486,41 @@ namespace Wino.Views
{
var change = JsonSerializer.Deserialize<WebViewMessage>(args.WebMessageAsJson);
if (change.type == "bold")
if (change.Type == "bold")
{
BoldButton.IsChecked = change.value == "true";
BoldButton.IsChecked = change.Value == "true";
}
else if (change.type == "italic")
else if (change.Type == "italic")
{
ItalicButton.IsChecked = change.value == "true";
ItalicButton.IsChecked = change.Value == "true";
}
else if (change.type == "underline")
else if (change.Type == "underline")
{
UnderlineButton.IsChecked = change.value == "true";
UnderlineButton.IsChecked = change.Value == "true";
}
else if (change.type == "strikethrough")
else if (change.Type == "strikethrough")
{
StrokeButton.IsChecked = change.value == "true";
StrokeButton.IsChecked = change.Value == "true";
}
else if (change.type == "ol")
else if (change.Type == "ol")
{
OrderedListButton.IsChecked = change.value == "true";
OrderedListButton.IsChecked = change.Value == "true";
}
else if (change.type == "ul")
else if (change.Type == "ul")
{
BulletListButton.IsChecked = change.value == "true";
BulletListButton.IsChecked = change.Value == "true";
}
else if (change.type == "indent")
else if (change.Type == "indent")
{
IncreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
IncreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.type == "outdent")
else if (change.Type == "outdent")
{
DecreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
DecreaseIndentButton.IsEnabled = change.Value == "disabled" ? false : true;
}
else if (change.type == "alignment")
else if (change.Type == "alignment")
{
var parsedValue = change.value switch
var parsedValue = change.Value switch
{
"jodit-icon_left" => 0,
"jodit-icon_center" => 1,

View File

@@ -6,8 +6,8 @@ using MoreLinq;
using Wino.Core.Domain.Enums;
using Wino.Core.Messages.Navigation;
using Wino.Core.Requests;
using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Server;
using Wino.Views.Abstract;
using Wino.Views.Account;
using Wino.Views.Settings;

View File

@@ -336,8 +336,6 @@
<Compile Include="Services\ApplicationResourceManager.cs" />
<Compile Include="Services\DialogService.cs" />
<Compile Include="Services\LaunchProtocolService.cs" />
<Compile Include="Services\PreferencesService.cs" />
<Compile Include="Services\StatePersistenceService.cs" />
<Compile Include="Services\ToastActivationService.cs" />
<Compile Include="Services\WinoNavigationService.cs" />
<Compile Include="Styles\CommandBarItems.xaml.cs">
@@ -770,7 +768,6 @@
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
<None Include="Wino.Mail_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Thumbnails\uber.com.png" />
@@ -802,6 +799,10 @@
<Project>{d62f1c03-da57-4709-a640-0283296a8e66}</Project>
<Name>Wino.Mail.ViewModels</Name>
</ProjectReference>
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj">
<Project>{0c307d7e-256f-448c-8265-5622a812fbcc}</Project>
<Name>Wino.Messaging</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<SDKReference Include="WindowsDesktop, Version=10.0.22621.0">

View File

@@ -0,0 +1,14 @@
using System;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Messaging.Client.Accounts
{
/// <summary>
/// When menu item for the account is requested to be extended.
/// Additional properties are also supported to navigate to correct IMailItem.
/// </summary>
/// <param name="AutoSelectAccount">Account to extend menu item for.</param>
/// <param name="FolderId">Folder to select after expansion.</param>
/// <param name="NavigateMailItem">Mail item to select if possible in the expanded folder.</param>
public record AccountMenuItemExtended(Guid FolderId, IMailItem NavigateMailItem);
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
namespace Wino.Messaging.Client.Accounts
{
/// <summary>
/// Emitted when account menu items are reordered.
/// </summary>
/// <param name="newOrderDictionary">New order info.</param>
public record AccountMenuItemsReordered(Dictionary<Guid, int> newOrderDictionary);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Accounts
{
/// <summary>
/// When a full menu refresh for accounts menu is requested.
/// </summary>
public record AccountsMenuRefreshRequested(bool AutomaticallyNavigateFirstItem = true);
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Wino.Messaging.Client.Authorization
{
/// <summary>
/// When Google authentication makes a callback to the app via protocol activation to the app.
/// </summary>
/// <param name="AuthorizationResponseUri">Callback Uri that Google returned.</param>
public record ProtocolAuthorizationCallbackReceived(Uri AuthorizationResponseUri);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When rendered html is requested to cancel.
/// </summary>
public record CancelRenderingContentRequested;
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When reset all mail selections requested.
/// </summary>
public record ClearMailSelectionsRequested;
}

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Models.Reader;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When a new composing requested.
/// </summary>
/// <param name="RenderModel"></param>
public record CreateNewComposeMailRequested(MailRenderModel RenderModel);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When rendering frame should be disposed.
/// </summary>
public class DisposeRenderingFrameRequested { }
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When existing a new html is requested to be rendered due to mail selection or signature.
/// </summary>
/// <param name="HtmlBody">HTML to render in WebView2.</param>
public record HtmlRenderingRequested(string HtmlBody);
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When IMAP setup dialog requestes back breadcrumb navigation.
/// Not providing PageType will go back to previous page by doing back navigation.
/// </summary>
/// <param name="PageType">Type to go back.</param>
/// <param name="Parameter">Back parameters.</param>
public record ImapSetupBackNavigationRequested(Type PageType = null, object Parameter = null);
}

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Entities;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When user asked to dismiss IMAP setup dialog.
/// </summary>
/// <param name="CompletedServerInformation"> Validated server information that is ready to be saved to database. </param>
public record ImapSetupDismissRequested(CustomServerInformation CompletedServerInformation = null);
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When IMAP setup dialog breadcrumb navigation requested.
/// </summary>
/// <param name="PageType">Page type to navigate.</param>
/// <param name="Parameter">Navigation parameters.</param>
public record ImapSetupNavigationRequested(Type PageType, object Parameter);
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When a IMailItem needs to be navigated (or selected)
/// </summary>
/// <param name="UniqueMailId">UniqueId of the mail to navigate.</param>
/// <param name="ScrollToItem">Whether navigated item should be scrolled to or not..</param>
public record MailItemNavigationRequested(Guid UniqueMailId, bool ScrollToItem = false);
}

View File

@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// Selects the given FolderMenuItem in the shell folders list.
/// </summary>
public class NavigateMailFolderEvent : NavigateMailFolderEventArgs
{
public NavigateMailFolderEvent(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool> folderInitLoadAwaitTask = null)
: base(baseFolderMenuItem, folderInitLoadAwaitTask)
{
}
}
}

View File

@@ -0,0 +1,6 @@
using System;
namespace Wino.Messaging.Client.Mails
{
public record RefreshUnreadCountsMessage(Guid AccountId);
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When mail save as PDF requested.
/// </summary>
public record SaveAsPDFRequested(string FileSavePath);
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Messaging.Client.Mails
{
/// <summary>
/// When selected mail count is changed.
/// </summary>
/// <param name="SelectedItemCount">New selected mail count.</param>
public record SelectedMailItemsChanged(int SelectedItemCount);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Navigation
{
/// <summary>
/// When back navigation is requested for breadcrumb pages.
/// </summary>
public record BackBreadcrumNavigationRequested { }
}

View File

@@ -0,0 +1,12 @@
using Wino.Core.Domain.Enums;
namespace Wino.Messaging.Client.Navigation
{
/// <summary>
/// When Breadcrumb control navigation requested.
/// </summary>
/// <param name="PageTitle">Title to display for the page.</param>
/// <param name="PageType">Enum equilavent of the page to navigate.</param>
/// <param name="Parameter">Additional parameters to the page.</param>
public record BreadcrumbNavigationRequested(string PageTitle, WinoPage PageType, object Parameter = null);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Navigation
{
/// <summary>
/// Navigates to settings page.
/// </summary>
public record NavigateSettingsRequested;
}

View File

@@ -0,0 +1,8 @@
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When the application theme changed.
/// </summary>
/// <param name="IsUnderlyingThemeDark"></param>
public record ApplicationThemeChanged(bool IsUnderlyingThemeDark);
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using Wino.Core.Domain.Entities;
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When
/// - There is no selection of any folder for any account
/// - Multiple accounts exists
/// - User clicked 'Create New Mail'
///
/// flyout must be presented to pick correct account.
/// This message will be picked up by UWP Shell.
/// </summary>
public record CreateNewMailWithMultipleAccountsRequested(IEnumerable<MailAccount> AllAccounts);
}

View File

@@ -0,0 +1,17 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// For displaying right sliding notification message in shell.
/// </summary>
/// <param name="Severity">Severity of notification.</param>
/// <param name="Title">Title of the message.</param>
/// <param name="Message">Message content.</param>
public record InfoBarMessageRequested(InfoBarMessageType Severity,
string Title,
string Message,
string ActionButtonTitle = "",
Action Action = null);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When application language is updated.
/// </summary>
public record LanguageChanged;
}

View File

@@ -0,0 +1,4 @@
namespace Wino.Messaging.Client.Shell
{
public class MailtoProtocolMessageRequested { }
}

View File

@@ -0,0 +1,10 @@
using Wino.Core.Domain.Enums;
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When navigation pane mode is changed.
/// </summary>
/// <param name="NewMode">New navigation mode.</param>
public record NavigationPaneModeChanged(MenuPaneMode NewMode);
}

View File

@@ -0,0 +1,7 @@
namespace Wino.Messaging.Client.Shell
{
/// <summary>
/// When reading mail state or reader pane narrowed state is changed.
/// </summary>
public record ShellStateUpdated;
}

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