Add capability-first account and calendar setup flow
This commit is contained in:
@@ -45,6 +45,6 @@ public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
|
||||
return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets()
|
||||
{
|
||||
ClientId = ClientId
|
||||
}, AuthenticatorConfig.GmailScope, account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
|
||||
}, AuthenticatorConfig.GetGmailScope(account?.IsMailAccessGranted != false, account?.IsCalendarAccessGranted == true), account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,10 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
_publicClientApplication = outlookAppBuilder.Build();
|
||||
}
|
||||
|
||||
public string[] Scope => AuthenticatorConfig.OutlookScope;
|
||||
private string[] GetScope(MailAccount account)
|
||||
=> AuthenticatorConfig.GetOutlookScope(
|
||||
account?.IsMailAccessGranted != false,
|
||||
account?.IsCalendarAccessGranted == true);
|
||||
|
||||
private async Task EnsureTokenCacheAttachedAsync()
|
||||
{
|
||||
@@ -91,7 +94,7 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
|
||||
try
|
||||
{
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(Scope, storedAccount).ExecuteAsync();
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(GetScope(account), storedAccount).ExecuteAsync();
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
@@ -122,7 +125,7 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
if (_nativeAppService.GetCoreWindowHwnd == null) throw new AuthenticationAttentionException(account);
|
||||
|
||||
AuthenticationResult authResult = await _publicClientApplication
|
||||
.AcquireTokenInteractive(Scope)
|
||||
.AcquireTokenInteractive(GetScope(account))
|
||||
.ExecuteAsync();
|
||||
|
||||
// If the account is null, it means it's the initial creation of it.
|
||||
|
||||
@@ -299,6 +299,9 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
if (!GroupedAccountCalendarViewModel.SupportsCalendar(account))
|
||||
continue;
|
||||
|
||||
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||
var calendarViewModels = accountCalendars.Select(calendar => new AccountCalendarViewModel(account, calendar)).ToList();
|
||||
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
|
||||
|
||||
@@ -408,6 +408,9 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
if (!GroupedAccountCalendarViewModel.SupportsCalendar(account))
|
||||
continue;
|
||||
|
||||
var calendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||
var viewModels = calendars
|
||||
.Select(calendar => new AccountCalendarViewModel(account, calendar))
|
||||
|
||||
@@ -17,6 +17,9 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
public MailAccount Account { get; }
|
||||
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
|
||||
|
||||
public static bool SupportsCalendar(MailAccount account)
|
||||
=> account?.IsCalendarAccessGranted == true;
|
||||
|
||||
public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable<AccountCalendarViewModel> calendarViewModels)
|
||||
{
|
||||
Account = account;
|
||||
|
||||
@@ -78,6 +78,13 @@ public class MailAccount
|
||||
/// </summary>
|
||||
public SpecialImapProvider SpecialImapProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether mail access is granted for this account.
|
||||
/// When false, mail folders, aliases, compose flows, and mail synchronization are unavailable.
|
||||
/// Default is true for legacy accounts to preserve existing behavior.
|
||||
/// </summary>
|
||||
public bool IsMailAccessGranted { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether calendar access is granted for this account.
|
||||
/// When false, synchronizers will not process EventMessages or calendar invitations.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
public interface IAuthenticatorConfig
|
||||
{
|
||||
string OutlookAuthenticatorClientId { get; }
|
||||
string[] OutlookScope { get; }
|
||||
string[] GetOutlookScope(bool isMailAccessGranted, bool isCalendarAccessGranted);
|
||||
string GmailAuthenticatorClientId { get; }
|
||||
string[] GmailScope { get; }
|
||||
string[] GetGmailScope(bool isMailAccessGranted, bool isCalendarAccessGranted);
|
||||
string GmailTokenStoreIdentifier { get; }
|
||||
}
|
||||
|
||||
@@ -7,4 +7,6 @@ public record AccountCreationDialogResult(
|
||||
string AccountName,
|
||||
SpecialImapProviderDetails SpecialImapProviderDetails,
|
||||
string AccountColorHex,
|
||||
InitialSynchronizationRange InitialSynchronizationRange);
|
||||
InitialSynchronizationRange InitialSynchronizationRange,
|
||||
bool IsMailAccessGranted,
|
||||
bool IsCalendarAccessGranted);
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Select a calendar to configure its settings",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Initial synchronization",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino synchronized your mails until {0} going back.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Connected features",
|
||||
"AccountDetailsPage_CapabilityDescription": "Choose whether this account is used for mail, calendar, or both. Enabling a new feature may ask you to sign in again.",
|
||||
"AccountCapability_MailOnly": "Mail only",
|
||||
"AccountCapability_CalendarOnly": "Calendar only",
|
||||
"AccountCapability_MailAndCalendar": "Mail + Calendar",
|
||||
"AddHyperlink": "Add",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Apply Theme",
|
||||
"Buttons_PopOut": "Pop out",
|
||||
"Buttons_Browse": "Browse",
|
||||
"Buttons_Back": "Back",
|
||||
"Buttons_Cancel": "Cancel",
|
||||
"Buttons_Close": "Close",
|
||||
"Buttons_Copy": "Copy",
|
||||
@@ -681,6 +687,10 @@
|
||||
"NoMailSelected": "No message selected",
|
||||
"NoMessageCrieteria": "No messages match your search criteria",
|
||||
"NoMessageEmptyFolder": "This folder is empty",
|
||||
"MailEmptyState_Title": "No mail-enabled accounts",
|
||||
"MailEmptyState_Message": "You have accounts connected for calendar, but none of them are enabled for mail. Add a mail account or update an existing account to use mail.",
|
||||
"MailEmptyState_AddAccount": "Add account",
|
||||
"MailEmptyState_ManageAccounts": "Manage accounts",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
|
||||
@@ -702,8 +712,8 @@
|
||||
"ProviderDetail_Gmail_Description": "Google Account",
|
||||
"ProviderDetail_iCloud_Description": "Apple iCloud Account",
|
||||
"ProviderDetail_iCloud_Title": "iCloud",
|
||||
"ProviderDetail_IMAP_Description": "Custom IMAP/SMTP server",
|
||||
"ProviderDetail_IMAP_Title": "IMAP Server",
|
||||
"ProviderDetail_IMAP_Description": "IMAP/SMTP mail with CalDAV or local calendar",
|
||||
"ProviderDetail_IMAP_Title": "Custom server",
|
||||
"ProviderDetail_Yahoo_Description": "Yahoo Account",
|
||||
"ProviderDetail_Yahoo_Title": "Yahoo Mail",
|
||||
"QuickEventDialog_EventName": "Event name",
|
||||
@@ -1427,8 +1437,8 @@
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exporting your selected Wino data...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transfer with a JSON file",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Import from or export to a local JSON file. Passwords, tokens, and other sensitive information are not included.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Import JSON",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Export JSON",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Import",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Export",
|
||||
"WinoAccount_Management_LocalDataSaved": "Saved your exported Wino data to {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "The selected JSON file doesn't contain a valid Wino export.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino could not load the latest Wino Account information.",
|
||||
@@ -1527,8 +1537,22 @@
|
||||
"WelcomeWizard_Step3Title": "Finish Setup",
|
||||
"ProviderSelection_Title": "Choose your email provider",
|
||||
"ProviderSelection_Subtitle": "Select a provider below to add your email account to Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Step {0} of 3",
|
||||
"ProviderSelection_IdentityTitle": "Account identity",
|
||||
"ProviderSelection_IdentityDescription": "Choose how this account appears inside Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Provider",
|
||||
"ProviderSelection_ProviderSectionDescription": "Select the service you want to connect.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Use this account for",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Choose whether you want mail, calendar, or both.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "On the next step, secure sign-in will connect your account. If you enable calendar, Wino will also connect Outlook Calendar or Google Calendar automatically.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "On the next step, you'll enter your provider credentials. Mail uses IMAP/SMTP, and calendar can use CalDAV or stay local on this device.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "On the next step, you'll enter your server details. Mail uses IMAP/SMTP, and calendar can use CalDAV or stay local on this device.",
|
||||
"ProviderSelection_AccountNameHeader": "Account Name",
|
||||
"ProviderSelection_AccountNamePlaceholder": "e.g. Personal, Work",
|
||||
"ProviderSelection_UseForMail": "Mail",
|
||||
"ProviderSelection_UseForCalendar": "Calendar",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Choose at least one capability before continuing.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "If you continue with calendar only, the next page will not require an email address.",
|
||||
"ProviderSelection_DisplayNameHeader": "Display Name",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "e.g. John Doe",
|
||||
"ProviderSelection_EmailHeader": "E-mail Address",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||
@@ -83,7 +83,7 @@ public class AccountServiceTests : IAsyncLifetime
|
||||
var secondAccountId = Guid.NewGuid();
|
||||
|
||||
await _accountService.CreateAccountAsync(
|
||||
CreateImapAccount(firstAccountId),
|
||||
CreateImapAccount(firstAccountId, "IMAP Test Account 1", "imap1@test.local"),
|
||||
new CustomServerInformation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -92,7 +92,7 @@ public class AccountServiceTests : IAsyncLifetime
|
||||
});
|
||||
|
||||
await _accountService.CreateAccountAsync(
|
||||
CreateImapAccount(secondAccountId),
|
||||
CreateImapAccount(secondAccountId, "IMAP Test Account 2", "imap2@test.local"),
|
||||
new CustomServerInformation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -119,13 +119,13 @@ public class AccountServiceTests : IAsyncLifetime
|
||||
.BeGreaterThanOrEqualTo(50);
|
||||
}
|
||||
|
||||
private static MailAccount CreateImapAccount(Guid accountId)
|
||||
private static MailAccount CreateImapAccount(Guid accountId, string name = "IMAP Test Account", string address = "imap@test.local")
|
||||
{
|
||||
return new MailAccount
|
||||
{
|
||||
Id = accountId,
|
||||
Name = "IMAP Test Account",
|
||||
Address = "imap@test.local",
|
||||
Name = name,
|
||||
Address = address,
|
||||
SenderName = "IMAP Test",
|
||||
ProviderType = MailProviderType.IMAP4
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ public sealed class MailRequestStateTests
|
||||
[Fact]
|
||||
public void MarkReadRequest_RevertUiChanges_RestoresOriginalReadState()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
|
||||
var mailCopy = CreateMailCopy(isRead: false, isFlagged: false);
|
||||
var request = new MarkReadRequest(mailCopy, IsRead: true);
|
||||
var recipient = new MailRequestRecipient();
|
||||
@@ -35,12 +37,15 @@ public sealed class MailRequestStateTests
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.UnregisterAll(recipient);
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangeFlagRequest_RevertUiChanges_RestoresOriginalFlagState()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
|
||||
var mailCopy = CreateMailCopy(isRead: true, isFlagged: false);
|
||||
var request = new ChangeFlagRequest(mailCopy, IsFlagged: true);
|
||||
var recipient = new MailRequestRecipient();
|
||||
@@ -63,6 +68,7 @@ public sealed class MailRequestStateTests
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.UnregisterAll(recipient);
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ public sealed class BaseSynchronizerUiChangeTests
|
||||
[Fact]
|
||||
public void ApplyOptimisticUiChanges_UsesBundleUiChangeRequest_ForBatchBundle()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
|
||||
var folderId = Guid.NewGuid();
|
||||
var account = new MailAccount { Id = Guid.NewGuid(), Name = "Test account" };
|
||||
var synchronizer = new TestSynchronizer(account);
|
||||
@@ -48,6 +50,7 @@ public sealed class BaseSynchronizerUiChangeTests
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<MailStateUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Unregister<BulkMailStateUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -9,6 +10,7 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels.Data;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
|
||||
@@ -17,6 +19,7 @@ namespace Wino.Core.ViewModels;
|
||||
public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewModel
|
||||
{
|
||||
public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; set; } = [];
|
||||
public IEnumerable<IAccountProviderDetailViewModel> StartupAccounts => Accounts.Where(IsStartupEligible);
|
||||
|
||||
public bool IsPurchasePanelVisible => !HasUnlimitedAccountProduct;
|
||||
public bool IsAccountCreationAlmostOnLimit => Accounts != null && Accounts.Count == FREE_ACCOUNT_COUNT - 1;
|
||||
@@ -130,10 +133,21 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM
|
||||
private void AccountsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(HasAccountsDefined));
|
||||
OnPropertyChanged(nameof(StartupAccounts));
|
||||
}
|
||||
|
||||
private static string GetAccountDetailsTitle(MailAccount account)
|
||||
=> !string.IsNullOrWhiteSpace(account?.Address)
|
||||
? string.Format(Translator.SettingsAccountDetails_NavigationTitle, account.Address)
|
||||
: account?.Name ?? Translator.AccountDetailsPage_Title;
|
||||
|
||||
private static bool IsStartupEligible(IAccountProviderDetailViewModel account)
|
||||
{
|
||||
return account switch
|
||||
{
|
||||
AccountProviderDetailViewModel accountViewModel => accountViewModel.Account.IsMailAccessGranted,
|
||||
MergedAccountProviderDetailViewModel mergedAccountViewModel => mergedAccountViewModel.HoldingAccounts.Any(a => a.Account.IsMailAccessGranted),
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -9,6 +10,8 @@ public partial class AccountProviderDetailViewModel : ObservableObject, IAccount
|
||||
{
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CapabilitySummary))]
|
||||
[NotifyPropertyChangedFor(nameof(DescriptionText))]
|
||||
private MailAccount account;
|
||||
|
||||
public IProviderDetail ProviderDetail { get; set; }
|
||||
@@ -20,6 +23,10 @@ public partial class AccountProviderDetailViewModel : ObservableObject, IAccount
|
||||
public int Order => Account.Order;
|
||||
|
||||
public string StartupEntityAddresses => Account.Address;
|
||||
public string CapabilitySummary => BuildCapabilitySummary(Account);
|
||||
public string DescriptionText => string.IsNullOrWhiteSpace(Account.Address)
|
||||
? CapabilitySummary
|
||||
: $"{CapabilitySummary} | {Account.Address}";
|
||||
|
||||
public int HoldingAccountCount => 1;
|
||||
|
||||
@@ -30,4 +37,15 @@ public partial class AccountProviderDetailViewModel : ObservableObject, IAccount
|
||||
ProviderDetail = providerDetail;
|
||||
Account = account;
|
||||
}
|
||||
|
||||
private static string BuildCapabilitySummary(MailAccount account)
|
||||
{
|
||||
if (account?.IsMailAccessGranted == true && account.IsCalendarAccessGranted)
|
||||
return Translator.AccountCapability_MailAndCalendar;
|
||||
|
||||
if (account?.IsMailAccessGranted == true)
|
||||
return Translator.AccountCapability_MailOnly;
|
||||
|
||||
return Translator.AccountCapability_CalendarOnly;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Misc;
|
||||
using Wino.Core.Services;
|
||||
using Wino.Core.ViewModels.Data;
|
||||
@@ -96,8 +97,14 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
[ObservableProperty]
|
||||
private bool isTaskbarBadgeEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AccountCapabilityOption SelectedCapabilityOption { get; set; }
|
||||
|
||||
public bool IsFocusedInboxSupportedForAccount => Account != null && Account.Preferences.IsFocusedInboxEnabled != null;
|
||||
public bool IsImapServer => ServerInformation != null;
|
||||
public bool HasMailAccess => Account?.IsMailAccessGranted == true;
|
||||
public bool HasCalendarAccess => Account?.IsCalendarAccessGranted == true;
|
||||
public bool IsOAuthCapabilityEditable => Account?.ProviderType is MailProviderType.Outlook or MailProviderType.Gmail;
|
||||
public string ProviderIconPath => Account?.SpecialImapProvider != SpecialImapProvider.None
|
||||
? $"ms-appx:///Assets/Providers/{Account.SpecialImapProvider}.png"
|
||||
: $"ms-appx:///Assets/Providers/{Account?.ProviderType}.png";
|
||||
@@ -130,6 +137,13 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.None, Translator.ImapConnectionSecurity_None)
|
||||
];
|
||||
|
||||
public List<AccountCapabilityOption> CapabilityOptions { get; } =
|
||||
[
|
||||
new(true, false, Translator.AccountCapability_MailOnly),
|
||||
new(false, true, Translator.AccountCapability_CalendarOnly),
|
||||
new(true, true, Translator.AccountCapability_MailAndCalendar)
|
||||
];
|
||||
|
||||
|
||||
public AccountDetailsPageViewModel(IMailDialogService dialogService,
|
||||
IAccountService accountService,
|
||||
@@ -262,6 +276,7 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
AccountName = Account.Name;
|
||||
SenderName = Account.SenderName;
|
||||
ServerInformation = Account.ServerInformation;
|
||||
SelectedCapabilityOption = ResolveCapabilityOption(Account.IsMailAccessGranted, Account.IsCalendarAccessGranted);
|
||||
|
||||
IsFocusedInboxEnabled = Account.Preferences.IsFocusedInboxEnabled.GetValueOrDefault();
|
||||
AreNotificationsEnabled = Account.Preferences.IsNotificationsEnabled;
|
||||
@@ -288,7 +303,11 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
SelectedOutgoingServerConnectionSecurityIndex = AvailableConnectionSecurities.FindIndex(a => a.ImapConnectionSecurity == ServerInformation.OutgoingServerSocketOption);
|
||||
}
|
||||
|
||||
SelectedTabIndex = _statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar ? 2 : 1;
|
||||
SelectedTabIndex = _statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar && HasCalendarAccess
|
||||
? 2
|
||||
: HasMailAccess
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
var folderStructures = (await _folderService.GetFolderStructureForAccountAsync(Account.Id, true)).Folders;
|
||||
|
||||
@@ -382,11 +401,15 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
|
||||
partial void OnAccountChanged(MailAccount value)
|
||||
{
|
||||
SelectedCapabilityOption = ResolveCapabilityOption(value?.IsMailAccessGranted == true, value?.IsCalendarAccessGranted == true);
|
||||
OnPropertyChanged(nameof(IsFocusedInboxSupportedForAccount));
|
||||
OnPropertyChanged(nameof(ProviderIconPath));
|
||||
OnPropertyChanged(nameof(Address));
|
||||
OnPropertyChanged(nameof(IsInitialSynchronizationSummaryVisible));
|
||||
OnPropertyChanged(nameof(InitialSynchronizationSummary));
|
||||
OnPropertyChanged(nameof(HasMailAccess));
|
||||
OnPropertyChanged(nameof(HasCalendarAccess));
|
||||
OnPropertyChanged(nameof(IsOAuthCapabilityEditable));
|
||||
}
|
||||
|
||||
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
@@ -417,6 +440,21 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
Account.Preferences.IsTaskbarBadgeEnabled = IsTaskbarBadgeEnabled;
|
||||
await _accountService.UpdateAccountAsync(Account);
|
||||
break;
|
||||
case nameof(SelectedCapabilityOption) when IsOAuthCapabilityEditable && SelectedCapabilityOption != null:
|
||||
if (Account.IsMailAccessGranted == SelectedCapabilityOption.IsMailAccessGranted &&
|
||||
Account.IsCalendarAccessGranted == SelectedCapabilityOption.IsCalendarAccessGranted)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
await UpdateOAuthCapabilityAsync(SelectedCapabilityOption);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ExecuteUIThread(() => SelectedCapabilityOption = ResolveCapabilityOption(Account.IsMailAccessGranted, Account.IsCalendarAccessGranted));
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, InfoBarMessageType.Error);
|
||||
}
|
||||
break;
|
||||
case nameof(SelectedPrimaryCalendar) when SelectedPrimaryCalendar != null:
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
@@ -427,6 +465,111 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private AccountCapabilityOption ResolveCapabilityOption(bool isMailAccessGranted, bool isCalendarAccessGranted)
|
||||
=> CapabilityOptions.First(option =>
|
||||
option.IsMailAccessGranted == isMailAccessGranted &&
|
||||
option.IsCalendarAccessGranted == isCalendarAccessGranted);
|
||||
|
||||
private async Task UpdateOAuthCapabilityAsync(AccountCapabilityOption selectedOption)
|
||||
{
|
||||
var previousMailAccess = Account.IsMailAccessGranted;
|
||||
var previousCalendarAccess = Account.IsCalendarAccessGranted;
|
||||
var requiresReauthorization = (selectedOption.IsMailAccessGranted && !previousMailAccess) ||
|
||||
(selectedOption.IsCalendarAccessGranted && !previousCalendarAccess);
|
||||
|
||||
try
|
||||
{
|
||||
if (requiresReauthorization)
|
||||
{
|
||||
Account.IsMailAccessGranted = selectedOption.IsMailAccessGranted;
|
||||
Account.IsCalendarAccessGranted = selectedOption.IsCalendarAccessGranted;
|
||||
|
||||
await SynchronizationManager.Instance.HandleAuthorizationAsync(
|
||||
Account.ProviderType,
|
||||
Account,
|
||||
Account.ProviderType == MailProviderType.Gmail);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Account.IsMailAccessGranted = previousMailAccess;
|
||||
Account.IsCalendarAccessGranted = previousCalendarAccess;
|
||||
throw;
|
||||
}
|
||||
|
||||
Account.IsMailAccessGranted = selectedOption.IsMailAccessGranted;
|
||||
Account.IsCalendarAccessGranted = selectedOption.IsCalendarAccessGranted;
|
||||
|
||||
await _accountService.UpdateAccountAsync(Account);
|
||||
|
||||
if (selectedOption.IsMailAccessGranted && !previousMailAccess)
|
||||
{
|
||||
await SynchronizationManager.Instance.SynchronizeProfileAsync(Account.Id);
|
||||
await SynchronizationManager.Instance.SynchronizeMailAsync(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = MailSynchronizationType.FullFolders
|
||||
});
|
||||
|
||||
if (Account.ProviderType == MailProviderType.Outlook)
|
||||
{
|
||||
await SynchronizationManager.Instance.SynchronizeMailAsync(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = MailSynchronizationType.Categories
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Account.Address))
|
||||
{
|
||||
var aliases = await _accountService.GetAccountAliasesAsync(Account.Id);
|
||||
var hasRootAlias = aliases.Any(alias => alias.IsRootAlias);
|
||||
|
||||
if (!hasRootAlias)
|
||||
{
|
||||
await _accountService.CreateRootAliasAsync(Account.Id, Account.Address);
|
||||
}
|
||||
}
|
||||
|
||||
await SynchronizationManager.Instance.SynchronizeMailAsync(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = MailSynchronizationType.Alias
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedOption.IsCalendarAccessGranted && !previousCalendarAccess)
|
||||
{
|
||||
await SynchronizationManager.Instance.SynchronizeCalendarAsync(new CalendarSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
});
|
||||
}
|
||||
|
||||
var refreshedAccount = await _accountService.GetAccountAsync(Account.Id);
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
Account = refreshedAccount;
|
||||
AccountName = refreshedAccount.Name;
|
||||
SenderName = refreshedAccount.SenderName;
|
||||
EnsureSelectedTabForCapabilities();
|
||||
});
|
||||
}
|
||||
|
||||
private void EnsureSelectedTabForCapabilities()
|
||||
{
|
||||
if (SelectedTabIndex == 1 && !HasMailAccess)
|
||||
{
|
||||
SelectedTabIndex = HasCalendarAccess ? 2 : 0;
|
||||
}
|
||||
else if (SelectedTabIndex == 2 && !HasCalendarAccess)
|
||||
{
|
||||
SelectedTabIndex = HasMailAccess ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AccountCalendarShowAsOption
|
||||
@@ -441,6 +584,20 @@ public sealed class AccountCalendarShowAsOption
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AccountCapabilityOption
|
||||
{
|
||||
public bool IsMailAccessGranted { get; }
|
||||
public bool IsCalendarAccessGranted { get; }
|
||||
public string DisplayText { get; }
|
||||
|
||||
public AccountCapabilityOption(bool isMailAccessGranted, bool isCalendarAccessGranted, string displayText)
|
||||
{
|
||||
IsMailAccessGranted = isMailAccessGranted;
|
||||
IsCalendarAccessGranted = isCalendarAccessGranted;
|
||||
DisplayText = displayText;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class AccountCalendarSettingsItemViewModel : ObservableObject
|
||||
{
|
||||
public AccountCalendar Calendar { get; }
|
||||
|
||||
@@ -71,6 +71,8 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
private void BuildSteps()
|
||||
{
|
||||
Steps.Clear();
|
||||
var shouldSetupMail = WizardContext.IsMailAccessEnabled;
|
||||
var shouldSetupCalendar = WizardContext.IsCalendarAccessEnabled;
|
||||
|
||||
if (WizardContext.IsOAuthProvider)
|
||||
{
|
||||
@@ -78,31 +80,47 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
{
|
||||
Title = string.Format(Translator.AccountSetup_Step_Authenticating, WizardContext.SelectedProvider.Name)
|
||||
});
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_FetchingProfile });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SavingAccount });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingFolders });
|
||||
if (WizardContext.SelectedProvider.Type == MailProviderType.Outlook)
|
||||
if (shouldSetupMail)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingCategories });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_FetchingProfile });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingFolders });
|
||||
|
||||
if (WizardContext.SelectedProvider.Type == MailProviderType.Outlook)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingCategories });
|
||||
}
|
||||
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingAliases });
|
||||
}
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_FetchingCalendarMetadata });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingAliases });
|
||||
|
||||
if (shouldSetupCalendar)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_FetchingCalendarMetadata });
|
||||
}
|
||||
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_Finalizing });
|
||||
}
|
||||
else if (WizardContext.IsSpecialImapProvider)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_TestingMailAuth });
|
||||
if (shouldSetupMail)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_TestingMailAuth });
|
||||
}
|
||||
|
||||
if (WizardContext.CalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
||||
if (shouldSetupCalendar && WizardContext.CalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_DiscoveringCalDav });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_TestingCalendarAuth });
|
||||
}
|
||||
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SavingAccount });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingFolders });
|
||||
if (shouldSetupMail)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingFolders });
|
||||
}
|
||||
|
||||
if (WizardContext.CalendarSupportMode != ImapCalendarSupportMode.Disabled)
|
||||
if (shouldSetupCalendar && WizardContext.CalendarSupportMode != ImapCalendarSupportMode.Disabled)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_FetchingCalendarMetadata });
|
||||
}
|
||||
@@ -112,7 +130,10 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
else // Generic IMAP
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SavingAccount });
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingFolders });
|
||||
if (shouldSetupMail)
|
||||
{
|
||||
Steps.Add(new AccountSetupStepModel { Title = Translator.AccountSetup_Step_SyncingFolders });
|
||||
}
|
||||
|
||||
var setupResult = WizardContext.ImapCalDavSetupResult;
|
||||
if (setupResult?.IsCalendarAccessGranted == true &&
|
||||
@@ -186,7 +207,8 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
AccountColorHex = WizardContext.AccountColorHex,
|
||||
CreatedAt = accountCreatedAt,
|
||||
InitialSynchronizationRange = WizardContext.SelectedInitialSynchronizationRange,
|
||||
IsCalendarAccessGranted = true
|
||||
IsMailAccessGranted = WizardContext.IsMailAccessEnabled,
|
||||
IsCalendarAccessGranted = WizardContext.IsCalendarAccessEnabled
|
||||
};
|
||||
|
||||
if (WizardContext.IsOAuthProvider)
|
||||
@@ -208,50 +230,53 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
_dbWritten = true;
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Profile
|
||||
SetStepInProgress(Translator.AccountSetup_Step_FetchingProfile);
|
||||
var profileResult = await SynchronizationManager.Instance.SynchronizeProfileAsync(_createdAccount.Id);
|
||||
if (profileResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
||||
|
||||
if (profileResult.ProfileInformation != null)
|
||||
if (_createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
_createdAccount.SenderName = profileResult.ProfileInformation.SenderName;
|
||||
_createdAccount.Base64ProfilePictureData = profileResult.ProfileInformation.Base64ProfilePictureData;
|
||||
// Step: Profile
|
||||
SetStepInProgress(Translator.AccountSetup_Step_FetchingProfile);
|
||||
var profileResult = await SynchronizationManager.Instance.SynchronizeProfileAsync(_createdAccount.Id);
|
||||
if (profileResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
||||
|
||||
if (!string.IsNullOrEmpty(profileResult.ProfileInformation.AccountAddress))
|
||||
if (profileResult.ProfileInformation != null)
|
||||
{
|
||||
if (await _accountService.AccountAddressExistsAsync(profileResult.ProfileInformation.AccountAddress, _createdAccount.Id))
|
||||
throw new InvalidOperationException(Translator.DialogMessage_AccountAddressExistsMessage);
|
||||
_createdAccount.SenderName = profileResult.ProfileInformation.SenderName;
|
||||
_createdAccount.Base64ProfilePictureData = profileResult.ProfileInformation.Base64ProfilePictureData;
|
||||
|
||||
_createdAccount.Address = profileResult.ProfileInformation.AccountAddress;
|
||||
if (!string.IsNullOrEmpty(profileResult.ProfileInformation.AccountAddress))
|
||||
{
|
||||
if (await _accountService.AccountAddressExistsAsync(profileResult.ProfileInformation.AccountAddress, _createdAccount.Id))
|
||||
throw new InvalidOperationException(Translator.DialogMessage_AccountAddressExistsMessage);
|
||||
|
||||
_createdAccount.Address = profileResult.ProfileInformation.AccountAddress;
|
||||
}
|
||||
|
||||
await _accountService.UpdateProfileInformationAsync(_createdAccount.Id, profileResult.ProfileInformation);
|
||||
}
|
||||
|
||||
await _accountService.UpdateProfileInformationAsync(_createdAccount.Id, profileResult.ProfileInformation);
|
||||
}
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Folders
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingFolders);
|
||||
var folderResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(_createdAccount.Id);
|
||||
if (folderResult == null || folderResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Categories
|
||||
if (_createdAccount.IsCategorySyncSupported)
|
||||
{
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingCategories);
|
||||
var categoryResult = await SynchronizationManager.Instance.SynchronizeCategoriesAsync(_createdAccount.Id);
|
||||
if (categoryResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeCategories);
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Folders
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingFolders);
|
||||
var folderResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(_createdAccount.Id);
|
||||
if (folderResult == null || folderResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Categories
|
||||
if (_createdAccount.IsCategorySyncSupported)
|
||||
{
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingCategories);
|
||||
var categoryResult = await SynchronizationManager.Instance.SynchronizeCategoriesAsync(_createdAccount.Id);
|
||||
if (categoryResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeCategories);
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
// Step: Calendar metadata
|
||||
SetStepInProgress(Translator.AccountSetup_Step_FetchingCalendarMetadata);
|
||||
if (_createdAccount.IsCalendarAccessGranted)
|
||||
{
|
||||
SetStepInProgress(Translator.AccountSetup_Step_FetchingCalendarMetadata);
|
||||
var calResult = await SynchronizationManager.Instance.SynchronizeCalendarAsync(new CalendarSynchronizationOptions
|
||||
{
|
||||
AccountId = _createdAccount.Id,
|
||||
@@ -259,22 +284,25 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
});
|
||||
if (calResult == null || calResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeCalendarMetadata);
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Aliases
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingAliases);
|
||||
if (_createdAccount.IsAliasSyncSupported)
|
||||
if (_createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
var aliasResult = await SynchronizationManager.Instance.SynchronizeAliasesAsync(_createdAccount.Id);
|
||||
if (aliasResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingAliases);
|
||||
if (_createdAccount.IsAliasSyncSupported)
|
||||
{
|
||||
var aliasResult = await SynchronizationManager.Instance.SynchronizeAliasesAsync(_createdAccount.Id);
|
||||
if (aliasResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _accountService.CreateRootAliasAsync(_createdAccount.Id, _createdAccount.Address);
|
||||
}
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _accountService.CreateRootAliasAsync(_createdAccount.Id, _createdAccount.Address);
|
||||
}
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
else if (WizardContext.IsSpecialImapProvider)
|
||||
{
|
||||
@@ -288,13 +316,17 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
|
||||
_createdAccount.Address = WizardContext.EmailAddress;
|
||||
_createdAccount.SenderName = WizardContext.DisplayName;
|
||||
_createdAccount.IsMailAccessGranted = dialogResult.IsMailAccessGranted;
|
||||
_createdAccount.IsCalendarAccessGranted = customServerInformation.CalendarSupportMode != ImapCalendarSupportMode.Disabled;
|
||||
_createdAccount.ServerInformation = customServerInformation;
|
||||
|
||||
// Step: Test IMAP
|
||||
SetStepInProgress(Translator.AccountSetup_Step_TestingMailAuth);
|
||||
await ValidateImapConnectivityAsync(customServerInformation);
|
||||
SetCurrentStepSucceeded();
|
||||
if (_createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
// Step: Test IMAP
|
||||
SetStepInProgress(Translator.AccountSetup_Step_TestingMailAuth);
|
||||
await ValidateImapConnectivityAsync(customServerInformation);
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
|
||||
// Step: CalDAV discovery and testing (if applicable)
|
||||
if (customServerInformation.CalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
||||
@@ -313,12 +345,15 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
_dbWritten = true;
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Folders
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingFolders);
|
||||
var folderResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(_createdAccount.Id);
|
||||
if (folderResult == null || folderResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||
SetCurrentStepSucceeded();
|
||||
if (_createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
// Step: Folders
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingFolders);
|
||||
var folderResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(_createdAccount.Id);
|
||||
if (folderResult == null || folderResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
|
||||
// Step: Calendar metadata (if not disabled)
|
||||
if (_createdAccount.IsCalendarAccessGranted)
|
||||
@@ -334,8 +369,10 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
|
||||
// Aliases for IMAP
|
||||
await _accountService.CreateRootAliasAsync(_createdAccount.Id, _createdAccount.Address);
|
||||
if (_createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
await _accountService.CreateRootAliasAsync(_createdAccount.Id, _createdAccount.Address);
|
||||
}
|
||||
}
|
||||
else // Generic IMAP
|
||||
{
|
||||
@@ -350,6 +387,7 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
|
||||
_createdAccount.Address = setupResult.EmailAddress;
|
||||
_createdAccount.SenderName = setupResult.DisplayName;
|
||||
_createdAccount.IsMailAccessGranted = setupResult.IsMailAccessGranted;
|
||||
_createdAccount.IsCalendarAccessGranted = setupResult.IsCalendarAccessGranted;
|
||||
_createdAccount.ServerInformation = customServerInformation;
|
||||
|
||||
@@ -359,12 +397,15 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
_dbWritten = true;
|
||||
SetCurrentStepSucceeded();
|
||||
|
||||
// Step: Folders
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingFolders);
|
||||
var folderResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(_createdAccount.Id);
|
||||
if (folderResult == null || folderResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||
SetCurrentStepSucceeded();
|
||||
if (_createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
// Step: Folders
|
||||
SetStepInProgress(Translator.AccountSetup_Step_SyncingFolders);
|
||||
var folderResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(_createdAccount.Id);
|
||||
if (folderResult == null || folderResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
|
||||
// Step: CalDAV (if applicable)
|
||||
if (setupResult.IsCalendarAccessGranted &&
|
||||
@@ -392,8 +433,10 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
||||
SetCurrentStepSucceeded();
|
||||
}
|
||||
|
||||
// Aliases for IMAP
|
||||
await _accountService.CreateRootAliasAsync(_createdAccount.Id, _createdAccount.Address);
|
||||
if (_createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
await _accountService.CreateRootAliasAsync(_createdAccount.Id, _createdAccount.Address);
|
||||
}
|
||||
}
|
||||
|
||||
// Step: Finalizing
|
||||
|
||||
@@ -61,6 +61,7 @@ public sealed class ImapCalDavSetupResult
|
||||
{
|
||||
public string DisplayName { get; init; }
|
||||
public string EmailAddress { get; init; }
|
||||
public bool IsMailAccessGranted { get; init; }
|
||||
public bool IsCalendarAccessGranted { get; init; }
|
||||
public CustomServerInformation ServerInformation { get; init; }
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ public partial class WelcomeWizardContext : ObservableObject
|
||||
[ObservableProperty]
|
||||
public partial InitialSynchronizationRange SelectedInitialSynchronizationRange { get; set; } = InitialSynchronizationRange.SixMonths;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsMailAccessEnabled { get; set; } = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsCalendarAccessEnabled { get; set; } = true;
|
||||
|
||||
// Special IMAP fields (iCloud/Yahoo)
|
||||
[ObservableProperty]
|
||||
public partial string DisplayName { get; set; }
|
||||
@@ -66,7 +72,9 @@ public partial class WelcomeWizardContext : ObservableObject
|
||||
AccountName,
|
||||
BuildSpecialImapProviderDetails(),
|
||||
AccountColorHex,
|
||||
SelectedInitialSynchronizationRange);
|
||||
SelectedInitialSynchronizationRange,
|
||||
IsMailAccessEnabled,
|
||||
IsCalendarAccessEnabled);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
@@ -75,6 +83,8 @@ public partial class WelcomeWizardContext : ObservableObject
|
||||
AccountName = null;
|
||||
AccountColorHex = null;
|
||||
SelectedInitialSynchronizationRange = InitialSynchronizationRange.SixMonths;
|
||||
IsMailAccessEnabled = true;
|
||||
IsCalendarAccessEnabled = true;
|
||||
DisplayName = null;
|
||||
EmailAddress = null;
|
||||
AppSpecificPassword = null;
|
||||
|
||||
@@ -1,9 +1,50 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels;
|
||||
|
||||
namespace Wino.Mail.ViewModels;
|
||||
|
||||
public partial class IdlePageViewModel : CoreBaseViewModel
|
||||
{
|
||||
public IdlePageViewModel(IMailDialogService dialogService) { }
|
||||
public const string MailEmptyStateParameter = "mail-empty-state";
|
||||
|
||||
private readonly INavigationService _navigationService;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsMailEmptyStateVisible { get; set; }
|
||||
|
||||
public string MailEmptyStateTitle => Translator.MailEmptyState_Title;
|
||||
public string MailEmptyStateMessage => Translator.MailEmptyState_Message;
|
||||
public string AddAccountText => Translator.MailEmptyState_AddAccount;
|
||||
public string ManageAccountsText => Translator.MailEmptyState_ManageAccounts;
|
||||
|
||||
public IdlePageViewModel(IMailDialogService dialogService, INavigationService navigationService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
IsMailEmptyStateVisible = string.Equals(parameters as string, MailEmptyStateParameter, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddAccount()
|
||||
=> _navigationService.Navigate(
|
||||
WinoPage.SettingsPage,
|
||||
ProviderSelectionNavigationContext.CreateForSettingsAddAccount(),
|
||||
NavigationReferenceFrame.ShellFrame);
|
||||
|
||||
[RelayCommand]
|
||||
private void ManageAccounts()
|
||||
=> _navigationService.Navigate(
|
||||
WinoPage.SettingsPage,
|
||||
WinoPage.ManageAccountsPage,
|
||||
NavigationReferenceFrame.ShellFrame);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
[ObservableProperty]
|
||||
private string password = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsMailSettingsVisible))]
|
||||
[NotifyPropertyChangedFor(nameof(IsMailPasswordInputVisible))]
|
||||
[NotifyPropertyChangedFor(nameof(IsMailActionsVisible))]
|
||||
private bool isMailSupportEnabled = true;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsCalendarModeSelectionVisible))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCalDavSettingsVisible))]
|
||||
@@ -141,6 +147,9 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
public bool HasProviderHint => !string.IsNullOrWhiteSpace(ProviderHint);
|
||||
public bool IsBasicSetupSelected => SelectedSetupTabIndex == 0;
|
||||
public bool IsAdvancedSetupSelected => SelectedSetupTabIndex == 1;
|
||||
public bool IsMailSettingsVisible => IsMailSupportEnabled;
|
||||
public bool IsMailPasswordInputVisible => IsMailSupportEnabled;
|
||||
public bool IsMailActionsVisible => IsMailSupportEnabled;
|
||||
public bool IsCalendarModeSelectionVisible => IsCalendarSupportEnabled;
|
||||
public bool IsCalDavSettingsVisible => IsCalendarSupportEnabled && SelectedCalendarSupportMode == ImapCalendarSupportMode.CalDav;
|
||||
public bool IsLocalCalendarModeSelected => IsCalendarSupportEnabled && SelectedCalendarSupportMode == ImapCalendarSupportMode.LocalOnly;
|
||||
@@ -152,6 +161,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
public string EmailAddressHeaderText => Translator.IMAPSetupDialog_MailAddress;
|
||||
public string EmailAddressPlaceholderText => Translator.IMAPSetupDialog_MailAddressPlaceholder;
|
||||
public string PasswordHeaderText => Translator.IMAPSetupDialog_Password;
|
||||
public string EnableMailSupportText => Translator.ProviderSelection_UseForMail;
|
||||
public string EnableCalendarSupportText => Translator.ImapCalDavSettingsPage_EnableCalendarSupport;
|
||||
public string AutoDiscoverButtonText => Translator.ImapCalDavSettingsPage_AutoDiscoverButton;
|
||||
public string BasicTabText => Translator.ImapCalDavSettingsPage_BasicTab;
|
||||
@@ -342,6 +352,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateCapabilitySelection();
|
||||
await EnsureImapSettingsPreparedAsync().ConfigureAwait(false);
|
||||
var serverInformation = BuildServerInformation();
|
||||
|
||||
@@ -401,6 +412,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateCapabilitySelection();
|
||||
await EnsureImapSettingsPreparedAsync();
|
||||
|
||||
var serverInformation = BuildServerInformation();
|
||||
@@ -416,8 +428,15 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
if (!await ValidateAccountUniquenessAsync(excludedAccountId))
|
||||
return;
|
||||
|
||||
await ValidateImapConnectivityAsync(serverInformation);
|
||||
IsImapValidationSucceeded = true;
|
||||
if (IsMailSupportEnabled)
|
||||
{
|
||||
await ValidateImapConnectivityAsync(serverInformation);
|
||||
IsImapValidationSucceeded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsImapValidationSucceeded = false;
|
||||
}
|
||||
|
||||
if (serverInformation.CalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
||||
{
|
||||
@@ -485,6 +504,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
{
|
||||
DisplayName = DisplayName.Trim(),
|
||||
EmailAddress = EmailAddress.Trim(),
|
||||
IsMailAccessGranted = IsMailSupportEnabled,
|
||||
IsCalendarAccessGranted = serverInformation.CalendarSupportMode != ImapCalendarSupportMode.Disabled,
|
||||
ServerInformation = serverInformation
|
||||
};
|
||||
@@ -511,6 +531,14 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnIsMailSupportEnabledChanged(bool value)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
IsImapValidationSucceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedCalendarSupportModeChanged(ImapCalendarSupportMode value)
|
||||
{
|
||||
if (value == ImapCalendarSupportMode.LocalOnly && !_localOnlyInfoShown)
|
||||
@@ -562,6 +590,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
ApplyProviderHint(_editingSpecialImapProvider);
|
||||
|
||||
ApplyServerInformation(account.ServerInformation);
|
||||
IsMailSupportEnabled = account.IsMailAccessGranted;
|
||||
|
||||
if (account.ServerInformation != null)
|
||||
{
|
||||
@@ -588,8 +617,10 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
if (!string.IsNullOrWhiteSpace(accountCreationDialogResult?.SpecialImapProviderDetails?.SenderName))
|
||||
DisplayName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName;
|
||||
|
||||
IsCalendarSupportEnabled = true;
|
||||
SelectedCalendarSupportMode = ImapCalendarSupportMode.CalDav;
|
||||
IsMailSupportEnabled = accountCreationDialogResult?.IsMailAccessGranted != false;
|
||||
IsCalendarSupportEnabled = accountCreationDialogResult?.IsCalendarAccessGranted == true;
|
||||
SelectedCalendarSupportMode = accountCreationDialogResult?.SpecialImapProviderDetails?.CalendarSupportMode
|
||||
?? (IsCalendarSupportEnabled ? ImapCalendarSupportMode.CalDav : ImapCalendarSupportMode.Disabled);
|
||||
|
||||
var specialProvider = accountCreationDialogResult?.SpecialImapProviderDetails?.SpecialImapProvider ?? SpecialImapProvider.None;
|
||||
_editingSpecialImapProvider = specialProvider;
|
||||
@@ -737,6 +768,9 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
|
||||
private async Task EnsureImapSettingsPreparedAsync()
|
||||
{
|
||||
if (!IsMailSupportEnabled)
|
||||
return;
|
||||
|
||||
if (HasCompleteImapSettings())
|
||||
return;
|
||||
|
||||
@@ -847,6 +881,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
{
|
||||
DisplayName = DisplayName.Trim(),
|
||||
EmailAddress = EmailAddress.Trim(),
|
||||
IsMailAccessGranted = IsMailSupportEnabled,
|
||||
IsCalendarAccessGranted = serverInformation.CalendarSupportMode != ImapCalendarSupportMode.Disabled,
|
||||
ServerInformation = serverInformation
|
||||
});
|
||||
@@ -863,7 +898,9 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
|
||||
private async Task<bool> ValidateAccountUniquenessAsync(Guid? excludedAccountId)
|
||||
{
|
||||
var accountName = (_pageMode == ImapCalDavSettingsPageMode.Create || _pageMode == ImapCalDavSettingsPageMode.Wizard)
|
||||
var accountName = (_pageMode == ImapCalDavSettingsPageMode.Create
|
||||
|| _pageMode == ImapCalDavSettingsPageMode.Wizard
|
||||
|| _pageMode == ImapCalDavSettingsPageMode.AddAccount)
|
||||
? _accountCreationContext?.AccountName
|
||||
: null;
|
||||
|
||||
@@ -889,6 +926,15 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ValidateCapabilitySelection(bool isMailEnabled, bool isCalendarEnabled)
|
||||
{
|
||||
if (!isMailEnabled && !isCalendarEnabled)
|
||||
throw new InvalidOperationException(Translator.ProviderSelection_CapabilityValidationMessage);
|
||||
}
|
||||
|
||||
private void ValidateCapabilitySelection()
|
||||
=> ValidateCapabilitySelection(IsMailSupportEnabled, IsCalendarSupportEnabled);
|
||||
|
||||
private async Task SaveEditFlowAsync(CustomServerInformation serverInformation)
|
||||
{
|
||||
var account = await _accountService.GetAccountAsync(_editingAccountId);
|
||||
@@ -897,6 +943,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
|
||||
account.SenderName = DisplayName.Trim();
|
||||
account.Address = EmailAddress.Trim();
|
||||
account.IsMailAccessGranted = IsMailSupportEnabled;
|
||||
account.IsCalendarAccessGranted = serverInformation.CalendarSupportMode != ImapCalendarSupportMode.Disabled;
|
||||
|
||||
serverInformation.Id = account.ServerInformation?.Id ?? Guid.NewGuid();
|
||||
@@ -908,11 +955,14 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
await _accountService.UpdateAccountCustomServerInformationAsync(serverInformation);
|
||||
await _accountService.UpdateAccountAsync(account);
|
||||
|
||||
Messenger.Send(new NewMailSynchronizationRequested(new MailSynchronizationOptions
|
||||
if (account.IsMailAccessGranted)
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = MailSynchronizationType.FullFolders
|
||||
}));
|
||||
Messenger.Send(new NewMailSynchronizationRequested(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Type = MailSynchronizationType.FullFolders
|
||||
}));
|
||||
}
|
||||
|
||||
if (account.IsCalendarAccessGranted)
|
||||
{
|
||||
@@ -1007,6 +1057,9 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
|
||||
private void ValidateImapSettings(CustomServerInformation serverInformation)
|
||||
{
|
||||
if (!IsMailSupportEnabled)
|
||||
return;
|
||||
|
||||
ValidateIdentitySettings();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(serverInformation.IncomingServer))
|
||||
@@ -1075,7 +1128,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
|
||||
private bool TryApplyKnownProviderSettingsIfNeeded(bool requireCompleteImapSettings, bool requireCompleteCalDavSettings)
|
||||
{
|
||||
var needsImapSettings = requireCompleteImapSettings && !HasCompleteImapSettings();
|
||||
var needsImapSettings = IsMailSupportEnabled && requireCompleteImapSettings && !HasCompleteImapSettings();
|
||||
var needsCalDavSettings = requireCompleteCalDavSettings
|
||||
&& IsCalendarSupportEnabled
|
||||
&& SelectedCalendarSupportMode == ImapCalendarSupportMode.CalDav
|
||||
@@ -1114,6 +1167,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
SenderName = DisplayName.Trim(),
|
||||
ProviderType = MailProviderType.IMAP4,
|
||||
SpecialImapProvider = _editingSpecialImapProvider,
|
||||
IsMailAccessGranted = IsMailSupportEnabled,
|
||||
IsCalendarAccessGranted = mode != ImapCalendarSupportMode.Disabled
|
||||
},
|
||||
new AccountCreationDialogResult(
|
||||
@@ -1121,7 +1175,9 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
DisplayName.Trim(),
|
||||
providerDetails,
|
||||
string.Empty,
|
||||
_wizardContext.SelectedInitialSynchronizationRange));
|
||||
_wizardContext.SelectedInitialSynchronizationRange,
|
||||
IsMailSupportEnabled,
|
||||
mode != ImapCalendarSupportMode.Disabled));
|
||||
|
||||
if (serverInformation == null)
|
||||
return false;
|
||||
@@ -1158,7 +1214,8 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
&& !string.IsNullOrWhiteSpace(CalDavPassword));
|
||||
|
||||
private bool HasCompleteImapSettings()
|
||||
=> !string.IsNullOrWhiteSpace(IncomingServer)
|
||||
=> !IsMailSupportEnabled
|
||||
|| (!string.IsNullOrWhiteSpace(IncomingServer)
|
||||
&& !string.IsNullOrWhiteSpace(IncomingServerPort)
|
||||
&& !string.IsNullOrWhiteSpace(IncomingServerUsername)
|
||||
&& !string.IsNullOrWhiteSpace(IncomingServerPassword)
|
||||
@@ -1167,7 +1224,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
||||
&& !string.IsNullOrWhiteSpace(OutgoingServerUsername)
|
||||
&& !string.IsNullOrWhiteSpace(OutgoingServerPassword)
|
||||
&& IsValidPort(IncomingServerPort)
|
||||
&& IsValidPort(OutgoingServerPort);
|
||||
&& IsValidPort(OutgoingServerPort));
|
||||
|
||||
private int FindAuthenticationMethodIndex(ImapAuthenticationMethod method)
|
||||
{
|
||||
|
||||
@@ -46,6 +46,8 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
IRecipient<AccountRemovedMessage>,
|
||||
IRecipient<AccountUpdatedMessage>
|
||||
{
|
||||
private const string MailEmptyStateNavigationParameter = IdlePageViewModel.MailEmptyStateParameter;
|
||||
|
||||
#region Menu Items
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -188,7 +190,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
// First clear all account menu items.
|
||||
MenuItems.RemoveRange(MenuItems.Where(a => a is IAccountMenuItem));
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
var accounts = await GetMailEnabledAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
List<Guid> initializedAccountIds = new();
|
||||
|
||||
@@ -373,7 +375,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
private async Task ForceAllAccountSynchronizationsAsync()
|
||||
{
|
||||
// Run Inbox synchronization for all accounts on startup.
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
var accounts = await GetMailEnabledAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
@@ -442,6 +444,9 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
|
||||
private async Task ProcessLaunchDefaultAsync()
|
||||
{
|
||||
if (await NavigateToMailEmptyStateIfNeededAsync().ConfigureAwait(false))
|
||||
return;
|
||||
|
||||
if (PreferencesService.StartupEntityId == null)
|
||||
{
|
||||
NavigateToWelcomeWizard();
|
||||
@@ -468,8 +473,16 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to the welcome wizard if startup entity is not found.
|
||||
NavigateToWelcomeWizard();
|
||||
var firstMailAccountMenuItem = MenuItems.FirstOrDefault(a => a is IAccountMenuItem) as IAccountMenuItem;
|
||||
if (firstMailAccountMenuItem != null)
|
||||
{
|
||||
firstMailAccountMenuItem.Expand();
|
||||
await ChangeLoadedAccountAsync(firstMailAccountMenuItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigateToWelcomeWizard();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -947,6 +960,11 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
operationAccount = selectedFolderMenuItem.ParentAccount;
|
||||
}
|
||||
|
||||
if (operationAccount?.IsMailAccessGranted == false)
|
||||
{
|
||||
operationAccount = null;
|
||||
}
|
||||
|
||||
// We couldn't find any account so far.
|
||||
// If there is only 1 account to use, use it. If not,
|
||||
// send a message for flyout so user can pick from it.
|
||||
@@ -956,7 +974,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
// No selected account.
|
||||
// List all accounts and let user pick one.
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
var accounts = await GetMailEnabledAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
if (!accounts.Any())
|
||||
{
|
||||
@@ -1081,7 +1099,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
|
||||
public async void Receive(MailtoProtocolMessageRequested message)
|
||||
{
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
var accounts = await GetMailEnabledAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
MailAccount targetAccount = null;
|
||||
|
||||
@@ -1114,7 +1132,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
if (shareRequest?.Files == null || shareRequest.Files.Count == 0)
|
||||
return;
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
var accounts = await GetMailEnabledAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
if (!accounts.Any())
|
||||
return;
|
||||
@@ -1188,7 +1206,10 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
else
|
||||
{
|
||||
await ExecuteUIThread(() => SelectedMenuItem = null);
|
||||
NavigateToWelcomeWizard();
|
||||
if (!await NavigateToMailEmptyStateIfNeededAsync().ConfigureAwait(false))
|
||||
{
|
||||
NavigateToWelcomeWizard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1199,6 +1220,37 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
NavigationReferenceFrame.ShellFrame,
|
||||
NavigationTransitionType.None);
|
||||
|
||||
private Task<List<MailAccount>> GetMailEnabledAccountsAsync()
|
||||
=> GetAccountsByCapabilityAsync(requireMail: true);
|
||||
|
||||
private async Task<List<MailAccount>> GetAccountsByCapabilityAsync(bool requireMail = false, bool requireCalendar = false)
|
||||
{
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
|
||||
return accounts
|
||||
.Where(account => (!requireMail || account.IsMailAccessGranted) &&
|
||||
(!requireCalendar || account.IsCalendarAccessGranted))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task<bool> NavigateToMailEmptyStateIfNeededAsync()
|
||||
{
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
if (!accounts.Any() || accounts.Any(account => account.IsMailAccessGranted))
|
||||
return false;
|
||||
|
||||
latestSelectedAccountMenuItem = null;
|
||||
await ExecuteUIThread(() => SelectedMenuItem = null);
|
||||
|
||||
NavigationService.Navigate(
|
||||
WinoPage.IdlePage,
|
||||
MailEmptyStateNavigationParameter,
|
||||
NavigationReferenceFrame.InnerShellFrame,
|
||||
NavigationTransitionType.None);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAccountCurrentlyLoaded(Guid accountId)
|
||||
{
|
||||
return latestSelectedAccountMenuItem?.HoldingAccounts?.Any(a => a.Id == accountId) == true;
|
||||
@@ -1385,7 +1437,9 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
public async void Receive(AccountRemovedMessage message)
|
||||
{
|
||||
var remainingAccounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
if (!remainingAccounts.Any())
|
||||
var remainingMailAccounts = remainingAccounts.Where(account => account.IsMailAccessGranted).ToList();
|
||||
|
||||
if (!remainingMailAccounts.Any())
|
||||
{
|
||||
latestSelectedAccountMenuItem = null;
|
||||
await ExecuteUIThread(() =>
|
||||
@@ -1394,6 +1448,12 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
MenuItems?.Clear();
|
||||
MenuItems?.Add(CreateMailMenuItem);
|
||||
});
|
||||
|
||||
if (remainingAccounts.Any())
|
||||
{
|
||||
await NavigateToMailEmptyStateIfNeededAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1413,22 +1473,34 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
|
||||
await RecreateMenuItemsAsync();
|
||||
|
||||
if (!createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
await NavigateToMailEmptyStateIfNeededAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!MenuItems.TryGetAccountMenuItem(createdAccount.Id, out IAccountMenuItem createdMenuItem))
|
||||
{
|
||||
Log.Warning("Created account {AccountId} could not be found in menu items after refresh.", createdAccount.Id);
|
||||
|
||||
if (!createdAccount.IsMailAccessGranted)
|
||||
return;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await ChangeLoadedAccountAsync(createdMenuItem);
|
||||
|
||||
// Each created account should start a new synchronization automatically.
|
||||
var options = new MailSynchronizationOptions()
|
||||
if (createdAccount.IsMailAccessGranted)
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = MailSynchronizationType.FullFolders,
|
||||
};
|
||||
// Each created account should start a new synchronization automatically.
|
||||
var options = new MailSynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = MailSynchronizationType.FullFolders,
|
||||
};
|
||||
|
||||
Messenger.Send(new NewMailSynchronizationRequested(options));
|
||||
Messenger.Send(new NewMailSynchronizationRequested(options));
|
||||
}
|
||||
|
||||
if (createdAccount.IsCalendarAccessGranted)
|
||||
{
|
||||
@@ -1455,6 +1527,13 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
{
|
||||
var updatedAccount = message.Account;
|
||||
|
||||
if (!updatedAccount.IsMailAccessGranted || !MenuItems.TryGetAccountMenuItem(updatedAccount.Id, out _))
|
||||
{
|
||||
await RecreateMenuItemsAsync();
|
||||
await RestoreSelectedAccountAfterMenuRefreshAsync(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
if (MenuItems.TryGetAccountMenuItem(updatedAccount.Id, out IAccountMenuItem foundAccountMenuItem))
|
||||
|
||||
@@ -10,9 +10,7 @@ using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Requests.Category;
|
||||
using Wino.Core.Services;
|
||||
|
||||
@@ -53,11 +51,11 @@ public partial class MailCategoryManagementPageViewModel : MailBaseViewModel
|
||||
if (parameters is not Guid accountId)
|
||||
return;
|
||||
|
||||
Account = await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
|
||||
Account = await _accountService.GetAccountAsync(accountId);
|
||||
|
||||
if (Account != null)
|
||||
{
|
||||
await LoadCategoriesAsync().ConfigureAwait(false);
|
||||
await LoadCategoriesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,13 @@ using Wino.Messaging.Client.Navigation;
|
||||
|
||||
namespace Wino.Mail.ViewModels;
|
||||
|
||||
public enum ProviderSelectionWizardStep
|
||||
{
|
||||
Provider = 0,
|
||||
Identity = 1,
|
||||
Capabilities = 2
|
||||
}
|
||||
|
||||
public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
||||
{
|
||||
private readonly IAccountService _accountService;
|
||||
@@ -45,16 +52,50 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsInitialSynchronizationWarningVisible))]
|
||||
[NotifyPropertyChangedFor(nameof(IsMailSynchronizationRangeVisible))]
|
||||
public partial InitialSynchronizationRangeOption SelectedInitialSynchronizationRange { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string AccountName { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool CanProceed { get; set; }
|
||||
[NotifyPropertyChangedFor(nameof(IsMailSynchronizationRangeVisible))]
|
||||
public partial bool IsMailAccessEnabled { get; set; } = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsCalendarAccessEnabled { get; set; } = true;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CurrentStepNumber))]
|
||||
[NotifyPropertyChangedFor(nameof(StepProgressValue))]
|
||||
[NotifyPropertyChangedFor(nameof(StepProgressText))]
|
||||
[NotifyPropertyChangedFor(nameof(IsProviderStepVisible))]
|
||||
[NotifyPropertyChangedFor(nameof(IsIdentityStepVisible))]
|
||||
[NotifyPropertyChangedFor(nameof(IsCapabilityStepVisible))]
|
||||
[NotifyPropertyChangedFor(nameof(CanGoBack))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ContinueCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(GoBackCommand))]
|
||||
public partial ProviderSelectionWizardStep CurrentStep { get; set; } = ProviderSelectionWizardStep.Provider;
|
||||
|
||||
public bool IsColorSelected => SelectedColor != null;
|
||||
public bool IsInitialSynchronizationWarningVisible => SelectedInitialSynchronizationRange?.IsEverything == true;
|
||||
public bool IsInitialSynchronizationWarningVisible => IsMailSynchronizationRangeVisible && SelectedInitialSynchronizationRange?.IsEverything == true;
|
||||
public bool IsMailSynchronizationRangeVisible => IsMailAccessEnabled;
|
||||
public int CurrentStepNumber => (int)CurrentStep + 1;
|
||||
public double StepProgressValue => CurrentStepNumber;
|
||||
public string StepProgressText => string.Format(Translator.ProviderSelection_StepProgress, CurrentStepNumber);
|
||||
public bool IsProviderStepVisible => CurrentStep == ProviderSelectionWizardStep.Provider;
|
||||
public bool IsIdentityStepVisible => CurrentStep == ProviderSelectionWizardStep.Identity;
|
||||
public bool IsCapabilityStepVisible => CurrentStep == ProviderSelectionWizardStep.Capabilities;
|
||||
public bool CanGoBack => CurrentStep != ProviderSelectionWizardStep.Provider;
|
||||
public string SelectedProviderName => SelectedProvider?.Name ?? string.Empty;
|
||||
public string SelectedProviderDescription => SelectedProvider?.Description ?? string.Empty;
|
||||
public string SelectedProviderImage => SelectedProvider?.ProviderImage ?? string.Empty;
|
||||
public string SelectedProviderCapabilityDescription => GetSelectedProviderCapabilityDescription();
|
||||
public bool IsCapabilitySelectionMissing => !IsMailAccessEnabled && !IsCalendarAccessEnabled;
|
||||
public bool IsCalendarOnlyServerHintVisible =>
|
||||
SelectedProvider?.Type == MailProviderType.IMAP4 &&
|
||||
!IsMailAccessEnabled &&
|
||||
IsCalendarAccessEnabled;
|
||||
|
||||
public ProviderSelectionPageViewModel(
|
||||
IAccountService accountService,
|
||||
@@ -101,48 +142,110 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
||||
p.Type == WizardContext.SelectedProvider.Type &&
|
||||
p.SpecialImapProvider == WizardContext.SelectedProvider.SpecialImapProvider);
|
||||
AccountName = WizardContext.AccountName;
|
||||
IsMailAccessEnabled = WizardContext.IsMailAccessEnabled;
|
||||
IsCalendarAccessEnabled = WizardContext.IsCalendarAccessEnabled;
|
||||
|
||||
if (WizardContext.AccountColorHex != null)
|
||||
SelectedColor = AvailableColors.FirstOrDefault(c => c.Hex == WizardContext.AccountColorHex);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsMailAccessEnabled = true;
|
||||
IsCalendarAccessEnabled = true;
|
||||
}
|
||||
|
||||
Validate();
|
||||
CurrentStep = mode == NavigationMode.Back && SelectedProvider != null
|
||||
? ProviderSelectionWizardStep.Capabilities
|
||||
: ProviderSelectionWizardStep.Provider;
|
||||
}
|
||||
|
||||
partial void OnSelectedProviderChanged(IProviderDetail value)
|
||||
{
|
||||
Validate();
|
||||
OnPropertyChanged(nameof(SelectedProviderName));
|
||||
OnPropertyChanged(nameof(SelectedProviderDescription));
|
||||
OnPropertyChanged(nameof(SelectedProviderImage));
|
||||
OnPropertyChanged(nameof(SelectedProviderCapabilityDescription));
|
||||
OnPropertyChanged(nameof(IsCapabilitySelectionMissing));
|
||||
OnPropertyChanged(nameof(IsCalendarOnlyServerHintVisible));
|
||||
ContinueCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
partial void OnAccountNameChanged(string value) => Validate();
|
||||
partial void OnAccountNameChanged(string value) => ContinueCommand.NotifyCanExecuteChanged();
|
||||
|
||||
partial void OnIsMailAccessEnabledChanged(bool value)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsCapabilitySelectionMissing));
|
||||
OnPropertyChanged(nameof(IsCalendarOnlyServerHintVisible));
|
||||
ContinueCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
partial void OnIsCalendarAccessEnabledChanged(bool value)
|
||||
{
|
||||
OnPropertyChanged(nameof(IsCapabilitySelectionMissing));
|
||||
OnPropertyChanged(nameof(IsCalendarOnlyServerHintVisible));
|
||||
ContinueCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ClearColor() => SelectedColor = null;
|
||||
|
||||
private void Validate()
|
||||
private bool CanContinue()
|
||||
{
|
||||
CanProceed = SelectedProvider != null && !string.IsNullOrWhiteSpace(AccountName);
|
||||
return CurrentStep switch
|
||||
{
|
||||
ProviderSelectionWizardStep.Provider => SelectedProvider != null,
|
||||
ProviderSelectionWizardStep.Identity => !string.IsNullOrWhiteSpace(AccountName),
|
||||
ProviderSelectionWizardStep.Capabilities => IsMailAccessEnabled || IsCalendarAccessEnabled,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ProceedAsync()
|
||||
[RelayCommand(CanExecute = nameof(CanGoBack))]
|
||||
private void GoBack()
|
||||
{
|
||||
if (!CanProceed) return;
|
||||
|
||||
if (await _accountService.AccountNameExistsAsync(AccountName))
|
||||
{
|
||||
await _dialogService.ShowMessageAsync(
|
||||
Translator.DialogMessage_AccountNameExistsMessage,
|
||||
Translator.DialogMessage_AccountExistsTitle,
|
||||
WinoCustomMessageDialogIcon.Warning);
|
||||
if (!CanGoBack)
|
||||
return;
|
||||
|
||||
CurrentStep--;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanContinue))]
|
||||
private async Task ContinueAsync()
|
||||
{
|
||||
switch (CurrentStep)
|
||||
{
|
||||
case ProviderSelectionWizardStep.Provider:
|
||||
CurrentStep = ProviderSelectionWizardStep.Identity;
|
||||
return;
|
||||
case ProviderSelectionWizardStep.Identity:
|
||||
if (await _accountService.AccountNameExistsAsync(AccountName?.Trim()))
|
||||
{
|
||||
await _dialogService.ShowMessageAsync(
|
||||
Translator.DialogMessage_AccountNameExistsMessage,
|
||||
Translator.DialogMessage_AccountExistsTitle,
|
||||
WinoCustomMessageDialogIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentStep = ProviderSelectionWizardStep.Capabilities;
|
||||
return;
|
||||
case ProviderSelectionWizardStep.Capabilities:
|
||||
await CompleteWizardAsync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CompleteWizardAsync()
|
||||
{
|
||||
if (!CanContinue())
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist to wizard context
|
||||
WizardContext.SelectedProvider = SelectedProvider;
|
||||
WizardContext.AccountName = AccountName?.Trim();
|
||||
WizardContext.AccountColorHex = SelectedColor?.Hex ?? string.Empty;
|
||||
WizardContext.SelectedInitialSynchronizationRange = SelectedInitialSynchronizationRange?.Range ?? InitialSynchronizationRange.SixMonths;
|
||||
WizardContext.IsMailAccessEnabled = IsMailAccessEnabled;
|
||||
WizardContext.IsCalendarAccessEnabled = IsCalendarAccessEnabled;
|
||||
|
||||
if (WizardContext.IsGenericImap)
|
||||
{
|
||||
@@ -172,4 +275,23 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
||||
WinoPage.AccountSetupProgressPage));
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedProviderChanging(IProviderDetail value)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private string GetSelectedProviderCapabilityDescription()
|
||||
{
|
||||
if (SelectedProvider == null)
|
||||
return string.Empty;
|
||||
|
||||
if (SelectedProvider.Type is MailProviderType.Outlook or MailProviderType.Gmail)
|
||||
return Translator.ProviderSelection_CapabilityProviderDescription_OAuth;
|
||||
|
||||
if (SelectedProvider.SpecialImapProvider is SpecialImapProvider.iCloud or SpecialImapProvider.Yahoo)
|
||||
return Translator.ProviderSelection_CapabilityProviderDescription_SpecialImap;
|
||||
|
||||
return Translator.ProviderSelection_CapabilityProviderDescription_CustomServer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,11 +38,15 @@ public partial class SpecialImapCredentialsPageViewModel : MailBaseViewModel
|
||||
public partial string AppSpecificPassword { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(RequiresAppSpecificPassword))]
|
||||
public partial int SelectedCalendarModeIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool CanProceed { get; set; }
|
||||
|
||||
public bool IsCalendarModeSelectionVisible => WizardContext.IsCalendarAccessEnabled;
|
||||
public bool RequiresAppSpecificPassword => WizardContext.IsMailAccessEnabled || SelectedCalendarModeIndex == 1;
|
||||
|
||||
public string AppPasswordHelpUrl
|
||||
{
|
||||
get
|
||||
@@ -86,8 +90,15 @@ public partial class SpecialImapCredentialsPageViewModel : MailBaseViewModel
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (!WizardContext.IsCalendarAccessEnabled)
|
||||
{
|
||||
SelectedCalendarModeIndex = 0;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(AppPasswordHelpUrl));
|
||||
OnPropertyChanged(nameof(CalendarModeCalDavDescription));
|
||||
OnPropertyChanged(nameof(IsCalendarModeSelectionVisible));
|
||||
OnPropertyChanged(nameof(RequiresAppSpecificPassword));
|
||||
|
||||
Validate();
|
||||
}
|
||||
@@ -95,13 +106,18 @@ public partial class SpecialImapCredentialsPageViewModel : MailBaseViewModel
|
||||
partial void OnDisplayNameChanged(string value) => Validate();
|
||||
partial void OnEmailAddressChanged(string value) => Validate();
|
||||
partial void OnAppSpecificPasswordChanged(string value) => Validate();
|
||||
partial void OnSelectedCalendarModeIndexChanged(int value)
|
||||
{
|
||||
OnPropertyChanged(nameof(RequiresAppSpecificPassword));
|
||||
Validate();
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
CanProceed = !string.IsNullOrWhiteSpace(DisplayName)
|
||||
&& !string.IsNullOrWhiteSpace(EmailAddress)
|
||||
&& MailAccountAddressValidator.IsValid(EmailAddress)
|
||||
&& !string.IsNullOrWhiteSpace(AppSpecificPassword);
|
||||
&& (!RequiresAppSpecificPassword || !string.IsNullOrWhiteSpace(AppSpecificPassword));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -121,12 +137,14 @@ public partial class SpecialImapCredentialsPageViewModel : MailBaseViewModel
|
||||
WizardContext.DisplayName = DisplayName?.Trim();
|
||||
WizardContext.EmailAddress = EmailAddress?.Trim();
|
||||
WizardContext.AppSpecificPassword = AppSpecificPassword?.Trim();
|
||||
WizardContext.CalendarSupportMode = SelectedCalendarModeIndex switch
|
||||
{
|
||||
1 => ImapCalendarSupportMode.CalDav,
|
||||
2 => ImapCalendarSupportMode.LocalOnly,
|
||||
_ => ImapCalendarSupportMode.Disabled
|
||||
};
|
||||
WizardContext.CalendarSupportMode = WizardContext.IsCalendarAccessEnabled
|
||||
? SelectedCalendarModeIndex switch
|
||||
{
|
||||
1 => ImapCalendarSupportMode.CalDav,
|
||||
2 => ImapCalendarSupportMode.LocalOnly,
|
||||
_ => ImapCalendarSupportMode.Disabled
|
||||
}
|
||||
: ImapCalendarSupportMode.Disabled;
|
||||
|
||||
Messenger.Send(new BreadcrumbNavigationRequested(
|
||||
Translator.WelcomeWizard_Step3Title,
|
||||
|
||||
@@ -158,7 +158,9 @@ public sealed partial class NewAccountDialog : ContentDialog
|
||||
AccountNameTextbox.Text.Trim(),
|
||||
details,
|
||||
SelectedColor?.Hex ?? string.Empty,
|
||||
initialSynchronizationRange);
|
||||
initialSynchronizationRange,
|
||||
true,
|
||||
calendarSupportMode != ImapCalendarSupportMode.Disabled);
|
||||
Hide();
|
||||
|
||||
return;
|
||||
@@ -185,7 +187,9 @@ public sealed partial class NewAccountDialog : ContentDialog
|
||||
AccountNameTextbox.Text.Trim(),
|
||||
null,
|
||||
SelectedColor?.Hex ?? string.Empty,
|
||||
initialSynchronizationRange);
|
||||
initialSynchronizationRange,
|
||||
true,
|
||||
true);
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,26 @@ public static class XamlHelpers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Microsoft.UI.Xaml.Media.Imaging.BitmapImage? StringToBitmapImage(string? imagePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(imagePath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var uri = imagePath.StartsWith("/")
|
||||
? new Uri($"ms-appx://{imagePath}")
|
||||
: new Uri(imagePath, UriKind.Absolute);
|
||||
|
||||
return new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static InfoBarSeverity InfoBarSeverityConverter(InfoBarMessageType messageType)
|
||||
{
|
||||
return messageType switch
|
||||
|
||||
@@ -105,6 +105,9 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
||||
{
|
||||
lock (_calendarStateLock)
|
||||
{
|
||||
if (!GroupedAccountCalendarViewModel.SupportsCalendar(groupedAccountCalendar.Account))
|
||||
return;
|
||||
|
||||
groupedAccountCalendar.CalendarSelectionStateChanged += SingleCalendarSelectionStateChanged;
|
||||
groupedAccountCalendar.CollectiveSelectionStateChanged += SingleGroupCalendarCollectiveStateChanged;
|
||||
try
|
||||
@@ -180,6 +183,9 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
||||
{
|
||||
lock (_calendarStateLock)
|
||||
{
|
||||
if (!GroupedAccountCalendarViewModel.SupportsCalendar(accountCalendar.Account))
|
||||
return;
|
||||
|
||||
// Find the group that this calendar belongs to.
|
||||
var group = _internalGroupedAccountCalendars.FirstOrDefault(g => g.Account.Id == accountCalendar.Account.Id);
|
||||
|
||||
@@ -396,6 +402,16 @@ public partial class AccountCalendarStateService : ObservableRecipient,
|
||||
lock (_calendarStateLock)
|
||||
{
|
||||
groupedAccount = _internalGroupedAccountCalendars.FirstOrDefault(a => a.Account.Id == updatedAccount.Id);
|
||||
|
||||
if (!GroupedAccountCalendarViewModel.SupportsCalendar(updatedAccount))
|
||||
{
|
||||
if (groupedAccount != null)
|
||||
{
|
||||
RemoveGroupedAccountCalendar(groupedAccount);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
groupedAccount?.UpdateAccount(updatedAccount);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Services;
|
||||
@@ -6,35 +7,73 @@ public class MailAuthenticatorConfiguration : IAuthenticatorConfig
|
||||
{
|
||||
public string OutlookAuthenticatorClientId => "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||
|
||||
public string[] OutlookScope =>
|
||||
[
|
||||
"email",
|
||||
"mail.readwrite",
|
||||
"offline_access",
|
||||
"mail.send",
|
||||
"Mail.Send.Shared",
|
||||
"Mail.ReadWrite.Shared",
|
||||
"User.Read",
|
||||
"Calendars.ReadBasic",
|
||||
"Calendars.ReadWrite",
|
||||
"Calendars.ReadWrite.Shared",
|
||||
"Calendars.Read",
|
||||
"Calendars.Read.Shared",
|
||||
];
|
||||
|
||||
public string GmailAuthenticatorClientId => "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
||||
|
||||
public string[] GmailScope =>
|
||||
[
|
||||
"https://mail.google.com/",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/gmail.labels",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/calendar",
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.settings.readonly",
|
||||
"https://www.googleapis.com/auth/drive.file",
|
||||
];
|
||||
|
||||
public string GmailTokenStoreIdentifier => "WinoMailGmailTokenStore";
|
||||
|
||||
public string[] GetOutlookScope(bool isMailAccessGranted, bool isCalendarAccessGranted)
|
||||
{
|
||||
var scopes = new List<string>
|
||||
{
|
||||
"email",
|
||||
"offline_access",
|
||||
"User.Read"
|
||||
};
|
||||
|
||||
if (isMailAccessGranted)
|
||||
{
|
||||
scopes.AddRange(
|
||||
[
|
||||
"mail.readwrite",
|
||||
"mail.send",
|
||||
"Mail.Send.Shared",
|
||||
"Mail.ReadWrite.Shared"
|
||||
]);
|
||||
}
|
||||
|
||||
if (isCalendarAccessGranted)
|
||||
{
|
||||
scopes.AddRange(
|
||||
[
|
||||
"Calendars.ReadBasic",
|
||||
"Calendars.ReadWrite",
|
||||
"Calendars.ReadWrite.Shared",
|
||||
"Calendars.Read",
|
||||
"Calendars.Read.Shared"
|
||||
]);
|
||||
}
|
||||
|
||||
return [.. scopes];
|
||||
}
|
||||
|
||||
public string[] GetGmailScope(bool isMailAccessGranted, bool isCalendarAccessGranted)
|
||||
{
|
||||
var scopes = new List<string>
|
||||
{
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/userinfo.email"
|
||||
};
|
||||
|
||||
if (isMailAccessGranted)
|
||||
{
|
||||
scopes.AddRange(
|
||||
[
|
||||
"https://mail.google.com/",
|
||||
"https://www.googleapis.com/auth/gmail.labels"
|
||||
]);
|
||||
}
|
||||
|
||||
if (isCalendarAccessGranted)
|
||||
{
|
||||
scopes.AddRange(
|
||||
[
|
||||
"https://www.googleapis.com/auth/calendar",
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.settings.readonly",
|
||||
"https://www.googleapis.com/auth/drive.file"
|
||||
]);
|
||||
}
|
||||
|
||||
return [.. scopes];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Wino.Mail.ViewModels;
|
||||
using Wino.Mail.WinUI;
|
||||
|
||||
namespace Wino.Mail.WinUI.Views.Abstract;
|
||||
|
||||
|
||||
@@ -90,12 +90,12 @@
|
||||
<SymbolIcon Symbol="ContactInfo" />
|
||||
</controls:SegmentedItem.Icon>
|
||||
</controls:SegmentedItem>
|
||||
<controls:SegmentedItem Content="{x:Bind domain:Translator.AccountDetailsPage_TabMail, Mode=OneTime}">
|
||||
<controls:SegmentedItem Content="{x:Bind domain:Translator.AccountDetailsPage_TabMail, Mode=OneTime}" Visibility="{x:Bind ViewModel.HasMailAccess, Mode=OneWay}">
|
||||
<controls:SegmentedItem.Icon>
|
||||
<SymbolIcon Symbol="Mail" />
|
||||
</controls:SegmentedItem.Icon>
|
||||
</controls:SegmentedItem>
|
||||
<controls:SegmentedItem Content="{x:Bind domain:Translator.AccountDetailsPage_TabCalendar, Mode=OneTime}">
|
||||
<controls:SegmentedItem Content="{x:Bind domain:Translator.AccountDetailsPage_TabCalendar, Mode=OneTime}" Visibility="{x:Bind ViewModel.HasCalendarAccess, Mode=OneWay}">
|
||||
<controls:SegmentedItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</controls:SegmentedItem.Icon>
|
||||
@@ -117,7 +117,10 @@
|
||||
Text="{x:Bind ViewModel.AccountName, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsEditAccountDetails_Description}" Header="{x:Bind domain:Translator.AccountSettingsDialog_AccountName}">
|
||||
<controls:SettingsCard
|
||||
Description="{x:Bind domain:Translator.SettingsEditAccountDetails_Description}"
|
||||
Header="{x:Bind domain:Translator.AccountSettingsDialog_AccountName}"
|
||||
Visibility="{x:Bind ViewModel.HasMailAccess, Mode=OneWay}">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<SymbolIcon Symbol="Mail" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
@@ -127,7 +130,9 @@
|
||||
Text="{x:Bind ViewModel.SenderName, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard Header="{x:Bind domain:Translator.IMAPSetupDialog_MailAddress}">
|
||||
<controls:SettingsCard
|
||||
Header="{x:Bind domain:Translator.IMAPSetupDialog_MailAddress}"
|
||||
Visibility="{x:Bind ViewModel.HasMailAccess, Mode=OneWay}">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<SymbolIcon Symbol="Mail" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
@@ -137,6 +142,25 @@
|
||||
Text="{x:Bind ViewModel.Address, Mode=OneWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard
|
||||
Description="{x:Bind domain:Translator.AccountDetailsPage_CapabilityDescription, Mode=OneTime}"
|
||||
Header="{x:Bind domain:Translator.AccountDetailsPage_CapabilityTitle, Mode=OneTime}"
|
||||
Visibility="{x:Bind ViewModel.IsOAuthCapabilityEditable, Mode=OneWay}">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
<ComboBox
|
||||
MinWidth="220"
|
||||
ItemsSource="{x:Bind ViewModel.CapabilityOptions, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedCapabilityOption, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="mailViewModels:AccountCapabilityOption">
|
||||
<TextBlock Text="{x:Bind DisplayText}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
@@ -212,6 +236,7 @@
|
||||
<Grid
|
||||
x:Name="MailSettingsPanel"
|
||||
Grid.Row="2"
|
||||
x:Load="{x:Bind ViewModel.HasMailAccess, Mode=OneWay}"
|
||||
Visibility="Collapsed">
|
||||
<StackPanel Spacing="4">
|
||||
<StackPanel.ChildrenTransitions>
|
||||
@@ -403,6 +428,7 @@
|
||||
<Grid
|
||||
x:Name="CalendarSettingsPanel"
|
||||
Grid.Row="2"
|
||||
x:Load="{x:Bind ViewModel.HasCalendarAccess, Mode=OneWay}"
|
||||
Visibility="Collapsed">
|
||||
<StackPanel MaxWidth="900" Spacing="12">
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
Margin="0,2,0,0"
|
||||
Click="RootAccountTemplate_Click"
|
||||
CommandParameter="{x:Bind}"
|
||||
Description="{x:Bind Account.Address}"
|
||||
Description="{x:Bind DescriptionText}"
|
||||
Header="{x:Bind Account.Name}"
|
||||
IsClickEnabled="True">
|
||||
<winuiControls:SettingsCard.HeaderIcon>
|
||||
@@ -199,6 +199,7 @@
|
||||
x:Name="AccountsListView"
|
||||
Grid.Row="1"
|
||||
x:Load="{x:Bind ViewModel.HasAccountsDefined, Mode=OneWay}"
|
||||
CanReorderItems="True"
|
||||
ItemContainerStyle="{StaticResource StretchedItemContainerStyle}"
|
||||
ItemTemplateSelector="{StaticResource AccountProviderViewModelTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.Accounts, Mode=OneWay}"
|
||||
@@ -208,7 +209,7 @@
|
||||
<winuiControls:SettingsCard Description="{x:Bind domain:Translator.SettingsStartupItem_Description}" Header="{x:Bind domain:Translator.SettingsStartupItem_Title}">
|
||||
<ComboBox
|
||||
MinWidth="150"
|
||||
ItemsSource="{x:Bind ViewModel.Accounts, Mode=OneTime}"
|
||||
ItemsSource="{x:Bind ViewModel.StartupAccounts, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.StartupAccount, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="interfaces:IAccountProviderDetailViewModel">
|
||||
@@ -220,16 +221,10 @@
|
||||
<SymbolIcon Symbol="Account" />
|
||||
</winuiControls:SettingsCard.HeaderIcon>
|
||||
</winuiControls:SettingsCard>
|
||||
<winuiControls:SettingsCard
|
||||
Description="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionDescription}"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionTitle}">
|
||||
<winuiControls:SettingsCard Description="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionDescription}" Header="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionTitle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.ImportLocalDataCommand}"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataImportAction}" />
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.ExportLocalDataCommand}"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataExportAction}" />
|
||||
<Button Command="{x:Bind ViewModel.ImportLocalDataCommand}" Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataImportAction}" />
|
||||
<Button Command="{x:Bind ViewModel.ExportLocalDataCommand}" Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataExportAction}" />
|
||||
</StackPanel>
|
||||
<winuiControls:SettingsCard.HeaderIcon>
|
||||
<SymbolIcon Symbol="Sync" />
|
||||
|
||||
@@ -76,12 +76,18 @@
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Header="{x:Bind ViewModel.PasswordHeaderText, Mode=OneWay}"
|
||||
Password="{x:Bind ViewModel.Password, Mode=TwoWay}" />
|
||||
Password="{x:Bind ViewModel.Password, Mode=TwoWay}"
|
||||
Visibility="{x:Bind ViewModel.IsMailPasswordInputVisible, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
<CheckBox
|
||||
Content="{x:Bind ViewModel.EnableCalendarSupportText, Mode=OneWay}"
|
||||
IsChecked="{x:Bind ViewModel.IsCalendarSupportEnabled, Mode=TwoWay}" />
|
||||
<StackPanel Spacing="10">
|
||||
<CheckBox
|
||||
Content="{x:Bind ViewModel.EnableMailSupportText, Mode=OneWay}"
|
||||
IsChecked="{x:Bind ViewModel.IsMailSupportEnabled, Mode=TwoWay}" />
|
||||
<CheckBox
|
||||
Content="{x:Bind ViewModel.EnableCalendarSupportText, Mode=OneWay}"
|
||||
IsChecked="{x:Bind ViewModel.IsCalendarSupportEnabled, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock
|
||||
@@ -93,7 +99,8 @@
|
||||
HorizontalAlignment="Left"
|
||||
Command="{x:Bind ViewModel.AutoDiscoverSettingsCommand}"
|
||||
Content="{x:Bind ViewModel.AutoDiscoverButtonText, Mode=OneWay}"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
Style="{ThemeResource AccentButtonStyle}"
|
||||
Visibility="{x:Bind ViewModel.IsMailActionsVisible, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -103,7 +110,8 @@
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8">
|
||||
CornerRadius="8"
|
||||
Visibility="{x:Bind ViewModel.IsMailSettingsVisible, Mode=OneWay}">
|
||||
<StackPanel Spacing="16">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock
|
||||
@@ -176,7 +184,8 @@
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Bind ViewModel.TestImapConnectionCommand}"
|
||||
Content="{x:Bind ViewModel.TestImapButtonText, Mode=OneWay}" />
|
||||
Content="{x:Bind ViewModel.TestImapButtonText, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.IsMailActionsVisible, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -9,7 +9,39 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<!-- Empty page for disposing composer or renderer page. -->
|
||||
<Grid />
|
||||
<Grid>
|
||||
<StackPanel
|
||||
MaxWidth="440"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12"
|
||||
Visibility="{x:Bind ViewModel.IsMailEmptyStateVisible, Mode=OneWay}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.MailEmptyStateTitle, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.MailEmptyStateMessage, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.AddAccountCommand}"
|
||||
Content="{x:Bind ViewModel.AddAccountText, Mode=OneWay}"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.ManageAccountsCommand}"
|
||||
Content="{x:Bind ViewModel.ManageAccountsText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</abstract:IdlePageAbstract>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,6 +12,16 @@ public sealed partial class ProviderSelectionPage : ProviderSelectionPageAbstrac
|
||||
|
||||
private void ProviderSelectionChanged(ItemsView sender, ItemsViewSelectionChangedEventArgs args)
|
||||
{
|
||||
if (sender.SelectedItem == null) return;
|
||||
|
||||
ViewModel.SelectedProvider = sender.SelectedItem as Wino.Core.Domain.Interfaces.IProviderDetail;
|
||||
}
|
||||
|
||||
private void AccountColorGridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.AddedItems.Count > 0)
|
||||
{
|
||||
AccountColorFlyout.Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,24 +50,33 @@
|
||||
<PasswordBox
|
||||
x:Name="AppPasswordBox"
|
||||
Header="{x:Bind domain:Translator.ProviderSelection_AppPasswordHeader}"
|
||||
PasswordChanged="AppPasswordChanged" />
|
||||
PasswordChanged="AppPasswordChanged"
|
||||
Visibility="{x:Bind ViewModel.RequiresAppSpecificPassword, Mode=OneWay}" />
|
||||
|
||||
<HyperlinkButton
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Bind ViewModel.OpenAppPasswordHelpCommand}"
|
||||
Content="{x:Bind domain:Translator.ProviderSelection_AppPasswordHelp}" />
|
||||
Content="{x:Bind domain:Translator.ProviderSelection_AppPasswordHelp}"
|
||||
Visibility="{x:Bind ViewModel.RequiresAppSpecificPassword, Mode=OneWay}" />
|
||||
|
||||
<!-- Divider -->
|
||||
<Rectangle Height="1" Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
Visibility="{x:Bind ViewModel.IsCalendarModeSelectionVisible, Mode=OneWay}" />
|
||||
|
||||
<!-- Calendar Mode -->
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.ProviderSelection_CalendarModeHeader}" />
|
||||
<TextBlock
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.ProviderSelection_CalendarModeHeader}"
|
||||
Visibility="{x:Bind ViewModel.IsCalendarModeSelectionVisible, Mode=OneWay}" />
|
||||
|
||||
<ListView
|
||||
x:Name="CalendarModeListView"
|
||||
IsItemClickEnabled="False"
|
||||
SelectionChanged="CalendarModeSelectionChanged"
|
||||
SelectionMode="Single">
|
||||
SelectionMode="Single"
|
||||
Visibility="{x:Bind ViewModel.IsCalendarModeSelectionVisible, Mode=OneWay}">
|
||||
<!-- Disabled -->
|
||||
<ListViewItem>
|
||||
<Grid Padding="12" ColumnSpacing="10">
|
||||
|
||||
@@ -371,8 +371,13 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
|
||||
{
|
||||
_ = DispatcherQueue.EnqueueAsync(async () =>
|
||||
{
|
||||
ViewModel.NavigationService.ChangeApplicationMode(WinoApplicationMode.Mail);
|
||||
await ViewModel.MailClient.HandleAccountCreatedAsync(message.Account);
|
||||
|
||||
var targetMode = !message.Account.IsMailAccessGranted && message.Account.IsCalendarAccessGranted
|
||||
? WinoApplicationMode.Calendar
|
||||
: WinoApplicationMode.Mail;
|
||||
|
||||
ViewModel.NavigationService.ChangeApplicationMode(targetMode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -211,9 +211,6 @@ public class AccountService : BaseDatabaseService, IAccountService
|
||||
|
||||
Guard.IsNotNull(token);
|
||||
|
||||
// Enable calendar access since new token includes calendar scopes
|
||||
account.IsCalendarAccessGranted = true;
|
||||
|
||||
await UpdateAccountAsync(account);
|
||||
}
|
||||
|
||||
@@ -270,6 +267,9 @@ public class AccountService : BaseDatabaseService, IAccountService
|
||||
|
||||
public async Task CreateRootAliasAsync(Guid accountId, string address)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address))
|
||||
return;
|
||||
|
||||
var rootAlias = new MailAccountAlias()
|
||||
{
|
||||
AccountId = accountId,
|
||||
|
||||
@@ -97,6 +97,13 @@ public class DatabaseService : IDatabaseService
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!accountColumns.Any(c => c.Name == nameof(MailAccount.IsMailAccessGranted)))
|
||||
{
|
||||
await Connection
|
||||
.ExecuteAsync($"ALTER TABLE {nameof(MailAccount)} ADD COLUMN {nameof(MailAccount.IsMailAccessGranted)} INTEGER NOT NULL DEFAULT 1")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var folderColumns = await Connection.GetTableInfoAsync(nameof(MailItemFolder)).ConfigureAwait(false);
|
||||
|
||||
if (!folderColumns.Any(c => c.Name == nameof(MailItemFolder.HighestKnownUid)))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -245,7 +246,11 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
||||
var serverInformation = CreateImportedServerInformation(mailbox, account.Id);
|
||||
|
||||
await _accountService.CreateAccountAsync(account, serverInformation).ConfigureAwait(false);
|
||||
await _accountService.CreateRootAliasAsync(account.Id, account.Address).ConfigureAwait(false);
|
||||
|
||||
if (account.IsMailAccessGranted)
|
||||
{
|
||||
await _accountService.CreateRootAliasAsync(account.Id, account.Address).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (account.ProviderType == MailProviderType.IMAP4)
|
||||
{
|
||||
@@ -289,7 +294,7 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
||||
? account.ServerInformation
|
||||
: null;
|
||||
|
||||
return new UserMailboxSyncItemDto
|
||||
var mailbox = new UserMailboxSyncItemDto
|
||||
{
|
||||
Address = account.Address ?? string.Empty,
|
||||
ProviderType = (int)account.ProviderType,
|
||||
@@ -316,6 +321,10 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
||||
ProxyServerPort = serverInformation?.ProxyServerPort,
|
||||
MaxConcurrentClients = serverInformation?.MaxConcurrentClients
|
||||
};
|
||||
|
||||
SetOptionalBooleanProperty(mailbox, "IsMailAccessGranted", account.IsMailAccessGranted);
|
||||
|
||||
return mailbox;
|
||||
}
|
||||
|
||||
private static MailAccount CreateImportedAccount(UserMailboxSyncItemDto mailbox)
|
||||
@@ -334,6 +343,7 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
||||
Base64ProfilePictureData = string.Empty,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
InitialSynchronizationRange = InitialSynchronizationRange.SixMonths,
|
||||
IsMailAccessGranted = GetOptionalBooleanProperty(mailbox, "IsMailAccessGranted", defaultValue: true),
|
||||
IsCalendarAccessGranted = mailbox.IsCalendarAccessGranted,
|
||||
SynchronizationDeltaIdentifier = string.Empty,
|
||||
CalendarSynchronizationDeltaIdentifier = string.Empty,
|
||||
@@ -410,6 +420,38 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
||||
private static string CreateMailboxKey(string? address, int providerType)
|
||||
=> $"{address?.Trim().ToLowerInvariant()}|{providerType}";
|
||||
|
||||
private static bool GetOptionalBooleanProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
|
||||
T instance,
|
||||
string propertyName,
|
||||
bool defaultValue)
|
||||
{
|
||||
if (instance == null)
|
||||
return defaultValue;
|
||||
|
||||
var property = typeof(T).GetProperty(propertyName);
|
||||
if (property?.PropertyType != typeof(bool) || !property.CanRead)
|
||||
return defaultValue;
|
||||
|
||||
return property.GetValue(instance) is bool boolValue
|
||||
? boolValue
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
private static void SetOptionalBooleanProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
|
||||
T instance,
|
||||
string propertyName,
|
||||
bool value)
|
||||
{
|
||||
if (instance == null)
|
||||
return;
|
||||
|
||||
var property = typeof(T).GetProperty(propertyName);
|
||||
if (property?.PropertyType == typeof(bool) && property.CanWrite)
|
||||
{
|
||||
property.SetValue(instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static string TrimUtf8Bom(string jsonContent)
|
||||
=> !string.IsNullOrEmpty(jsonContent) && jsonContent[0] == '\uFEFF'
|
||||
? jsonContent[1..]
|
||||
|
||||
Reference in New Issue
Block a user