Import functionality for wino accounts, calendar sync UI, bunch of shell improvements

This commit is contained in:
Burak Kaan Köse
2026-04-04 20:23:20 +02:00
parent 1667aa34db
commit 1d0fcfb5b0
68 changed files with 2792 additions and 519 deletions
+147 -51
View File
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System;
using System.IO;
@@ -48,7 +49,8 @@ public partial class App : WinoApplication,
IRecipient<NewCalendarSynchronizationRequested>,
IRecipient<AccountCreatedMessage>,
IRecipient<AccountRemovedMessage>,
IRecipient<GetStartedFromWelcomeRequested>
IRecipient<GetStartedFromWelcomeRequested>,
IRecipient<WelcomeImportCompletedMessage>
{
private const int InboxSyncsPerFullSync = 20;
private const string ToggleDefaultModeLaunchArgument = "--mode=toggle-default";
@@ -63,7 +65,7 @@ public partial class App : WinoApplication,
private bool _isExiting;
private CancellationTokenSource? _autoSynchronizationLoopCts;
private readonly SemaphoreSlim _autoSynchronizationSemaphore = new(1, 1);
private readonly Dictionary<Guid, int> _inboxSyncCounters = [];
private readonly ConcurrentDictionary<Guid, int> _inboxSyncCounters = [];
private NativeTrayIcon? _trayIcon;
internal bool IsExiting => _isExiting;
@@ -756,7 +758,9 @@ public partial class App : WinoApplication,
/// Creates the main window without activating it.
/// Used for both normal launch and startup task launch (tray only).
/// </summary>
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args, string? forcedLaunchArguments = null)
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args,
string? forcedLaunchArguments = null,
ShellModeActivationContext? activationContextOverride = null)
{
LogActivation("Creating main window.");
@@ -769,14 +773,28 @@ public partial class App : WinoApplication,
windowManager.SetPrimaryNavigationFrame(WinoWindowKind.Shell, shellWindow.GetMainFrame());
var navigationService = Services.GetRequiredService<INavigationService>();
var defaultMode = _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail;
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if (activationContextOverride != null)
{
var targetMode = !string.IsNullOrWhiteSpace(forcedLaunchArguments)
? AppModeActivationResolver.Resolve(forcedLaunchArguments, null, null, defaultMode)
: TryResolveActivationMode(activationArgs, defaultMode, out var resolvedActivationMode)
? resolvedActivationMode
: AppModeActivationResolver.Resolve(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine, defaultMode);
navigationService.ChangeApplicationMode(targetMode, activationContextOverride);
return;
}
if (!string.IsNullOrWhiteSpace(forcedLaunchArguments))
{
shellWindow.HandleAppActivation(forcedLaunchArguments);
return;
}
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
activationArgs.Data is ILaunchActivatedEventArgs launchArgs)
{
@@ -791,7 +809,7 @@ public partial class App : WinoApplication,
return;
}
if (TryResolveActivationMode(activationArgs, _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail, out var activationMode))
if (TryResolveActivationMode(activationArgs, defaultMode, out var activationMode))
{
shellWindow.HandleAppActivation(GetModeLaunchArgument(activationMode));
return;
@@ -859,6 +877,7 @@ public partial class App : WinoApplication,
WeakReferenceMessenger.Default.Register<AccountCreatedMessage>(this);
WeakReferenceMessenger.Default.Register<AccountRemovedMessage>(this);
WeakReferenceMessenger.Default.Register<GetStartedFromWelcomeRequested>(this);
WeakReferenceMessenger.Default.Register<WelcomeImportCompletedMessage>(this);
}
public async void Receive(NewMailSynchronizationRequested message)
@@ -882,6 +901,11 @@ public partial class App : WinoApplication,
syncResult.CompletedState,
message.Options.GroupedSynchronizationTrackingId));
if (syncResult.CompletedState is SynchronizationCompletedState.Success or SynchronizationCompletedState.PartiallyCompleted)
{
await ClearInvalidCredentialAttentionIfNeededAsync(message.Options.AccountId).ConfigureAwait(false);
}
if (syncResult.CompletedState == SynchronizationCompletedState.Failed ||
syncResult.CompletedState == SynchronizationCompletedState.PartiallyCompleted)
{
@@ -906,7 +930,12 @@ public partial class App : WinoApplication,
var dialogService = Services.GetRequiredService<IMailDialogService>();
dialogService.InfoBarMessage(
Translator.Info_SyncFailedTitle,
Translator.Exception_FailedToSynchronizeFolders,
message.Options.Type switch
{
CalendarSynchronizationType.CalendarMetadata => Translator.Exception_FailedToSynchronizeCalendarMetadata,
CalendarSynchronizationType.Strict => Translator.Exception_FailedToSynchronizeCalendarData,
_ => Translator.Exception_FailedToSynchronizeCalendarEvents
},
InfoBarMessageType.Error);
}
}
@@ -942,6 +971,47 @@ public partial class App : WinoApplication,
});
}
public void Receive(WelcomeImportCompletedMessage message)
{
_hasConfiguredAccounts = message.ImportedMailboxCount > 0;
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
if (windowManager.GetWindow(WinoWindowKind.Welcome) == null)
return;
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
{
if (_preferencesService != null)
{
_preferencesService.PreferenceChanged -= PreferencesServiceChanged;
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
}
CreateWindow(
null,
GetModeLaunchArgument(WinoApplicationMode.Mail),
new ShellModeActivationContext
{
SuppressStartupFlows = true
});
await LoadInitialWinoAccountAsync();
CloseWelcomeWindowIfPresent();
if (MainWindow != null)
{
await ActivateWindowAsync(MainWindow);
}
RestartAutoSynchronizationLoop();
Services.GetRequiredService<IMailDialogService>().InfoBarMessage(
Translator.GeneralTitle_Info,
Translator.WinoAccount_Management_ImportReloginReminder,
InfoBarMessageType.Information);
});
}
public void Receive(AccountRemovedMessage message)
{
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
@@ -1078,52 +1148,16 @@ public partial class App : WinoApplication,
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
var currentAccountIds = accounts.Select(a => a.Id).ToHashSet();
_inboxSyncCounters.Keys.Where(a => !currentAccountIds.Contains(a)).ToList().ForEach(a => _inboxSyncCounters.Remove(a));
foreach (var account in accounts)
foreach (var staleAccountId in _inboxSyncCounters.Keys.Where(a => !currentAccountIds.Contains(a)).ToList())
{
cancellationToken.ThrowIfCancellationRequested();
if (_synchronizationManager.IsAccountSynchronizing(account.Id))
continue;
var inboxSyncOptions = new MailSynchronizationOptions()
{
AccountId = account.Id,
Type = MailSynchronizationType.InboxOnly
};
var inboxSyncResult = await _synchronizationManager.SynchronizeMailAsync(inboxSyncOptions, cancellationToken).ConfigureAwait(false);
if (inboxSyncResult.CompletedState is SynchronizationCompletedState.Success or SynchronizationCompletedState.PartiallyCompleted)
{
_inboxSyncCounters.TryAdd(account.Id, 0);
_inboxSyncCounters[account.Id]++;
if (_inboxSyncCounters[account.Id] >= InboxSyncsPerFullSync)
{
var fullSyncOptions = new MailSynchronizationOptions()
{
AccountId = account.Id,
Type = MailSynchronizationType.FullFolders
};
await _synchronizationManager.SynchronizeMailAsync(fullSyncOptions, cancellationToken).ConfigureAwait(false);
_inboxSyncCounters[account.Id] = 0;
}
}
if (!account.IsCalendarAccessGranted)
continue;
var calendarOptions = new CalendarSynchronizationOptions()
{
AccountId = account.Id,
Type = CalendarSynchronizationType.CalendarMetadata
};
await _synchronizationManager.SynchronizeCalendarAsync(calendarOptions, cancellationToken).ConfigureAwait(false);
_inboxSyncCounters.TryRemove(staleAccountId, out _);
}
var synchronizationTasks = accounts
.Select(account => ExecuteAutoSynchronizationForAccountAsync(account, cancellationToken))
.ToList();
await Task.WhenAll(synchronizationTasks).ConfigureAwait(false);
}
finally
{
@@ -1134,6 +1168,68 @@ public partial class App : WinoApplication,
}
}
private async Task ExecuteAutoSynchronizationForAccountAsync(Wino.Core.Domain.Entities.Shared.MailAccount account, CancellationToken cancellationToken)
{
if (_synchronizationManager == null)
return;
cancellationToken.ThrowIfCancellationRequested();
if (_synchronizationManager.IsAccountSynchronizing(account.Id))
return;
var inboxSyncOptions = new MailSynchronizationOptions
{
AccountId = account.Id,
Type = MailSynchronizationType.InboxOnly
};
var inboxSyncResult = await _synchronizationManager.SynchronizeMailAsync(inboxSyncOptions, cancellationToken).ConfigureAwait(false);
if (inboxSyncResult.CompletedState is SynchronizationCompletedState.Success or SynchronizationCompletedState.PartiallyCompleted)
{
await ClearInvalidCredentialAttentionIfNeededAsync(account.Id).ConfigureAwait(false);
var inboxSyncCount = _inboxSyncCounters.AddOrUpdate(account.Id, 1, (_, currentCount) => currentCount + 1);
if (inboxSyncCount >= InboxSyncsPerFullSync)
{
var fullSyncOptions = new MailSynchronizationOptions
{
AccountId = account.Id,
Type = MailSynchronizationType.FullFolders
};
await _synchronizationManager.SynchronizeMailAsync(fullSyncOptions, cancellationToken).ConfigureAwait(false);
_inboxSyncCounters[account.Id] = 0;
}
}
if (!account.IsCalendarAccessGranted)
return;
var calendarOptions = new CalendarSynchronizationOptions
{
AccountId = account.Id,
Type = CalendarSynchronizationType.CalendarMetadata
};
await _synchronizationManager.SynchronizeCalendarAsync(calendarOptions, cancellationToken).ConfigureAwait(false);
}
private async Task ClearInvalidCredentialAttentionIfNeededAsync(Guid accountId)
{
if (_accountService == null)
return;
var account = await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
if (account?.AttentionReason != AccountAttentionReason.InvalidCredentials)
return;
await _accountService.ClearAccountAttentionAsync(accountId).ConfigureAwait(false);
}
/// <summary>
/// Handles activation redirected from another instance (single-instancing).
/// This is called when a second instance tries to launch and redirects to this existing instance.