Removed migrations. New onboarding screen and wizard like steps.

This commit is contained in:
Burak Kaan Köse
2026-03-06 03:42:08 +01:00
parent db5ecd60e4
commit aaa6e8a2c9
56 changed files with 1843 additions and 554 deletions
@@ -86,249 +86,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
return;
}
MailAccount createdAccount = null;
IAccountCreationDialog creationDialog = null;
bool creationDialogClosed = false;
try
{
var providers = ProviderService.GetAvailableProviders();
// Select provider.
var accountCreationDialogResult = await ExecuteUIThreadTaskAsync(() => MailDialogService.ShowAccountProviderSelectionDialogAsync(providers));
if (accountCreationDialogResult != null)
{
CustomServerInformation customServerInformation = null;
createdAccount = new MailAccount()
{
ProviderType = accountCreationDialogResult.ProviderType,
Name = accountCreationDialogResult.AccountName,
SpecialImapProvider = accountCreationDialogResult.SpecialImapProviderDetails?.SpecialImapProvider ?? SpecialImapProvider.None,
Id = Guid.NewGuid(),
AccountColorHex = accountCreationDialogResult.AccountColorHex,
IsCalendarAccessGranted = true // New accounts have calendar scopes
};
if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4)
{
if (createdAccount.SpecialImapProvider == SpecialImapProvider.iCloud || createdAccount.SpecialImapProvider == SpecialImapProvider.Yahoo)
{
var accountCreationCancellationTokenSource = new CancellationTokenSource();
creationDialog = MailDialogService.GetAccountCreationDialog(accountCreationDialogResult);
await ExecuteUIThreadTaskAsync(() => creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource));
await Task.Delay(500);
await ExecuteUIThread(() => creationDialog.State = AccountCreationDialogState.SigningIn);
customServerInformation = _specialImapProviderConfigResolver.GetServerInformation(createdAccount, accountCreationDialogResult)
?? throw new AccountSetupCanceledException();
customServerInformation.Id = Guid.NewGuid();
customServerInformation.AccountId = createdAccount.Id;
createdAccount.Address = accountCreationDialogResult.SpecialImapProviderDetails.Address;
createdAccount.SenderName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName;
createdAccount.IsCalendarAccessGranted = customServerInformation.CalendarSupportMode != ImapCalendarSupportMode.Disabled;
createdAccount.ServerInformation = customServerInformation;
await ValidateSpecialImapConnectivityAsync(customServerInformation).ConfigureAwait(false);
}
else
{
var completionSource = new TaskCompletionSource<ImapCalDavSetupResult>();
var setupContext = ImapCalDavSettingsNavigationContext.CreateForCreateMode(accountCreationDialogResult, completionSource);
await ExecuteUIThread(() => Messenger.Send(new BreadcrumbNavigationRequested(
Translator.ImapCalDavSettingsPage_TitleCreate,
WinoPage.ImapCalDavSettingsPage,
setupContext)));
var setupResult = await completionSource.Task.ConfigureAwait(false)
?? throw new AccountSetupCanceledException();
customServerInformation = setupResult.ServerInformation ?? throw new AccountSetupCanceledException();
customServerInformation.Id = Guid.NewGuid();
customServerInformation.AccountId = createdAccount.Id;
createdAccount.Address = setupResult.EmailAddress;
createdAccount.SenderName = setupResult.DisplayName;
createdAccount.IsCalendarAccessGranted = setupResult.IsCalendarAccessGranted;
createdAccount.ServerInformation = customServerInformation;
}
}
else
{
var accountCreationCancellationTokenSource = new CancellationTokenSource();
creationDialog = MailDialogService.GetAccountCreationDialog(accountCreationDialogResult);
await ExecuteUIThreadTaskAsync(() => creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource));
await Task.Delay(500);
await ExecuteUIThread(() => creationDialog.State = AccountCreationDialogState.SigningIn);
// OAuth authentication is handled here.
// Use SynchronizationManager to handle OAuth authentication.
var authTokenInfo = await SynchronizationManager.Instance.HandleAuthorizationAsync(
accountCreationDialogResult.ProviderType,
createdAccount,
createdAccount.ProviderType == MailProviderType.Gmail);
bool creationCanceled = false;
await ExecuteUIThread(() => creationCanceled = creationDialog.State == AccountCreationDialogState.Canceled);
if (creationCanceled)
throw new AccountSetupCanceledException();
// Update account address with authenticated user information
createdAccount.Address = authTokenInfo.AccountAddress;
}
// Address is still doesn't have a value for API synchronizers.
// It'll be synchronized with profile information.
await AccountService.CreateAccountAsync(createdAccount, customServerInformation);
// Local account has been created.
// Sync profile information if supported.
if (createdAccount.IsProfileInfoSyncSupported)
{
// Start profile information synchronization.
// It's only available for Outlook and Gmail synchronizers.
var profileSynchronizationResult = await SynchronizationManager.Instance.SynchronizeProfileAsync(createdAccount.Id);
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
if (profileSynchronizationResult.ProfileInformation != null)
{
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
if (!string.IsNullOrEmpty(profileSynchronizationResult.ProfileInformation.AccountAddress))
{
createdAccount.Address = profileSynchronizationResult.ProfileInformation.AccountAddress;
}
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
}
}
if (creationDialog != null)
await ExecuteUIThread(() => creationDialog.State = AccountCreationDialogState.PreparingFolders);
var folderSynchronizationResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(createdAccount.Id);
if (folderSynchronizationResult == null || folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
if (createdAccount.IsCalendarAccessGranted)
{
if (creationDialog != null)
await ExecuteUIThread(() => creationDialog.State = AccountCreationDialogState.CalendarMetadataFetch);
var calendarMetadataSynchronizationResult = await SynchronizationManager.Instance.SynchronizeCalendarAsync(new CalendarSynchronizationOptions
{
AccountId = createdAccount.Id,
Type = CalendarSynchronizationType.CalendarMetadata
});
if (calendarMetadataSynchronizationResult == null || calendarMetadataSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeCalendarMetadata);
}
// Sync aliases if supported.
if (createdAccount.IsAliasSyncSupported)
{
// Try to synchronize aliases for the account.
var aliasSynchronizationResult = await SynchronizationManager.Instance.SynchronizeAliasesAsync(createdAccount.Id);
if (aliasSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
}
else
{
// Create root primary alias for the account.
// This is only available for accounts that do not support alias synchronization.
await AccountService.CreateRootAliasAsync(createdAccount.Id, createdAccount.Address);
}
if (creationDialog != null)
{
await ExecuteUIThread(() => creationDialog.Complete(false));
creationDialogClosed = true;
}
// Send changes to listeners.
await ExecuteUIThread(() => ReportUIChange(new AccountCreatedMessage(createdAccount)));
// Notify success.
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreatedTitle, string.Format(Translator.Info_AccountCreatedMessage, createdAccount.Address), InfoBarMessageType.Success));
}
}
catch (Exception ex) when (ex.Message.Contains(nameof(GmailServiceDisabledException)))
{
// For Google Workspace accounts, Gmail API might be disabled by the admin.
// Wino can't continue synchronization in this case.
// We must notify the user about this and prevent account creation.
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.GmailServiceDisabled_Title, Translator.GmailServiceDisabled_Message, InfoBarMessageType.Error));
if (createdAccount != null)
{
await AccountService.DeleteAccountAsync(createdAccount);
}
}
catch (AccountSetupCanceledException)
{
// Ignore
}
catch (Exception ex) when (ex.Message.Contains(nameof(AccountSetupCanceledException)))
{
// Ignore
}
catch (ImapClientPoolException testClientPoolException) when (testClientPoolException.CustomServerInformation != null)
{
var properties = testClientPoolException.CustomServerInformation.GetConnectionProperties();
properties.Add("ProtocolLog", testClientPoolException.ProtocolLog);
properties.Add("DiagnosticId", PreferencesService.DiagnosticId);
_winoLogger.TrackEvent("IMAP Test Failed", properties);
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, testClientPoolException.Message, InfoBarMessageType.Error));
}
catch (ImapClientPoolException clientPoolException) when (clientPoolException.InnerException != null)
{
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error));
}
catch (Exception ex)
{
Log.Error(ex, "Failed to create account.");
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, ex.Message, InfoBarMessageType.Error));
// Delete account in case of failure.
if (createdAccount != null)
{
await AccountService.DeleteAccountAsync(createdAccount);
}
}
finally
{
if (creationDialog != null && !creationDialogClosed)
{
bool isCanceled = false;
await ExecuteUIThread(() => isCanceled = creationDialog.State == AccountCreationDialogState.Canceled);
await ExecuteUIThread(() => creationDialog.Complete(isCanceled));
}
}
Messenger.Send(new BreadcrumbNavigationRequested(Translator.WelcomeWizard_Step2Title, WinoPage.ProviderSelectionPage));
}
public Task StartAddNewAccountAsync() => AddNewAccountAsync();