Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bba3523ec6 | |||
| feff929333 | |||
| aa16609f89 | |||
| 4bea53a667 | |||
| b2ad4a1664 | |||
| dad3a51885 | |||
| 59ff0a1d7d | |||
| df19ab3196 | |||
| c622858d2d | |||
| 2e36772a4c |
@@ -178,6 +178,18 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
|||||||
await InitializeAccountCalendarsAsync();
|
await InitializeAccountCalendarsAsync();
|
||||||
ValidateConfiguredNewEventCalendar();
|
ValidateConfiguredNewEventCalendar();
|
||||||
|
|
||||||
|
if (activationContext?.Parameter is CalendarItemTarget calendarItemTarget)
|
||||||
|
{
|
||||||
|
NavigationService.Navigate(WinoPage.EventDetailsPage, calendarItemTarget);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activationContext?.Parameter is CalendarPageNavigationArgs calendarPageNavigationArgs)
|
||||||
|
{
|
||||||
|
NavigationService.Navigate(WinoPage.CalendarPage, calendarPageNavigationArgs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TodayClicked();
|
TodayClicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public static class Constants
|
|||||||
public const string ToastCalendarJoinOnlineAction = nameof(ToastCalendarJoinOnlineAction);
|
public const string ToastCalendarJoinOnlineAction = nameof(ToastCalendarJoinOnlineAction);
|
||||||
public const string ToastCalendarSnoozeAction = nameof(ToastCalendarSnoozeAction);
|
public const string ToastCalendarSnoozeAction = nameof(ToastCalendarSnoozeAction);
|
||||||
public const string ToastCalendarSnoozeDurationInputId = nameof(ToastCalendarSnoozeDurationInputId);
|
public const string ToastCalendarSnoozeDurationInputId = nameof(ToastCalendarSnoozeDurationInputId);
|
||||||
|
public const string ToastCalendarSnoozeDurationMinutesKey = nameof(ToastCalendarSnoozeDurationMinutesKey);
|
||||||
public const string ToastModeKey = nameof(ToastModeKey);
|
public const string ToastModeKey = nameof(ToastModeKey);
|
||||||
public const string ToastModeMail = nameof(ToastModeMail);
|
public const string ToastModeMail = nameof(ToastModeMail);
|
||||||
public const string ToastModeCalendar = nameof(ToastModeCalendar);
|
public const string ToastModeCalendar = nameof(ToastModeCalendar);
|
||||||
|
|||||||
@@ -112,6 +112,16 @@ public class MailAccount
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? LastFolderStructureSyncDate { get; set; }
|
public DateTime? LastFolderStructureSyncDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets when the account was created in Wino.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? CreatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the timespan used for the account's initial mail synchronization.
|
||||||
|
/// </summary>
|
||||||
|
public InitialSynchronizationRange InitialSynchronizationRange { get; set; } = InitialSynchronizationRange.SixMonths;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether the account can perform ProfileInformation sync type.
|
/// Gets whether the account can perform ProfileInformation sync type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
public enum InitialSynchronizationRange
|
||||||
|
{
|
||||||
|
SixMonths = 0,
|
||||||
|
ThreeMonths = 1,
|
||||||
|
NineMonths = 2,
|
||||||
|
OneYear = 3,
|
||||||
|
Everything = 4
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Extensions;
|
||||||
|
|
||||||
|
public static class InitialSynchronizationRangeExtensions
|
||||||
|
{
|
||||||
|
public static DateTime? ToCutoffDateUtc(this InitialSynchronizationRange range, DateTime utcNow)
|
||||||
|
{
|
||||||
|
var normalizedUtcNow = utcNow.Kind == DateTimeKind.Utc
|
||||||
|
? utcNow
|
||||||
|
: utcNow.ToUniversalTime();
|
||||||
|
|
||||||
|
return range switch
|
||||||
|
{
|
||||||
|
InitialSynchronizationRange.ThreeMonths => normalizedUtcNow.AddMonths(-3),
|
||||||
|
InitialSynchronizationRange.SixMonths => normalizedUtcNow.AddMonths(-6),
|
||||||
|
InitialSynchronizationRange.NineMonths => normalizedUtcNow.AddMonths(-9),
|
||||||
|
InitialSynchronizationRange.OneYear => normalizedUtcNow.AddYears(-1),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ public interface INewThemeService : IInitializeAsync
|
|||||||
Task<List<AppThemeBase>> GetAvailableThemesAsync();
|
Task<List<AppThemeBase>> GetAvailableThemesAsync();
|
||||||
Task<CustomThemeMetadata> CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData);
|
Task<CustomThemeMetadata> CreateNewCustomThemeAsync(string themeName, string accentColor, byte[] wallpaperData);
|
||||||
Task<List<CustomThemeMetadata>> GetCurrentCustomThemesAsync();
|
Task<List<CustomThemeMetadata>> GetCurrentCustomThemesAsync();
|
||||||
|
Task<bool> DeleteCustomThemeAsync(Guid themeId);
|
||||||
List<string> GetAvailableAccountColors();
|
List<string> GetAvailableAccountColors();
|
||||||
Task ApplyCustomThemeAsync(bool isInitializing);
|
Task ApplyCustomThemeAsync(bool isInitializing);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
public interface IShareActivationService
|
||||||
|
{
|
||||||
|
MailShareRequest? PendingShareRequest { get; set; }
|
||||||
|
MailShareRequest? ConsumePendingShareRequest();
|
||||||
|
void ClearPendingShareRequest();
|
||||||
|
void StagePendingComposeShareRequest(Guid draftUniqueId, MailShareRequest shareRequest);
|
||||||
|
MailShareRequest? ConsumePendingComposeShareRequest(Guid draftUniqueId);
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Accounts;
|
namespace Wino.Core.Domain.Models.Accounts;
|
||||||
|
|
||||||
public record AccountCreationDialogResult(MailProviderType ProviderType, string AccountName, SpecialImapProviderDetails SpecialImapProviderDetails, string AccountColorHex);
|
public record AccountCreationDialogResult(
|
||||||
|
MailProviderType ProviderType,
|
||||||
|
string AccountName,
|
||||||
|
SpecialImapProviderDetails SpecialImapProviderDetails,
|
||||||
|
string AccountColorHex,
|
||||||
|
InitialSynchronizationRange InitialSynchronizationRange);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Accounts;
|
||||||
|
|
||||||
|
public sealed class InitialSynchronizationRangeOption
|
||||||
|
{
|
||||||
|
public InitialSynchronizationRange Range { get; }
|
||||||
|
public string DisplayText { get; }
|
||||||
|
|
||||||
|
public bool IsEverything => Range == InitialSynchronizationRange.Everything;
|
||||||
|
|
||||||
|
public InitialSynchronizationRangeOption(InitialSynchronizationRange range, string displayText)
|
||||||
|
{
|
||||||
|
Range = range;
|
||||||
|
DisplayText = displayText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Wino.Core.Domain.Models.Common;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
|
public sealed class MailShareRequest
|
||||||
|
{
|
||||||
|
public MailShareRequest(IReadOnlyList<SharedFile> files)
|
||||||
|
{
|
||||||
|
Files = files ?? throw new ArgumentNullException(nameof(files));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<SharedFile> Files { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
|
public sealed class PendingComposeMailShareRequest
|
||||||
|
{
|
||||||
|
public PendingComposeMailShareRequest(Guid draftUniqueId, MailShareRequest shareRequest)
|
||||||
|
{
|
||||||
|
DraftUniqueId = draftUniqueId;
|
||||||
|
ShareRequest = shareRequest ?? throw new ArgumentNullException(nameof(shareRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid DraftUniqueId { get; }
|
||||||
|
public MailShareRequest ShareRequest { get; }
|
||||||
|
}
|
||||||
@@ -23,6 +23,14 @@
|
|||||||
"AccountCreationDialog_Initializing": "initializing",
|
"AccountCreationDialog_Initializing": "initializing",
|
||||||
"AccountCreationDialog_PreparingFolders": "We are getting folder information at the moment.",
|
"AccountCreationDialog_PreparingFolders": "We are getting folder information at the moment.",
|
||||||
"AccountCreationDialog_SigninIn": "Account information is being saved.",
|
"AccountCreationDialog_SigninIn": "Account information is being saved.",
|
||||||
|
"AccountCreation_InitialSynchronization_Title": "Mail synchronization range",
|
||||||
|
"AccountCreation_InitialSynchronization_Description": "Choose how far back Wino should download your mail during the first synchronization.",
|
||||||
|
"AccountCreation_InitialSynchronization_3Months": "3 Months",
|
||||||
|
"AccountCreation_InitialSynchronization_6Months": "6 Months",
|
||||||
|
"AccountCreation_InitialSynchronization_9Months": "9 Months",
|
||||||
|
"AccountCreation_InitialSynchronization_Year": "Year",
|
||||||
|
"AccountCreation_InitialSynchronization_Everything": "Everything",
|
||||||
|
"AccountCreation_InitialSynchronization_EverythingWarning": "This will synchronize all your mails to your computer. Extensive use of disk storage is needed. This is not recommended. For optimal performance use smaller synchronization timespan and use online search to access your mails.",
|
||||||
"Purchased": "Purchased",
|
"Purchased": "Purchased",
|
||||||
"AccountEditDialog_Message": "Account Name",
|
"AccountEditDialog_Message": "Account Name",
|
||||||
"AccountEditDialog_Title": "Edit Account",
|
"AccountEditDialog_Title": "Edit Account",
|
||||||
@@ -37,6 +45,8 @@
|
|||||||
"AccountDetailsPage_TabMail": "Mail",
|
"AccountDetailsPage_TabMail": "Mail",
|
||||||
"AccountDetailsPage_TabCalendar": "Calendar",
|
"AccountDetailsPage_TabCalendar": "Calendar",
|
||||||
"AccountDetailsPage_CalendarListDescription": "Select a calendar to configure its settings",
|
"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.",
|
||||||
"AddHyperlink": "Add",
|
"AddHyperlink": "Add",
|
||||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||||
@@ -797,6 +807,10 @@
|
|||||||
"SettingsConfigureSpecialFolders_Description": "Set folders with special functions. Folders such as Archive, Inbox, and Drafts are essential for Wino to function properly.",
|
"SettingsConfigureSpecialFolders_Description": "Set folders with special functions. Folders such as Archive, Inbox, and Drafts are essential for Wino to function properly.",
|
||||||
"SettingsConfigureSpecialFolders_Title": "Configure System Folders",
|
"SettingsConfigureSpecialFolders_Title": "Configure System Folders",
|
||||||
"SettingsCustomTheme_Description": "Create your own custom theme with custom wallpaper and accent color.",
|
"SettingsCustomTheme_Description": "Create your own custom theme with custom wallpaper and accent color.",
|
||||||
|
"SettingsCustomTheme_DeleteConfirm_Message": "Delete custom theme \"{0}\"? Its saved wallpaper will also be removed from disk.",
|
||||||
|
"SettingsCustomTheme_DeleteConfirm_Title": "Delete Theme",
|
||||||
|
"SettingsCustomTheme_DeleteMissing": "This custom theme no longer exists.",
|
||||||
|
"SettingsCustomTheme_DeleteSuccess": "Custom theme \"{0}\" was deleted.",
|
||||||
"SettingsCustomTheme_Title": "Custom Theme",
|
"SettingsCustomTheme_Title": "Custom Theme",
|
||||||
"SettingsDeleteAccount_Description": "Delete all e-mails and credentials associated with this account.",
|
"SettingsDeleteAccount_Description": "Delete all e-mails and credentials associated with this account.",
|
||||||
"SettingsDeleteAccount_Title": "Delete this account",
|
"SettingsDeleteAccount_Title": "Delete this account",
|
||||||
|
|||||||
@@ -174,8 +174,46 @@ public partial class PersonalizationPageViewModel : CoreBaseViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task DeleteCustomThemeAsync(AppThemeBase theme)
|
||||||
|
{
|
||||||
|
if (theme == null || theme.AppThemeType != AppThemeType.Custom)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldDelete = await _dialogService.ShowConfirmationDialogAsync(
|
||||||
|
string.Format(Translator.SettingsCustomTheme_DeleteConfirm_Message, theme.ThemeName),
|
||||||
|
Translator.SettingsCustomTheme_DeleteConfirm_Title,
|
||||||
|
Translator.Buttons_Delete);
|
||||||
|
|
||||||
|
if (!shouldDelete)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDeleted = await _newThemeService.DeleteCustomThemeAsync(theme.Id);
|
||||||
|
|
||||||
|
if (!isDeleted)
|
||||||
|
{
|
||||||
|
_dialogService.InfoBarMessage(
|
||||||
|
Translator.GeneralTitle_Warning,
|
||||||
|
Translator.SettingsCustomTheme_DeleteMissing,
|
||||||
|
InfoBarMessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await InitializeSettingsAsync();
|
||||||
|
|
||||||
|
_dialogService.InfoBarMessage(
|
||||||
|
Translator.GeneralTitle_Info,
|
||||||
|
string.Format(Translator.SettingsCustomTheme_DeleteSuccess, theme.ThemeName),
|
||||||
|
InfoBarMessageType.Success);
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeColors()
|
private void InitializeColors()
|
||||||
{
|
{
|
||||||
|
Colors.Clear();
|
||||||
Colors.Add(new AppColorViewModel("#0078d7"));
|
Colors.Add(new AppColorViewModel("#0078d7"));
|
||||||
Colors.Add(new AppColorViewModel("#00838c"));
|
Colors.Add(new AppColorViewModel("#00838c"));
|
||||||
Colors.Add(new AppColorViewModel("#e3008c"));
|
Colors.Add(new AppColorViewModel("#e3008c"));
|
||||||
|
|||||||
@@ -81,9 +81,8 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
public override uint BatchModificationSize => 1000;
|
public override uint BatchModificationSize => 1000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum messages to fetch per folder during initial sync (1500).
|
/// Legacy page size hint kept for compatibility with shared synchronizer contracts.
|
||||||
/// All messages are downloaded with METADATA ONLY - no raw MIME content.
|
/// Gmail initial sync now downloads all messages inside the selected cutoff window.
|
||||||
/// Uses Gmail API's Metadata format which includes headers, labels, and snippet but NOT full message body.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override uint InitialMessageDownloadCountPerFolder => 1500;
|
public override uint InitialMessageDownloadCountPerFolder => 1500;
|
||||||
|
|
||||||
@@ -304,13 +303,18 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs initial synchronization by downloading messages per-folder.
|
/// Performs initial synchronization by downloading messages per-folder.
|
||||||
/// Each folder gets up to 1500 messages, but we track already downloaded message IDs globally
|
/// Messages are filtered by the account's configured initial synchronization cutoff date when present,
|
||||||
/// to avoid downloading the same message multiple times (Gmail messages can have multiple labels).
|
/// and duplicates are avoided globally because Gmail messages can have multiple labels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<List<string>> PerformInitialSyncAsync(CancellationToken cancellationToken)
|
private async Task<List<string>> PerformInitialSyncAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Track all downloaded message IDs globally to avoid duplicate downloads
|
// Track all downloaded message IDs globally to avoid duplicate downloads
|
||||||
var downloadedMessageIds = new HashSet<string>();
|
var downloadedMessageIds = new HashSet<string>();
|
||||||
|
var referenceDateUtc = Account.CreatedAt ?? DateTime.UtcNow;
|
||||||
|
var initialSynchronizationCutoffDateUtc = Account.InitialSynchronizationRange.ToCutoffDateUtc(referenceDateUtc);
|
||||||
|
var queryText = initialSynchronizationCutoffDateUtc.HasValue
|
||||||
|
? $"after:{initialSynchronizationCutoffDateUtc.Value.ToUniversalTime():yyyy/MM/dd}"
|
||||||
|
: null;
|
||||||
|
|
||||||
_logger.Information("Performing initial sync for {Name} - downloading messages per folder", Account.Name);
|
_logger.Information("Performing initial sync for {Name} - downloading messages per folder", Account.Name);
|
||||||
|
|
||||||
@@ -337,7 +341,6 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
var folderDownloaded = 0;
|
var folderDownloaded = 0;
|
||||||
string pageToken = null;
|
string pageToken = null;
|
||||||
var remainingToDownload = (int)InitialMessageDownloadCountPerFolder;
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -345,8 +348,9 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
var request = _gmailService.Users.Messages.List("me");
|
var request = _gmailService.Users.Messages.List("me");
|
||||||
request.LabelIds = new Google.Apis.Util.Repeatable<string>(new[] { folder.RemoteFolderId });
|
request.LabelIds = new Google.Apis.Util.Repeatable<string>(new[] { folder.RemoteFolderId });
|
||||||
request.MaxResults = Math.Min(remainingToDownload, 500); // API max is 500
|
request.MaxResults = 500; // API max is 500
|
||||||
request.PageToken = pageToken;
|
request.PageToken = pageToken;
|
||||||
|
request.Q = queryText;
|
||||||
|
|
||||||
var response = await request.ExecuteAsync(cancellationToken);
|
var response = await request.ExecuteAsync(cancellationToken);
|
||||||
|
|
||||||
@@ -373,19 +377,12 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
totalMessagesDownloaded += newMessageIds.Count;
|
totalMessagesDownloaded += newMessageIds.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count all messages (including duplicates) toward the folder limit
|
|
||||||
remainingToDownload -= response.Messages.Count;
|
|
||||||
|
|
||||||
_logger.Debug("Folder {FolderName}: Downloaded {New} new messages ({Total} total in folder)",
|
_logger.Debug("Folder {FolderName}: Downloaded {New} new messages ({Total} total in folder)",
|
||||||
folder.FolderName, newMessageIds.Count, folderDownloaded);
|
folder.FolderName, newMessageIds.Count, folderDownloaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
pageToken = response.NextPageToken;
|
pageToken = response.NextPageToken;
|
||||||
|
|
||||||
// Stop if we've processed enough messages for this folder or no more pages
|
|
||||||
if (remainingToDownload <= 0 || string.IsNullOrEmpty(pageToken))
|
|
||||||
break;
|
|
||||||
|
|
||||||
} while (!string.IsNullOrEmpty(pageToken));
|
} while (!string.IsNullOrEmpty(pageToken));
|
||||||
|
|
||||||
_logger.Information("Folder {FolderName}: Downloaded {Count} messages", folder.FolderName, folderDownloaded);
|
_logger.Information("Folder {FolderName}: Downloaded {Count} messages", folder.FolderName, folderDownloaded);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using MailKit.Search;
|
|||||||
using MoreLinq;
|
using MoreLinq;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
using Wino.Core.Domain.Extensions;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
@@ -252,9 +253,20 @@ public class UnifiedImapSynchronizer
|
|||||||
.OpenAsync(FolderAccess.ReadOnly, folder.UidValidity, localHighestModSeq, knownUidStructs, cancellationToken)
|
.OpenAsync(FolderAccess.ReadOnly, folder.UidValidity, localHighestModSeq, knownUidStructs, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
var changedUids = await remoteFolder
|
IList<UniqueId> changedUids;
|
||||||
.SearchAsync(SearchQuery.ChangedSince(localHighestModSeq), cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
if (folder.HighestModeSeq == 0)
|
||||||
|
{
|
||||||
|
changedUids = await remoteFolder
|
||||||
|
.SearchAsync(BuildInitialSyncQuery(synchronizer), cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changedUids = await remoteFolder
|
||||||
|
.SearchAsync(SearchQuery.ChangedSince(localHighestModSeq), cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, changedUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, changedUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -308,25 +320,26 @@ public class UnifiedImapSynchronizer
|
|||||||
{
|
{
|
||||||
IList<UniqueId> changedUids;
|
IList<UniqueId> changedUids;
|
||||||
|
|
||||||
if (client.Capabilities.HasFlag(ImapCapabilities.Sort))
|
if (isInitialSync)
|
||||||
{
|
{
|
||||||
changedUids = await remoteFolder
|
changedUids = await remoteFolder
|
||||||
.SortAsync(SearchQuery.ChangedSince(localHighestModSeq), [OrderBy.ReverseDate], cancellationToken)
|
.SearchAsync(BuildInitialSyncQuery(synchronizer), cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
changedUids = await remoteFolder
|
if (client.Capabilities.HasFlag(ImapCapabilities.Sort))
|
||||||
.SearchAsync(SearchQuery.ChangedSince(localHighestModSeq), cancellationToken)
|
{
|
||||||
.ConfigureAwait(false);
|
changedUids = await remoteFolder
|
||||||
}
|
.SortAsync(SearchQuery.ChangedSince(localHighestModSeq), [OrderBy.ReverseDate], cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
if (isInitialSync)
|
}
|
||||||
{
|
else
|
||||||
changedUids = changedUids
|
{
|
||||||
.OrderByDescending(a => a.Id)
|
changedUids = await remoteFolder
|
||||||
.Take((int)synchronizer.InitialMessageDownloadCountPerFolder)
|
.SearchAsync(SearchQuery.ChangedSince(localHighestModSeq), cancellationToken)
|
||||||
.ToList();
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, changedUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, changedUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -367,15 +380,12 @@ public class UnifiedImapSynchronizer
|
|||||||
|
|
||||||
if (folder.HighestKnownUid == 0)
|
if (folder.HighestKnownUid == 0)
|
||||||
{
|
{
|
||||||
var remoteUids = await remoteFolder.SearchAsync(SearchQuery.All, cancellationToken).ConfigureAwait(false);
|
var initialUids = await remoteFolder
|
||||||
|
.SearchAsync(BuildInitialSyncQuery(synchronizer), cancellationToken)
|
||||||
var initialUids = remoteUids
|
.ConfigureAwait(false);
|
||||||
.OrderByDescending(a => a.Id)
|
|
||||||
.Take((int)synchronizer.InitialMessageDownloadCountPerFolder)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, initialUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, initialUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
||||||
UpdateHighestKnownUid(folder, remoteFolder, remoteUids.Select(a => a.Id));
|
UpdateHighestKnownUid(folder, remoteFolder, initialUids.Select(a => a.Id));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -410,6 +420,22 @@ public class UnifiedImapSynchronizer
|
|||||||
|
|
||||||
#region Shared Helpers
|
#region Shared Helpers
|
||||||
|
|
||||||
|
private static SearchQuery BuildInitialSyncQuery(IImapSynchronizer synchronizer)
|
||||||
|
{
|
||||||
|
if (synchronizer is IBaseSynchronizer { Account: { } account })
|
||||||
|
{
|
||||||
|
var referenceDateUtc = account.CreatedAt ?? DateTime.UtcNow;
|
||||||
|
var cutoffDateUtc = account.InitialSynchronizationRange.ToCutoffDateUtc(referenceDateUtc);
|
||||||
|
|
||||||
|
if (cutoffDateUtc.HasValue)
|
||||||
|
{
|
||||||
|
return SearchQuery.DeliveredAfter(cutoffDateUtc.Value.ToUniversalTime().Date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchQuery.All;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task EnsureUidValidityStateAsync(MailItemFolder folder, IMailFolder remoteFolder)
|
private async Task EnsureUidValidityStateAsync(MailItemFolder folder, IMailFolder remoteFolder)
|
||||||
{
|
{
|
||||||
if (folder.UidValidity != 0 && remoteFolder.UidValidity != folder.UidValidity)
|
if (folder.UidValidity != 0 && remoteFolder.UidValidity != folder.UidValidity)
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ public partial class OutlookSynchronizerJsonContext : JsonSerializerContext;
|
|||||||
///
|
///
|
||||||
/// SYNCHRONIZATION STRATEGY:
|
/// SYNCHRONIZATION STRATEGY:
|
||||||
/// - Uses delta API for both initial and incremental sync
|
/// - Uses delta API for both initial and incremental sync
|
||||||
/// - Initial sync: Downloads last 30 days of emails with metadata only
|
/// - Initial sync: Downloads messages using the account's configured cutoff date with metadata only
|
||||||
/// - Incremental sync: Uses delta token to get only changes since last sync
|
/// - Incremental sync: Uses delta token to get only changes since last sync
|
||||||
/// - Messages are downloaded with metadata only (no MIME content during sync)
|
/// - Messages are downloaded with metadata only (no MIME content during sync)
|
||||||
/// - MIME files are downloaded on-demand when user explicitly reads a message
|
/// - MIME files are downloaded on-demand when user explicitly reads a message
|
||||||
///
|
///
|
||||||
/// Key implementation details:
|
/// Key implementation details:
|
||||||
/// - SynchronizeFolderAsync: Main entry point for per-folder synchronization
|
/// - SynchronizeFolderAsync: Main entry point for per-folder synchronization
|
||||||
/// - DownloadMailsForInitialSyncAsync: Downloads last 30 days using delta API with filter
|
/// - DownloadMailsForInitialSyncAsync: Downloads messages using delta API with an optional cutoff filter
|
||||||
/// - ProcessDeltaChangesAsync: Processes incremental changes using delta token
|
/// - ProcessDeltaChangesAsync: Processes incremental changes using delta token
|
||||||
/// - DownloadMessageMetadataBatchAsync: Downloads metadata in batches using Graph batch API
|
/// - DownloadMessageMetadataBatchAsync: Downloads metadata in batches using Graph batch API
|
||||||
/// - CreateMailCopyFromMessageAsync: Creates MailCopy from Message metadata
|
/// - CreateMailCopyFromMessageAsync: Creates MailCopy from Message metadata
|
||||||
@@ -343,9 +343,9 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
// Check if we have a delta token
|
// Check if we have a delta token
|
||||||
if (string.IsNullOrEmpty(folder.DeltaToken))
|
if (string.IsNullOrEmpty(folder.DeltaToken))
|
||||||
{
|
{
|
||||||
_logger.Debug("No delta token for folder {FolderName}. Starting initial sync (last 30 days).", folder.FolderName);
|
_logger.Debug("No delta token for folder {FolderName}. Starting initial sync.", folder.FolderName);
|
||||||
|
|
||||||
// Download mails for initial sync (last 30 days)
|
// Download mails for initial sync using the account's configured cutoff date.
|
||||||
await DownloadMailsForInitialSyncAsync(folder, downloadedMessageIds, cancellationToken).ConfigureAwait(false);
|
await DownloadMailsForInitialSyncAsync(folder, downloadedMessageIds, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -367,27 +367,37 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downloads mails for initial synchronization using Delta API with 30-day filter.
|
/// Downloads mails for initial synchronization using Delta API with the account's configured cutoff date.
|
||||||
/// Downloads metadata only (no MIME content) for messages received in the last 30 days.
|
/// Downloads metadata only (no MIME content) for messages received after that date.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task DownloadMailsForInitialSyncAsync(MailItemFolder folder, List<string> downloadedMessageIds, CancellationToken cancellationToken)
|
private async Task DownloadMailsForInitialSyncAsync(MailItemFolder folder, List<string> downloadedMessageIds, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.Debug("Starting initial mail download for folder {FolderName} (last 6 months)", folder.FolderName);
|
_logger.Debug("Starting initial mail download for folder {FolderName}", folder.FolderName);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Calculate date 6 months ago
|
var referenceDateUtc = Account.CreatedAt ?? DateTime.UtcNow;
|
||||||
var sixMonthsAgo = DateTime.UtcNow.AddMonths(-6);
|
var initialSynchronizationCutoffDateUtc = Account.InitialSynchronizationRange.ToCutoffDateUtc(referenceDateUtc);
|
||||||
var filterDate = sixMonthsAgo.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
var filterDate = initialSynchronizationCutoffDateUtc?.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ");
|
||||||
|
|
||||||
_logger.Information("Downloading messages received after {FilterDate} for folder {FolderName}", filterDate, folder.FolderName);
|
if (filterDate != null)
|
||||||
|
{
|
||||||
|
_logger.Information("Downloading messages received after {FilterDate} for folder {FolderName}", filterDate, folder.FolderName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Information("Downloading all available messages for folder {FolderName}", folder.FolderName);
|
||||||
|
}
|
||||||
|
|
||||||
// Use Delta API with receivedDateTime filter for last 6 months
|
|
||||||
var messageCollectionPage = await _graphClient.Me.MailFolders[folder.RemoteFolderId].Messages.Delta.GetAsDeltaGetResponseAsync((config) =>
|
var messageCollectionPage = await _graphClient.Me.MailFolders[folder.RemoteFolderId].Messages.Delta.GetAsDeltaGetResponseAsync((config) =>
|
||||||
{
|
{
|
||||||
config.QueryParameters.Select = outlookMessageSelectParameters;
|
config.QueryParameters.Select = outlookMessageSelectParameters;
|
||||||
config.QueryParameters.Orderby = ["receivedDateTime desc"];
|
config.QueryParameters.Orderby = ["receivedDateTime desc"];
|
||||||
config.QueryParameters.Filter = $"receivedDateTime ge {filterDate}";
|
|
||||||
|
if (filterDate != null)
|
||||||
|
{
|
||||||
|
config.QueryParameters.Filter = $"receivedDateTime ge {filterDate}";
|
||||||
|
}
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var totalProcessed = 0;
|
var totalProcessed = 0;
|
||||||
|
|||||||
@@ -2,24 +2,25 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Wino.Core.Misc;
|
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Extensions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Accounts;
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
using Wino.Core.Domain.Models.Folders;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
|
using Wino.Core.Misc;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Core.ViewModels.Data;
|
using Wino.Core.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Calendar;
|
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
@@ -101,6 +102,14 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
|||||||
? $"ms-appx:///Assets/Providers/{Account.SpecialImapProvider}.png"
|
? $"ms-appx:///Assets/Providers/{Account.SpecialImapProvider}.png"
|
||||||
: $"ms-appx:///Assets/Providers/{Account?.ProviderType}.png";
|
: $"ms-appx:///Assets/Providers/{Account?.ProviderType}.png";
|
||||||
public string Address => Account?.Address ?? string.Empty;
|
public string Address => Account?.Address ?? string.Empty;
|
||||||
|
public bool IsInitialSynchronizationSummaryVisible => Account?.CreatedAt.HasValue == true && Account.InitialSynchronizationRange != InitialSynchronizationRange.Everything;
|
||||||
|
public string InitialSynchronizationSummary => Account?.CreatedAt is not DateTime createdAtUtc
|
||||||
|
? string.Empty
|
||||||
|
: Account.InitialSynchronizationRange.ToCutoffDateUtc(createdAtUtc) is not DateTime cutoffDateUtc
|
||||||
|
? string.Empty
|
||||||
|
: string.Format(
|
||||||
|
Translator.AccountDetailsPage_InitialSynchronization_Description,
|
||||||
|
cutoffDateUtc.ToLocalTime().ToString("D", CultureInfo.CurrentUICulture));
|
||||||
|
|
||||||
public List<ImapAuthenticationMethodModel> AvailableAuthenticationMethods { get; } =
|
public List<ImapAuthenticationMethodModel> AvailableAuthenticationMethods { get; } =
|
||||||
[
|
[
|
||||||
@@ -363,13 +372,15 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
|||||||
OnPropertyChanged(nameof(IsFocusedInboxSupportedForAccount));
|
OnPropertyChanged(nameof(IsFocusedInboxSupportedForAccount));
|
||||||
OnPropertyChanged(nameof(ProviderIconPath));
|
OnPropertyChanged(nameof(ProviderIconPath));
|
||||||
OnPropertyChanged(nameof(Address));
|
OnPropertyChanged(nameof(Address));
|
||||||
|
OnPropertyChanged(nameof(IsInitialSynchronizationSummaryVisible));
|
||||||
|
OnPropertyChanged(nameof(InitialSynchronizationSummary));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
|
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPropertyChanged(e);
|
base.OnPropertyChanged(e);
|
||||||
|
|
||||||
if (!IsActive || !isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
switch (e.PropertyName)
|
switch (e.PropertyName)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
CustomServerInformation customServerInformation = null;
|
CustomServerInformation customServerInformation = null;
|
||||||
|
var accountCreatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
// Build account in memory
|
// Build account in memory
|
||||||
_createdAccount = new MailAccount
|
_createdAccount = new MailAccount
|
||||||
@@ -179,6 +180,8 @@ public partial class AccountSetupProgressPageViewModel : MailBaseViewModel
|
|||||||
Name = WizardContext.AccountName,
|
Name = WizardContext.AccountName,
|
||||||
SpecialImapProvider = WizardContext.SelectedProvider.SpecialImapProvider,
|
SpecialImapProvider = WizardContext.SelectedProvider.SpecialImapProvider,
|
||||||
AccountColorHex = WizardContext.AccountColorHex,
|
AccountColorHex = WizardContext.AccountColorHex,
|
||||||
|
CreatedAt = accountCreatedAt,
|
||||||
|
InitialSynchronizationRange = WizardContext.SelectedInitialSynchronizationRange,
|
||||||
IsCalendarAccessGranted = true
|
IsCalendarAccessGranted = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using Wino.Core.Domain.Extensions;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
using Wino.Core.Domain.Models;
|
using Wino.Core.Domain.Models;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Extensions;
|
using Wino.Core.Extensions;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
@@ -159,6 +160,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
public readonly IPreferencesService PreferencesService;
|
public readonly IPreferencesService PreferencesService;
|
||||||
public readonly IContactService ContactService;
|
public readonly IContactService ContactService;
|
||||||
public readonly ISmimeCertificateService _smimeCertificateService;
|
public readonly ISmimeCertificateService _smimeCertificateService;
|
||||||
|
private readonly IShareActivationService _shareActivationService;
|
||||||
|
|
||||||
public ComposePageViewModel(IMailDialogService dialogService,
|
public ComposePageViewModel(IMailDialogService dialogService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
@@ -172,7 +174,8 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
IContactService contactService,
|
IContactService contactService,
|
||||||
IFontService fontService,
|
IFontService fontService,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService,
|
||||||
ISmimeCertificateService smimeCertificateService)
|
ISmimeCertificateService smimeCertificateService,
|
||||||
|
IShareActivationService shareActivationService)
|
||||||
{
|
{
|
||||||
NativeAppService = nativeAppService;
|
NativeAppService = nativeAppService;
|
||||||
ContactService = contactService;
|
ContactService = contactService;
|
||||||
@@ -188,6 +191,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
_emailTemplateService = emailTemplateService;
|
_emailTemplateService = emailTemplateService;
|
||||||
_worker = worker;
|
_worker = worker;
|
||||||
_smimeCertificateService = smimeCertificateService;
|
_smimeCertificateService = smimeCertificateService;
|
||||||
|
_shareActivationService = shareActivationService;
|
||||||
|
|
||||||
foreach (var cert in _smimeCertificateService.GetCertificates(emailAddress: SelectedAlias?.AliasAddress))
|
foreach (var cert in _smimeCertificateService.GetCertificates(emailAddress: SelectedAlias?.AliasAddress))
|
||||||
{
|
{
|
||||||
@@ -752,6 +756,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
await LoadAddressInfoAsync(replyingMime.Bcc, BCCItems);
|
await LoadAddressInfoAsync(replyingMime.Bcc, BCCItems);
|
||||||
|
|
||||||
LoadAttachments();
|
LoadAttachments();
|
||||||
|
ApplyPendingSharedAttachments();
|
||||||
|
|
||||||
if (replyingMime.Cc.Any() || replyingMime.Bcc.Any())
|
if (replyingMime.Cc.Any() || replyingMime.Bcc.Any())
|
||||||
IsCCBCCVisible = true;
|
IsCCBCCVisible = true;
|
||||||
@@ -783,6 +788,24 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyPendingSharedAttachments()
|
||||||
|
{
|
||||||
|
var draftUniqueId = CurrentMailDraftItem?.MailCopy?.UniqueId ?? Guid.Empty;
|
||||||
|
|
||||||
|
if (draftUniqueId == Guid.Empty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var shareRequest = _shareActivationService.ConsumePendingComposeShareRequest(draftUniqueId);
|
||||||
|
|
||||||
|
if (shareRequest?.Files == null || shareRequest.Files.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var sharedFile in shareRequest.Files)
|
||||||
|
{
|
||||||
|
IncludedAttachments.Add(new MailAttachmentViewModel(sharedFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadAddressInfoAsync(InternetAddressList list, ObservableCollection<AccountContact> collection)
|
private async Task LoadAddressInfoAsync(InternetAddressList list, ObservableCollection<AccountContact> collection)
|
||||||
{
|
{
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ public partial class WelcomeWizardContext : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string AccountColorHex { get; set; }
|
public partial string AccountColorHex { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial InitialSynchronizationRange SelectedInitialSynchronizationRange { get; set; } = InitialSynchronizationRange.SixMonths;
|
||||||
|
|
||||||
// Special IMAP fields (iCloud/Yahoo)
|
// Special IMAP fields (iCloud/Yahoo)
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string DisplayName { get; set; }
|
public partial string DisplayName { get; set; }
|
||||||
@@ -62,7 +65,8 @@ public partial class WelcomeWizardContext : ObservableObject
|
|||||||
SelectedProvider.Type,
|
SelectedProvider.Type,
|
||||||
AccountName,
|
AccountName,
|
||||||
BuildSpecialImapProviderDetails(),
|
BuildSpecialImapProviderDetails(),
|
||||||
AccountColorHex);
|
AccountColorHex,
|
||||||
|
SelectedInitialSynchronizationRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
@@ -70,6 +74,7 @@ public partial class WelcomeWizardContext : ObservableObject
|
|||||||
SelectedProvider = null;
|
SelectedProvider = null;
|
||||||
AccountName = null;
|
AccountName = null;
|
||||||
AccountColorHex = null;
|
AccountColorHex = null;
|
||||||
|
SelectedInitialSynchronizationRange = InitialSynchronizationRange.SixMonths;
|
||||||
DisplayName = null;
|
DisplayName = null;
|
||||||
EmailAddress = null;
|
EmailAddress = null;
|
||||||
AppSpecificPassword = null;
|
AppSpecificPassword = null;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ using Wino.Core.Domain.Models.Navigation;
|
|||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Calendar;
|
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
@@ -319,7 +318,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var minimalSettings = BuildMinimalSettingsOrThrow();
|
var minimalSettings = BuildMinimalSettingsOrThrow();
|
||||||
await AutoDiscoverAndApplySettingsAsync(minimalSettings).ConfigureAwait(false);
|
await AutoDiscoverAndApplySettingsAsync(minimalSettings);
|
||||||
|
|
||||||
_mailDialogService.InfoBarMessage(
|
_mailDialogService.InfoBarMessage(
|
||||||
Translator.IMAPSetupDialog_ValidationSuccess_Title,
|
Translator.IMAPSetupDialog_ValidationSuccess_Title,
|
||||||
@@ -399,7 +398,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await EnsureImapSettingsPreparedAsync().ConfigureAwait(false);
|
await EnsureImapSettingsPreparedAsync();
|
||||||
|
|
||||||
var serverInformation = BuildServerInformation();
|
var serverInformation = BuildServerInformation();
|
||||||
|
|
||||||
@@ -407,12 +406,12 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
ValidateImapSettings(serverInformation);
|
ValidateImapSettings(serverInformation);
|
||||||
ValidateCalendarModeSpecificSettings(serverInformation);
|
ValidateCalendarModeSpecificSettings(serverInformation);
|
||||||
|
|
||||||
await ValidateImapConnectivityAsync(serverInformation).ConfigureAwait(false);
|
await ValidateImapConnectivityAsync(serverInformation);
|
||||||
IsImapValidationSucceeded = true;
|
IsImapValidationSucceeded = true;
|
||||||
|
|
||||||
if (serverInformation.CalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
if (serverInformation.CalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
||||||
{
|
{
|
||||||
await ValidateCalDavConnectivityAsync(serverInformation).ConfigureAwait(false);
|
await ValidateCalDavConnectivityAsync(serverInformation);
|
||||||
IsCalDavValidationSucceeded = true;
|
IsCalDavValidationSucceeded = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -432,7 +431,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SaveEditFlowAsync(serverInformation).ConfigureAwait(false);
|
await SaveEditFlowAsync(serverInformation);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -654,7 +653,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var minimalSettings = BuildMinimalSettingsOrThrow();
|
var minimalSettings = BuildMinimalSettingsOrThrow();
|
||||||
await AutoDiscoverAndApplySettingsAsync(minimalSettings).ConfigureAwait(false);
|
await AutoDiscoverAndApplySettingsAsync(minimalSettings);
|
||||||
|
|
||||||
if (!HasCompleteImapSettings())
|
if (!HasCompleteImapSettings())
|
||||||
throw new InvalidOperationException(Translator.Exception_ImapAutoDiscoveryFailed);
|
throw new InvalidOperationException(Translator.Exception_ImapAutoDiscoveryFailed);
|
||||||
@@ -676,22 +675,25 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
if (serverInformation == null)
|
if (serverInformation == null)
|
||||||
throw new InvalidOperationException(Translator.Exception_ImapAutoDiscoveryFailed);
|
throw new InvalidOperationException(Translator.Exception_ImapAutoDiscoveryFailed);
|
||||||
|
|
||||||
ApplyServerInformation(serverInformation);
|
await ExecuteUIThread(async () =>
|
||||||
|
|
||||||
if (IsCalendarSupportEnabled && SelectedCalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
|
||||||
{
|
{
|
||||||
var discoveredCalDavUri = await _autoDiscoveryService.DiscoverCalDavServiceUriAsync(minimalSettings.Email).ConfigureAwait(false);
|
ApplyServerInformation(serverInformation);
|
||||||
if (discoveredCalDavUri != null)
|
|
||||||
|
if (IsCalendarSupportEnabled && SelectedCalendarSupportMode == ImapCalendarSupportMode.CalDav)
|
||||||
{
|
{
|
||||||
CalDavServiceUrl = discoveredCalDavUri.ToString();
|
var discoveredCalDavUri = await _autoDiscoveryService.DiscoverCalDavServiceUriAsync(minimalSettings.Email);
|
||||||
|
if (discoveredCalDavUri != null)
|
||||||
|
{
|
||||||
|
CalDavServiceUrl = discoveredCalDavUri.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(CalDavUsername))
|
||||||
|
CalDavUsername = minimalSettings.Email;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(CalDavPassword))
|
||||||
|
CalDavPassword = minimalSettings.Password;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (string.IsNullOrWhiteSpace(CalDavUsername))
|
|
||||||
CalDavUsername = minimalSettings.Email;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(CalDavPassword))
|
|
||||||
CalDavPassword = minimalSettings.Password;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private async Task ValidateImapConnectivityAsync(CustomServerInformation serverInformation)
|
private async Task ValidateImapConnectivityAsync(CustomServerInformation serverInformation)
|
||||||
{
|
{
|
||||||
@@ -995,7 +997,12 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
SpecialImapProvider = _editingSpecialImapProvider,
|
SpecialImapProvider = _editingSpecialImapProvider,
|
||||||
IsCalendarAccessGranted = mode != ImapCalendarSupportMode.Disabled
|
IsCalendarAccessGranted = mode != ImapCalendarSupportMode.Disabled
|
||||||
},
|
},
|
||||||
new AccountCreationDialogResult(MailProviderType.IMAP4, DisplayName.Trim(), providerDetails, string.Empty));
|
new AccountCreationDialogResult(
|
||||||
|
MailProviderType.IMAP4,
|
||||||
|
DisplayName.Trim(),
|
||||||
|
providerDetails,
|
||||||
|
string.Empty,
|
||||||
|
_wizardContext.SelectedInitialSynchronizationRange));
|
||||||
|
|
||||||
if (serverInformation == null)
|
if (serverInformation == null)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ using Wino.Core.Domain.Entities.Shared;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.MenuItems;
|
using Wino.Core.Domain.MenuItems;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
|
||||||
using Wino.Core.Domain.Models;
|
using Wino.Core.Domain.Models;
|
||||||
|
using Wino.Core.Domain.Models.Folders;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
@@ -84,6 +85,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
private readonly IMimeFileService _mimeFileService;
|
private readonly IMimeFileService _mimeFileService;
|
||||||
private readonly IWebView2RuntimeValidatorService _webView2RuntimeValidatorService;
|
private readonly IWebView2RuntimeValidatorService _webView2RuntimeValidatorService;
|
||||||
private readonly IStoreUpdateService _storeUpdateService;
|
private readonly IStoreUpdateService _storeUpdateService;
|
||||||
|
private readonly IShareActivationService _shareActivationService;
|
||||||
|
|
||||||
private readonly INativeAppService _nativeAppService;
|
private readonly INativeAppService _nativeAppService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
@@ -109,7 +111,8 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
IConfigurationService configurationService,
|
IConfigurationService configurationService,
|
||||||
IStartupBehaviorService startupBehaviorService,
|
IStartupBehaviorService startupBehaviorService,
|
||||||
IWebView2RuntimeValidatorService webView2RuntimeValidatorService,
|
IWebView2RuntimeValidatorService webView2RuntimeValidatorService,
|
||||||
IStoreUpdateService storeUpdateService)
|
IStoreUpdateService storeUpdateService,
|
||||||
|
IShareActivationService shareActivationService)
|
||||||
{
|
{
|
||||||
StatePersistenceService = statePersistanceService;
|
StatePersistenceService = statePersistanceService;
|
||||||
|
|
||||||
@@ -131,6 +134,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
_winoRequestDelegator = winoRequestDelegator;
|
_winoRequestDelegator = winoRequestDelegator;
|
||||||
_webView2RuntimeValidatorService = webView2RuntimeValidatorService;
|
_webView2RuntimeValidatorService = webView2RuntimeValidatorService;
|
||||||
_storeUpdateService = storeUpdateService;
|
_storeUpdateService = storeUpdateService;
|
||||||
|
_shareActivationService = shareActivationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDispatcherAssigned()
|
protected override void OnDispatcherAssigned()
|
||||||
@@ -274,6 +278,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ProcessLaunchOptionsAsync();
|
await ProcessLaunchOptionsAsync();
|
||||||
|
await HandlePendingShareRequestAsync();
|
||||||
await ValidateWebView2RuntimeAsync();
|
await ValidateWebView2RuntimeAsync();
|
||||||
|
|
||||||
if (shouldRunStartupFlows && !Debugger.IsAttached)
|
if (shouldRunStartupFlows && !Debugger.IsAttached)
|
||||||
@@ -943,6 +948,9 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateNewMailForAsync(MailAccount account)
|
public async Task CreateNewMailForAsync(MailAccount account)
|
||||||
|
=> await CreateNewMailForAsync(account, null);
|
||||||
|
|
||||||
|
public async Task CreateNewMailForAsync(MailAccount account, MailShareRequest shareRequest)
|
||||||
{
|
{
|
||||||
if (account == null) return;
|
if (account == null) return;
|
||||||
|
|
||||||
@@ -974,6 +982,11 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account.Id, draftOptions).ConfigureAwait(false);
|
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account.Id, draftOptions).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (shareRequest?.Files?.Count > 0)
|
||||||
|
{
|
||||||
|
_shareActivationService.StagePendingComposeShareRequest(draftMailCopy.UniqueId, shareRequest);
|
||||||
|
}
|
||||||
|
|
||||||
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason);
|
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason);
|
||||||
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||||
}
|
}
|
||||||
@@ -1034,6 +1047,35 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
await CreateNewMailForAsync(targetAccount);
|
await CreateNewMailForAsync(targetAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task HandlePendingShareRequestAsync()
|
||||||
|
{
|
||||||
|
var shareRequest = _shareActivationService.ConsumePendingShareRequest();
|
||||||
|
|
||||||
|
if (shareRequest?.Files == null || shareRequest.Files.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var accounts = await _accountService.GetAccountsAsync();
|
||||||
|
|
||||||
|
if (!accounts.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
MailAccount targetAccount = null;
|
||||||
|
|
||||||
|
if (accounts.Count == 1)
|
||||||
|
{
|
||||||
|
targetAccount = accounts[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetAccount = await _dialogService.ShowAccountPickerDialogAsync(accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetAccount == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await CreateNewMailForAsync(targetAccount, shareRequest);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RecreateMenuItemsAsync()
|
private async Task RecreateMenuItemsAsync()
|
||||||
{
|
{
|
||||||
await _menuRefreshSemaphore.WaitAsync().ConfigureAwait(false);
|
await _menuRefreshSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
|||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.ViewModels.Data;
|
using Wino.Core.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
@@ -22,13 +23,26 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
|
|
||||||
public List<IProviderDetail> Providers { get; private set; } = [];
|
public List<IProviderDetail> Providers { get; private set; } = [];
|
||||||
public List<AppColorViewModel> AvailableColors { get; private set; } = [];
|
public List<AppColorViewModel> AvailableColors { get; private set; } = [];
|
||||||
|
public List<InitialSynchronizationRangeOption> InitialSynchronizationRanges { get; } =
|
||||||
|
[
|
||||||
|
new(InitialSynchronizationRange.ThreeMonths, Translator.AccountCreation_InitialSynchronization_3Months),
|
||||||
|
new(InitialSynchronizationRange.SixMonths, Translator.AccountCreation_InitialSynchronization_6Months),
|
||||||
|
new(InitialSynchronizationRange.NineMonths, Translator.AccountCreation_InitialSynchronization_9Months),
|
||||||
|
new(InitialSynchronizationRange.OneYear, Translator.AccountCreation_InitialSynchronization_Year),
|
||||||
|
new(InitialSynchronizationRange.Everything, Translator.AccountCreation_InitialSynchronization_Everything)
|
||||||
|
];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial IProviderDetail SelectedProvider { get; set; }
|
public partial IProviderDetail SelectedProvider { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsColorSelected))]
|
||||||
public partial AppColorViewModel SelectedColor { get; set; }
|
public partial AppColorViewModel SelectedColor { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsInitialSynchronizationWarningVisible))]
|
||||||
|
public partial InitialSynchronizationRangeOption SelectedInitialSynchronizationRange { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string AccountName { get; set; }
|
public partial string AccountName { get; set; }
|
||||||
|
|
||||||
@@ -36,6 +50,7 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
public partial bool CanProceed { get; set; }
|
public partial bool CanProceed { get; set; }
|
||||||
|
|
||||||
public bool IsColorSelected => SelectedColor != null;
|
public bool IsColorSelected => SelectedColor != null;
|
||||||
|
public bool IsInitialSynchronizationWarningVisible => SelectedInitialSynchronizationRange?.IsEverything == true;
|
||||||
|
|
||||||
public ProviderSelectionPageViewModel(
|
public ProviderSelectionPageViewModel(
|
||||||
IProviderService providerService,
|
IProviderService providerService,
|
||||||
@@ -45,6 +60,7 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
_providerService = providerService;
|
_providerService = providerService;
|
||||||
_themeService = themeService;
|
_themeService = themeService;
|
||||||
WizardContext = wizardContext;
|
WizardContext = wizardContext;
|
||||||
|
SelectedInitialSynchronizationRange = InitialSynchronizationRanges.First(option => option.Range == InitialSynchronizationRange.SixMonths);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
@@ -56,6 +72,10 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
.Select(hex => new AppColorViewModel(hex))
|
.Select(hex => new AppColorViewModel(hex))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
SelectedInitialSynchronizationRange = InitialSynchronizationRanges
|
||||||
|
.FirstOrDefault(option => option.Range == WizardContext.SelectedInitialSynchronizationRange)
|
||||||
|
?? InitialSynchronizationRanges.First(option => option.Range == InitialSynchronizationRange.SixMonths);
|
||||||
|
|
||||||
// Restore from wizard context if navigating back
|
// Restore from wizard context if navigating back
|
||||||
if (WizardContext.SelectedProvider != null)
|
if (WizardContext.SelectedProvider != null)
|
||||||
{
|
{
|
||||||
@@ -71,9 +91,12 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
Validate();
|
Validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedProviderChanged(IProviderDetail value) => Validate();
|
partial void OnSelectedProviderChanged(IProviderDetail value)
|
||||||
|
{
|
||||||
|
Validate();
|
||||||
|
}
|
||||||
|
|
||||||
partial void OnAccountNameChanged(string value) => Validate();
|
partial void OnAccountNameChanged(string value) => Validate();
|
||||||
partial void OnSelectedColorChanged(AppColorViewModel value) => OnPropertyChanged(nameof(IsColorSelected));
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void ClearColor() => SelectedColor = null;
|
private void ClearColor() => SelectedColor = null;
|
||||||
@@ -92,6 +115,7 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
WizardContext.SelectedProvider = SelectedProvider;
|
WizardContext.SelectedProvider = SelectedProvider;
|
||||||
WizardContext.AccountName = AccountName?.Trim();
|
WizardContext.AccountName = AccountName?.Trim();
|
||||||
WizardContext.AccountColorHex = SelectedColor?.Hex ?? string.Empty;
|
WizardContext.AccountColorHex = SelectedColor?.Hex ?? string.Empty;
|
||||||
|
WizardContext.SelectedInitialSynchronizationRange = SelectedInitialSynchronizationRange?.Range ?? InitialSynchronizationRange.SixMonths;
|
||||||
|
|
||||||
if (WizardContext.IsGenericImap)
|
if (WizardContext.IsGenericImap)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Microsoft.Windows.AppNotifications;
|
using System;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
@@ -49,8 +49,31 @@ internal static class ToastActivationResolver
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryResolveMode(NotificationArguments toastArguments, out WinoApplicationMode mode)
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
|
||||||
|
if (!toastArguments.TryGetValue(Constants.ToastModeKey, out string toastMode))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (string.Equals(toastMode, Constants.ToastModeCalendar, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Calendar;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(toastMode, Constants.ToastModeMail, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool ContainsKnownToastKey(NotificationArguments toastArguments)
|
private static bool ContainsKnownToastKey(NotificationArguments toastArguments)
|
||||||
=> toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string _) ||
|
=> toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string _) ||
|
||||||
|
toastArguments.TryGetValue(Constants.ToastModeKey, out string _) ||
|
||||||
toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string _) ||
|
toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string _) ||
|
||||||
toastArguments.TryGetValue(Constants.ToastActionKey, out string _);
|
toastArguments.TryGetValue(Constants.ToastActionKey, out string _);
|
||||||
}
|
}
|
||||||
|
|||||||
+225
-42
@@ -4,8 +4,8 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.UI.Dispatching;
|
using Microsoft.UI.Dispatching;
|
||||||
@@ -15,6 +15,8 @@ using Microsoft.Windows.AppLifecycle;
|
|||||||
using Microsoft.Windows.AppNotifications;
|
using Microsoft.Windows.AppNotifications;
|
||||||
using MimeKit.Cryptography;
|
using MimeKit.Cryptography;
|
||||||
using Windows.ApplicationModel.Activation;
|
using Windows.ApplicationModel.Activation;
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
|
using Windows.Storage;
|
||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Calendar.ViewModels.Interfaces;
|
using Wino.Calendar.ViewModels.Interfaces;
|
||||||
using Wino.Core;
|
using Wino.Core;
|
||||||
@@ -22,6 +24,8 @@ using Wino.Core.Domain;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
using Wino.Core.Domain.Models.Common;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
using Wino.Core.Domain.Models.MailItem;
|
using Wino.Core.Domain.Models.MailItem;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
@@ -30,6 +34,7 @@ using Wino.Mail.Services;
|
|||||||
using Wino.Mail.ViewModels;
|
using Wino.Mail.ViewModels;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Mail.WinUI.Activation;
|
using Wino.Mail.WinUI.Activation;
|
||||||
|
using Wino.Mail.WinUI.Extensions;
|
||||||
using Wino.Mail.WinUI.Interfaces;
|
using Wino.Mail.WinUI.Interfaces;
|
||||||
using Wino.Mail.WinUI.Models;
|
using Wino.Mail.WinUI.Models;
|
||||||
using Wino.Mail.WinUI.Services;
|
using Wino.Mail.WinUI.Services;
|
||||||
@@ -61,6 +66,7 @@ public partial class App : WinoApplication,
|
|||||||
private bool _isExiting;
|
private bool _isExiting;
|
||||||
private bool _activationInfrastructureInitialized;
|
private bool _activationInfrastructureInitialized;
|
||||||
private int _initialNotificationActivationHandled;
|
private int _initialNotificationActivationHandled;
|
||||||
|
private int _initialShareActivationHandled;
|
||||||
private CancellationTokenSource? _autoSynchronizationLoopCts;
|
private CancellationTokenSource? _autoSynchronizationLoopCts;
|
||||||
private readonly SemaphoreSlim _autoSynchronizationSemaphore = new(1, 1);
|
private readonly SemaphoreSlim _autoSynchronizationSemaphore = new(1, 1);
|
||||||
private readonly SemaphoreSlim _activationInfrastructureSemaphore = new(1, 1);
|
private readonly SemaphoreSlim _activationInfrastructureSemaphore = new(1, 1);
|
||||||
@@ -446,12 +452,26 @@ public partial class App : WinoApplication,
|
|||||||
private bool TryMarkInitialNotificationActivationHandled()
|
private bool TryMarkInitialNotificationActivationHandled()
|
||||||
=> Interlocked.Exchange(ref _initialNotificationActivationHandled, 1) == 0;
|
=> Interlocked.Exchange(ref _initialNotificationActivationHandled, 1) == 0;
|
||||||
|
|
||||||
|
private bool TryMarkInitialShareActivationHandled()
|
||||||
|
=> Interlocked.Exchange(ref _initialShareActivationHandled, 1) == 0;
|
||||||
|
|
||||||
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
base.OnLaunched(args);
|
base.OnLaunched(args);
|
||||||
|
|
||||||
await EnsureActivationInfrastructureAsync();
|
await EnsureActivationInfrastructureAsync();
|
||||||
|
|
||||||
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.ShareTarget &&
|
||||||
|
TryMarkInitialShareActivationHandled())
|
||||||
|
{
|
||||||
|
LogActivation("Processing share target activation from OnLaunched.");
|
||||||
|
|
||||||
|
if (await HandleShareTargetActivationAsync(activationArgs, activateWindow: true))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var hasAnyAccount = _hasConfiguredAccounts;
|
var hasAnyAccount = _hasConfiguredAccounts;
|
||||||
if (!IsStartupTaskLaunch() && !hasAnyAccount)
|
if (!IsStartupTaskLaunch() && !hasAnyAccount)
|
||||||
{
|
{
|
||||||
@@ -522,23 +542,20 @@ public partial class App : WinoApplication,
|
|||||||
{
|
{
|
||||||
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
|
|
||||||
if (activationArgs.Kind != ExtendedActivationKind.AppNotification ||
|
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
||||||
activationArgs.Data is not AppNotificationActivatedEventArgs toastArgs ||
|
activationArgs.Data is ILaunchActivatedEventArgs launchArgs &&
|
||||||
!TryMarkInitialNotificationActivationHandled())
|
ToastActivationResolver.TryParse(launchArgs.Arguments, out var launchToastArguments) &&
|
||||||
|
TryMarkInitialNotificationActivationHandled())
|
||||||
{
|
{
|
||||||
return;
|
LogActivation($"Processing initial toast launch activation from application startup. Arguments: {launchArgs.Arguments}");
|
||||||
|
|
||||||
|
await EnsureActivationInfrastructureAsync();
|
||||||
|
await HandleToastActivationAsync(launchToastArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogActivation($"Processing initial notification activation from application startup. Arguments: {toastArgs.Argument}");
|
|
||||||
|
|
||||||
await EnsureActivationInfrastructureAsync();
|
|
||||||
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
private void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
// AppNotification callbacks are not guaranteed to run on the UI thread.
|
|
||||||
// Marshal toast handling to the window dispatcher before touching window APIs.
|
|
||||||
if (MainWindow?.DispatcherQueue?.TryEnqueue(() => _ = HandleToastActivationAsync(args.Argument, args.UserInput)) == true)
|
if (MainWindow?.DispatcherQueue?.TryEnqueue(() => _ = HandleToastActivationAsync(args.Argument, args.UserInput)) == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -548,19 +565,7 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
private void TryRegisterAppNotifications()
|
private void TryRegisterAppNotifications()
|
||||||
{
|
{
|
||||||
var notificationManager = AppNotificationManager.Default;
|
// Classic targeted toasts use normal launch activation instead of COM toast activators.
|
||||||
|
|
||||||
notificationManager.NotificationInvoked -= AppNotificationInvoked;
|
|
||||||
notificationManager.NotificationInvoked += AppNotificationInvoked;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
notificationManager.Register();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogActivation($"App notification registration failed: {ex.GetType().Name} - {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -590,7 +595,7 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
if (calendarAction == Constants.ToastCalendarSnoozeAction)
|
if (calendarAction == Constants.ToastCalendarSnoozeAction)
|
||||||
{
|
{
|
||||||
await HandleCalendarToastSnoozeAsync(userInput, calendarItemId);
|
await HandleCalendarToastSnoozeAsync(toastArguments, userInput, calendarItemId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,6 +640,109 @@ public partial class App : WinoApplication,
|
|||||||
return HandleToastActivationAsync(toastArguments, userInput);
|
return HandleToastActivationAsync(toastArguments, userInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int? GetToastSnoozeDurationMinutes(NotificationArguments toastArguments, IDictionary<string, string>? userInput)
|
||||||
|
{
|
||||||
|
if (toastArguments.TryGetValue(Constants.ToastCalendarSnoozeDurationMinutesKey, out var snoozeDurationValue) &&
|
||||||
|
int.TryParse(snoozeDurationValue, out var snoozeDurationMinutes) &&
|
||||||
|
snoozeDurationMinutes > 0)
|
||||||
|
{
|
||||||
|
return snoozeDurationMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userInput != null &&
|
||||||
|
userInput.TryGetValue(Constants.ToastCalendarSnoozeDurationInputId, out var selectedValue) &&
|
||||||
|
int.TryParse(selectedValue, out snoozeDurationMinutes) &&
|
||||||
|
snoozeDurationMinutes > 0)
|
||||||
|
{
|
||||||
|
return snoozeDurationMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> HandleShareTargetActivationAsync(AppActivationArguments activationArgs, bool activateWindow)
|
||||||
|
{
|
||||||
|
if (activationArgs.Kind != ExtendedActivationKind.ShareTarget ||
|
||||||
|
activationArgs.Data is not ShareTargetActivatedEventArgs shareTargetArgs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shareRequest = await ExtractMailShareRequestAsync(shareTargetArgs);
|
||||||
|
|
||||||
|
if (shareRequest?.Files == null || shareRequest.Files.Count == 0)
|
||||||
|
{
|
||||||
|
Services.GetRequiredService<IShareActivationService>().ClearPendingShareRequest();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shareActivationService = Services.GetRequiredService<IShareActivationService>();
|
||||||
|
shareActivationService.PendingShareRequest = shareRequest;
|
||||||
|
|
||||||
|
if (!_hasConfiguredAccounts)
|
||||||
|
{
|
||||||
|
shareActivationService.ClearPendingShareRequest();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shellWindowAlreadyExists = HasShellWindow();
|
||||||
|
|
||||||
|
await EnsureShellWindowAsync(WinoApplicationMode.Mail, activateWindow, suppressStartupFlows: true);
|
||||||
|
|
||||||
|
if (shellWindowAlreadyExists)
|
||||||
|
{
|
||||||
|
await Services.GetRequiredService<MailAppShellViewModel>().HandlePendingShareRequestAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MailShareRequest?> ExtractMailShareRequestAsync(ShareTargetActivatedEventArgs shareTargetArgs)
|
||||||
|
{
|
||||||
|
var shareOperation = shareTargetArgs.ShareOperation;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shareOperation.ReportStarted();
|
||||||
|
|
||||||
|
if (!shareOperation.Data.Contains(StandardDataFormats.StorageItems))
|
||||||
|
{
|
||||||
|
shareOperation.ReportCompleted();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var storageItems = await shareOperation.Data.GetStorageItemsAsync();
|
||||||
|
List<SharedFile> sharedFiles = [];
|
||||||
|
|
||||||
|
foreach (var storageFile in storageItems.OfType<StorageFile>())
|
||||||
|
{
|
||||||
|
sharedFiles.Add(await storageFile.ToSharedFileAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
shareOperation.ReportDataRetrieved();
|
||||||
|
shareOperation.ReportCompleted();
|
||||||
|
|
||||||
|
return sharedFiles.Count == 0
|
||||||
|
? null
|
||||||
|
: new MailShareRequest(sharedFiles);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogActivation($"Failed to extract share target payload: {ex.GetType().Name} - {ex.Message}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
shareOperation.ReportError(ex.Message);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore share reporting failures and fall back to normal launch flow.
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<IWinoShellWindow?> EnsureShellWindowAsync(WinoApplicationMode mode, bool activateWindow, bool suppressStartupFlows = true)
|
private async Task<IWinoShellWindow?> EnsureShellWindowAsync(WinoApplicationMode mode, bool activateWindow, bool suppressStartupFlows = true)
|
||||||
{
|
{
|
||||||
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
||||||
@@ -706,9 +814,59 @@ public partial class App : WinoApplication,
|
|||||||
navigationService.Navigate(WinoPage.EventDetailsPage, target);
|
navigationService.Navigate(WinoPage.EventDetailsPage, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleCalendarToastSnoozeAsync(IDictionary<string, string>? userInput, Guid calendarItemId)
|
private async Task<object?> TryCreateToastNavigationParameterAsync(NotificationArguments toastArguments)
|
||||||
{
|
{
|
||||||
if (!TryGetSnoozeDurationMinutes(userInput, out var snoozeDurationMinutes))
|
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
|
||||||
|
calendarAction == Constants.ToastCalendarNavigateAction &&
|
||||||
|
toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) &&
|
||||||
|
Guid.TryParse(calendarItemIdString, out Guid calendarItemId))
|
||||||
|
{
|
||||||
|
var calendarService = Services.GetRequiredService<ICalendarService>();
|
||||||
|
var calendarItem = await calendarService.GetCalendarItemAsync(calendarItemId);
|
||||||
|
|
||||||
|
if (calendarItem != null)
|
||||||
|
{
|
||||||
|
return new CalendarItemTarget(calendarItem, CalendarEventTargetType.Single);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(WinoApplicationMode Mode, object? Parameter)?> TryResolveToastActivationTargetAsync(AppActivationArguments activationArgs)
|
||||||
|
{
|
||||||
|
NotificationArguments? toastArguments = null;
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
||||||
|
activationArgs.Data is ILaunchActivatedEventArgs launchArgs &&
|
||||||
|
ToastActivationResolver.TryParse(launchArgs.Arguments, out var launchToastArguments))
|
||||||
|
{
|
||||||
|
toastArguments = launchToastArguments;
|
||||||
|
}
|
||||||
|
else if (activationArgs.Kind == ExtendedActivationKind.AppNotification &&
|
||||||
|
activationArgs.Data is AppNotificationActivatedEventArgs appNotificationArgs &&
|
||||||
|
ToastActivationResolver.TryParse(appNotificationArgs.Argument, out var appNotificationToastArguments))
|
||||||
|
{
|
||||||
|
toastArguments = appNotificationToastArguments;
|
||||||
|
}
|
||||||
|
else if (activationArgs.Data is ToastNotificationActivatedEventArgs classicToastArgs &&
|
||||||
|
ToastActivationResolver.TryParse(classicToastArgs.Argument, out var classicToastArguments))
|
||||||
|
{
|
||||||
|
toastArguments = classicToastArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastArguments == null ||
|
||||||
|
!ToastActivationResolver.TryResolveMode(toastArguments, out var mode))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mode, await TryCreateToastNavigationParameterAsync(toastArguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleCalendarToastSnoozeAsync(NotificationArguments toastArguments, IDictionary<string, string>? userInput, Guid calendarItemId)
|
||||||
|
{
|
||||||
|
if (!TryGetSnoozeDurationMinutes(toastArguments, userInput, out var snoozeDurationMinutes))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var calendarService = Services.GetRequiredService<ICalendarService>();
|
var calendarService = Services.GetRequiredService<ICalendarService>();
|
||||||
@@ -732,20 +890,13 @@ public partial class App : WinoApplication,
|
|||||||
await nativeAppService.LaunchUriAsync(joinUri);
|
await nativeAppService.LaunchUriAsync(joinUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetSnoozeDurationMinutes(IDictionary<string, string>? userInput, out int snoozeDurationMinutes)
|
private bool TryGetSnoozeDurationMinutes(NotificationArguments toastArguments, IDictionary<string, string>? userInput, out int snoozeDurationMinutes)
|
||||||
{
|
{
|
||||||
snoozeDurationMinutes = _preferencesService?.DefaultSnoozeDurationInMinutes ?? 0;
|
snoozeDurationMinutes = GetToastSnoozeDurationMinutes(toastArguments, userInput)
|
||||||
|
?? _preferencesService?.DefaultSnoozeDurationInMinutes
|
||||||
|
?? 0;
|
||||||
|
|
||||||
if (userInput == null ||
|
return snoozeDurationMinutes > 0;
|
||||||
!userInput.TryGetValue(Constants.ToastCalendarSnoozeDurationInputId, out var selectedValue) ||
|
|
||||||
selectedValue == null)
|
|
||||||
{
|
|
||||||
return snoozeDurationMinutes > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedText = selectedValue.ToString();
|
|
||||||
|
|
||||||
return int.TryParse(selectedText, out snoozeDurationMinutes) && snoozeDurationMinutes > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1446,6 +1597,11 @@ public partial class App : WinoApplication,
|
|||||||
LogActivation($"Processing redirected notification activation. Arguments: {toastArgs.Argument}");
|
LogActivation($"Processing redirected notification activation. Arguments: {toastArgs.Argument}");
|
||||||
_ = HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
_ = HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
||||||
}
|
}
|
||||||
|
else if (args.Kind == ExtendedActivationKind.ShareTarget)
|
||||||
|
{
|
||||||
|
LogActivation("Processing redirected share target activation.");
|
||||||
|
await HandleShareTargetActivationAsync(args, activateWindow: true);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var shouldActivateWindow = true;
|
var shouldActivateWindow = true;
|
||||||
@@ -1475,7 +1631,22 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
else if (TryResolveActivationMode(args, _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail, out var redirectedMode))
|
else if (TryResolveActivationMode(args, _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail, out var redirectedMode))
|
||||||
{
|
{
|
||||||
shellWindow.HandleAppActivation(AppEntryConstants.GetModeLaunchArgument(redirectedMode));
|
var navigationService = Services.GetRequiredService<INavigationService>();
|
||||||
|
var toastActivationTarget = await TryResolveToastActivationTargetAsync(args);
|
||||||
|
|
||||||
|
if (toastActivationTarget is { Parameter: CalendarItemTarget calendarTarget })
|
||||||
|
{
|
||||||
|
navigationService.ChangeApplicationMode(toastActivationTarget.Value.Mode, new ShellModeActivationContext
|
||||||
|
{
|
||||||
|
SuppressStartupFlows = true,
|
||||||
|
Parameter = calendarTarget
|
||||||
|
});
|
||||||
|
navigationService.Navigate(WinoPage.EventDetailsPage, calendarTarget);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shellWindow.HandleAppActivation(AppEntryConstants.GetModeLaunchArgument(redirectedMode));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1488,7 +1659,6 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch to UI thread since this is called from Program.OnActivated.
|
|
||||||
if (MainWindow?.DispatcherQueue.TryEnqueue(() => _ = HandleRedirectedActivationAsync()) == true)
|
if (MainWindow?.DispatcherQueue.TryEnqueue(() => _ = HandleRedirectedActivationAsync()) == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -1527,6 +1697,19 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.ShareTarget)
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activationArgs.Data is ToastNotificationActivatedEventArgs classicToastArgs &&
|
||||||
|
ToastActivationResolver.TryParse(classicToastArgs.Argument, out var classicToastArguments) &&
|
||||||
|
ToastActivationResolver.TryResolveMode(classicToastArguments, out mode))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (activationArgs.Kind == ExtendedActivationKind.File &&
|
if (activationArgs.Kind == ExtendedActivationKind.File &&
|
||||||
activationArgs.Data is IFileActivatedEventArgs fileArgs)
|
activationArgs.Data is IFileActivatedEventArgs fileArgs)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
<Grid MinWidth="400" RowSpacing="12">
|
<Grid MinWidth="400" RowSpacing="12">
|
||||||
<Grid Visibility="{x:Bind IsProviderSelectionVisible, Mode=OneWay}">
|
<Grid Visibility="{x:Bind IsProviderSelectionVisible, Mode=OneWay}">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
@@ -96,6 +97,48 @@
|
|||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
x:Name="InitialSynchronizationPanel"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,12,0,0"
|
||||||
|
Padding="12"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.AccountCreation_InitialSynchronization_Title}" />
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind domain:Translator.AccountCreation_InitialSynchronization_Description}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ComboBox
|
||||||
|
x:Name="InitialSynchronizationComboBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
ItemsSource="{x:Bind InitialSynchronizationRanges, Mode=OneWay}"
|
||||||
|
SelectionChanged="InitialSynchronizationSelectionChanged">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="accounts:InitialSynchronizationRangeOption">
|
||||||
|
<TextBlock Text="{x:Bind DisplayText}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<muxc:InfoBar
|
||||||
|
x:Name="InitialSynchronizationWarningBar"
|
||||||
|
IsOpen="True"
|
||||||
|
Message="{x:Bind domain:Translator.AccountCreation_InitialSynchronization_EverythingWarning}"
|
||||||
|
Severity="Warning"
|
||||||
|
Title="{x:Bind domain:Translator.GeneralTitle_Warning}"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Wino.Mail.WinUI.Dialogs;
|
|||||||
|
|
||||||
public sealed partial class NewAccountDialog : ContentDialog
|
public sealed partial class NewAccountDialog : ContentDialog
|
||||||
{
|
{
|
||||||
private readonly Dictionary<SpecialImapProvider, string> helpingLinks = new Dictionary<SpecialImapProvider, string>()
|
private readonly Dictionary<SpecialImapProvider, string> helpingLinks = new()
|
||||||
{
|
{
|
||||||
{ SpecialImapProvider.iCloud, "https://support.apple.com/en-us/102654" },
|
{ SpecialImapProvider.iCloud, "https://support.apple.com/en-us/102654" },
|
||||||
{ SpecialImapProvider.Yahoo, "http://help.yahoo.com/kb/SLN15241.html" },
|
{ SpecialImapProvider.Yahoo, "http://help.yahoo.com/kb/SLN15241.html" },
|
||||||
@@ -27,7 +27,6 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(AppColorViewModel), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedColorChanged)));
|
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(AppColorViewModel), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedColorChanged)));
|
||||||
public static readonly DependencyProperty SelectedCalendarModeIndexProperty = DependencyProperty.Register(nameof(SelectedCalendarModeIndex), typeof(int), typeof(NewAccountDialog), new PropertyMetadata(0));
|
public static readonly DependencyProperty SelectedCalendarModeIndexProperty = DependencyProperty.Register(nameof(SelectedCalendarModeIndex), typeof(int), typeof(NewAccountDialog), new PropertyMetadata(0));
|
||||||
|
|
||||||
|
|
||||||
public AppColorViewModel? SelectedColor
|
public AppColorViewModel? SelectedColor
|
||||||
{
|
{
|
||||||
get { return (AppColorViewModel?)GetValue(SelectedColorProperty); }
|
get { return (AppColorViewModel?)GetValue(SelectedColorProperty); }
|
||||||
@@ -49,7 +48,6 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
set { SetValue(SelectedMailProviderProperty, value); }
|
set { SetValue(SelectedMailProviderProperty, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool IsProviderSelectionVisible
|
public bool IsProviderSelectionVisible
|
||||||
{
|
{
|
||||||
get { return (bool)GetValue(IsProviderSelectionVisibleProperty); }
|
get { return (bool)GetValue(IsProviderSelectionVisibleProperty); }
|
||||||
@@ -63,10 +61,16 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List of available mail providers for now.
|
// List of available mail providers for now.
|
||||||
|
|
||||||
public List<IProviderDetail> Providers { get; set; } = [];
|
public List<IProviderDetail> Providers { get; set; } = [];
|
||||||
|
|
||||||
public List<AppColorViewModel> AvailableColors { get; set; } = [];
|
public List<AppColorViewModel> AvailableColors { get; set; } = [];
|
||||||
|
public List<InitialSynchronizationRangeOption> InitialSynchronizationRanges { get; } =
|
||||||
|
[
|
||||||
|
new(InitialSynchronizationRange.ThreeMonths, Translator.AccountCreation_InitialSynchronization_3Months),
|
||||||
|
new(InitialSynchronizationRange.SixMonths, Translator.AccountCreation_InitialSynchronization_6Months),
|
||||||
|
new(InitialSynchronizationRange.NineMonths, Translator.AccountCreation_InitialSynchronization_9Months),
|
||||||
|
new(InitialSynchronizationRange.OneYear, Translator.AccountCreation_InitialSynchronization_Year),
|
||||||
|
new(InitialSynchronizationRange.Everything, Translator.AccountCreation_InitialSynchronization_Everything)
|
||||||
|
];
|
||||||
public List<string> CalendarModeOptions { get; } =
|
public List<string> CalendarModeOptions { get; } =
|
||||||
[
|
[
|
||||||
Translator.ImapCalDavSettingsPage_CalendarModeCalDav,
|
Translator.ImapCalDavSettingsPage_CalendarModeCalDav,
|
||||||
@@ -74,7 +78,6 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
Translator.ImapCalDavSettingsPage_CalendarModeDisabled
|
Translator.ImapCalDavSettingsPage_CalendarModeDisabled
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
public AccountCreationDialogResult? Result = null;
|
public AccountCreationDialogResult? Result = null;
|
||||||
|
|
||||||
public NewAccountDialog()
|
public NewAccountDialog()
|
||||||
@@ -85,6 +88,8 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
AvailableColors = themeService.Select(a => new AppColorViewModel(a)).ToList();
|
AvailableColors = themeService.Select(a => new AppColorViewModel(a)).ToList();
|
||||||
|
|
||||||
UpdateSelectedColor();
|
UpdateSelectedColor();
|
||||||
|
InitialSynchronizationComboBox.SelectedItem = InitialSynchronizationRanges.First(option => option.Range == InitialSynchronizationRange.SixMonths);
|
||||||
|
UpdateInitialSynchronizationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnSelectedProviderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
private static void OnSelectedProviderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
||||||
@@ -105,6 +110,19 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
SelectedColorEllipse.Fill = SelectedColor == null ? null : XamlHelpers.GetSolidColorBrushFromHex(SelectedColor.Hex);
|
SelectedColorEllipse.Fill = SelectedColor == null ? null : XamlHelpers.GetSolidColorBrushFromHex(SelectedColor.Hex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateInitialSynchronizationState()
|
||||||
|
{
|
||||||
|
InitialSynchronizationPanel.Visibility = SelectedMailProvider == null ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
var selectedOption = InitialSynchronizationComboBox.SelectedItem as InitialSynchronizationRangeOption;
|
||||||
|
InitialSynchronizationWarningBar.Visibility = selectedOption?.IsEverything == true ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InitialSynchronizationRange GetInitialSynchronizationRange()
|
||||||
|
{
|
||||||
|
var selectedRange = (InitialSynchronizationComboBox.SelectedItem as InitialSynchronizationRangeOption)?.Range
|
||||||
|
?? InitialSynchronizationRange.SixMonths;
|
||||||
|
return selectedRange;
|
||||||
|
}
|
||||||
|
|
||||||
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
private void CancelClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
{
|
{
|
||||||
@@ -116,9 +134,11 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
if (SelectedMailProvider == null)
|
if (SelectedMailProvider == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var initialSynchronizationRange = GetInitialSynchronizationRange();
|
||||||
|
|
||||||
if (IsSpecialImapServerPartVisible)
|
if (IsSpecialImapServerPartVisible)
|
||||||
{
|
{
|
||||||
// Special imap detail input.
|
// Special IMAP detail input.
|
||||||
var calendarSupportMode = SelectedCalendarModeIndex switch
|
var calendarSupportMode = SelectedCalendarModeIndex switch
|
||||||
{
|
{
|
||||||
1 => ImapCalendarSupportMode.LocalOnly,
|
1 => ImapCalendarSupportMode.LocalOnly,
|
||||||
@@ -132,7 +152,12 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
DisplayNameTextBox.Text.Trim(),
|
DisplayNameTextBox.Text.Trim(),
|
||||||
SelectedMailProvider.SpecialImapProvider,
|
SelectedMailProvider.SpecialImapProvider,
|
||||||
calendarSupportMode);
|
calendarSupportMode);
|
||||||
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), details, SelectedColor?.Hex ?? string.Empty);
|
Result = new AccountCreationDialogResult(
|
||||||
|
SelectedMailProvider.Type,
|
||||||
|
AccountNameTextbox.Text.Trim(),
|
||||||
|
details,
|
||||||
|
SelectedColor?.Hex ?? string.Empty,
|
||||||
|
initialSynchronizationRange);
|
||||||
Hide();
|
Hide();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -140,11 +165,11 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
|
|
||||||
Validate();
|
Validate();
|
||||||
|
|
||||||
if (IsSecondaryButtonEnabled)
|
if (IsPrimaryButtonEnabled)
|
||||||
{
|
{
|
||||||
if (SelectedMailProvider.SpecialImapProvider != SpecialImapProvider.None)
|
if (SelectedMailProvider.SpecialImapProvider != SpecialImapProvider.None)
|
||||||
{
|
{
|
||||||
// This step requires app-sepcific password login for some providers.
|
// This step requires app-specific password login for some providers.
|
||||||
args.Cancel = true;
|
args.Cancel = true;
|
||||||
|
|
||||||
IsProviderSelectionVisible = false;
|
IsProviderSelectionVisible = false;
|
||||||
@@ -154,7 +179,12 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), null, SelectedColor?.Hex ?? string.Empty);
|
Result = new AccountCreationDialogResult(
|
||||||
|
SelectedMailProvider.Type,
|
||||||
|
AccountNameTextbox.Text.Trim(),
|
||||||
|
null,
|
||||||
|
SelectedColor?.Hex ?? string.Empty,
|
||||||
|
initialSynchronizationRange);
|
||||||
Hide();
|
Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,6 +197,7 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
{
|
{
|
||||||
ValidateCreateButton();
|
ValidateCreateButton();
|
||||||
ValidateNames();
|
ValidateNames();
|
||||||
|
UpdateInitialSynchronizationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether we can create account or not.
|
// Returns whether we can create account or not.
|
||||||
@@ -199,6 +230,9 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
|
|
||||||
private void ImapPasswordChanged(object sender, RoutedEventArgs e) => Validate();
|
private void ImapPasswordChanged(object sender, RoutedEventArgs e) => Validate();
|
||||||
|
|
||||||
|
private void InitialSynchronizationSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
=> UpdateInitialSynchronizationState();
|
||||||
|
|
||||||
private async void AppSpecificHelpButtonClicked(object sender, RoutedEventArgs e)
|
private async void AppSpecificHelpButtonClicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (SelectedMailProvider == null ||
|
if (SelectedMailProvider == null ||
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||||
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
|
||||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
|
||||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
IgnorableNamespaces="uap uap10 rescap com desktop">
|
IgnorableNamespaces="uap uap10 rescap">
|
||||||
|
|
||||||
<!-- Publisher Cache Folders -->
|
<!-- Publisher Cache Folders -->
|
||||||
<Extensions>
|
<Extensions>
|
||||||
@@ -23,7 +21,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="58272BurakKSE.WinoMailPreview"
|
Name="58272BurakKSE.WinoMailPreview"
|
||||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||||
Version="2.0.0.0" />
|
Version="2.0.1.0" />
|
||||||
|
|
||||||
<mp:PhoneIdentity PhoneProductId="3879fcfb-a561-4599-9103-e0c9b35a271f" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
<mp:PhoneIdentity PhoneProductId="3879fcfb-a561-4599-9103-e0c9b35a271f" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
@@ -47,16 +45,15 @@
|
|||||||
Executable="$targetnametoken$.exe"
|
Executable="$targetnametoken$.exe"
|
||||||
EntryPoint="$targetentrypoint$"
|
EntryPoint="$targetentrypoint$"
|
||||||
uap10:Parameters="--wino-mail">
|
uap10:Parameters="--wino-mail">
|
||||||
<uap:VisualElements
|
<uap:VisualElements
|
||||||
DisplayName="Wino Mail"
|
DisplayName="Wino Mail"
|
||||||
Description="Wino.Mail.WinUI"
|
Description="Wino Mail"
|
||||||
BackgroundColor="transparent"
|
BackgroundColor="transparent"
|
||||||
Square150x150Logo="Assets\AppEntries\MailAssets\Square150x150Logo.png"
|
Square150x150Logo="Assets\AppEntries\MailAssets\Square150x150Logo.png"
|
||||||
Square44x44Logo="Assets\AppEntries\MailAssets\Square44x44Logo.png">
|
Square44x44Logo="Assets\AppEntries\MailAssets\Square44x44Logo.png">
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\AppEntries\MailAssets\Wide310x150Logo.png" Square71x71Logo="Assets\AppEntries\MailAssets\SmallTile.png" Square310x310Logo="Assets\AppEntries\MailAssets\LargeTile.png"/>
|
<uap:DefaultTile Wide310x150Logo="Assets\AppEntries\MailAssets\Wide310x150Logo.png" Square71x71Logo="Assets\AppEntries\MailAssets\SmallTile.png" Square310x310Logo="Assets\AppEntries\MailAssets\LargeTile.png"/>
|
||||||
<uap:SplashScreen Image="Assets\AppEntries\MailAssets\SplashScreen.png" />
|
<uap:SplashScreen Image="Assets\AppEntries\MailAssets\SplashScreen.png" />
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<uap5:Extension Category="windows.startupTask">
|
<uap5:Extension Category="windows.startupTask">
|
||||||
<uap5:StartupTask
|
<uap5:StartupTask
|
||||||
@@ -65,19 +62,6 @@
|
|||||||
DisplayName="Wino Startup Service" />
|
DisplayName="Wino Startup Service" />
|
||||||
</uap5:Extension>
|
</uap5:Extension>
|
||||||
|
|
||||||
<!-- App notification activation -->
|
|
||||||
<desktop:Extension Category="windows.toastNotificationActivation">
|
|
||||||
<desktop:ToastNotificationActivation ToastActivatorCLSID="72c6d2d0-2538-44fe-a1b1-499f47bb1181" />
|
|
||||||
</desktop:Extension>
|
|
||||||
|
|
||||||
<com:Extension Category="windows.comServer">
|
|
||||||
<com:ComServer>
|
|
||||||
<com:ExeServer Executable="Wino.Mail.WinUI.exe" Arguments="----AppNotificationActivated:" DisplayName="Toast activator">
|
|
||||||
<com:Class Id="72c6d2d0-2538-44fe-a1b1-499f47bb1181" DisplayName="Toast activator"/>
|
|
||||||
</com:ExeServer>
|
|
||||||
</com:ComServer>
|
|
||||||
</com:Extension>
|
|
||||||
|
|
||||||
<!-- Protocol activation: mailto -->
|
<!-- Protocol activation: mailto -->
|
||||||
<uap:Extension Category="windows.protocol">
|
<uap:Extension Category="windows.protocol">
|
||||||
<uap:Protocol Name="mailto" />
|
<uap:Protocol Name="mailto" />
|
||||||
@@ -97,6 +81,15 @@
|
|||||||
</uap:Protocol>
|
</uap:Protocol>
|
||||||
</uap:Extension>
|
</uap:Extension>
|
||||||
|
|
||||||
|
<!-- Share target activation -->
|
||||||
|
<uap:Extension Category="windows.shareTarget">
|
||||||
|
<uap:ShareTarget>
|
||||||
|
<uap:SupportedFileTypes>
|
||||||
|
<uap:SupportsAnyFileType />
|
||||||
|
</uap:SupportedFileTypes>
|
||||||
|
</uap:ShareTarget>
|
||||||
|
</uap:Extension>
|
||||||
|
|
||||||
<!-- File Assosication: EML -->
|
<!-- File Assosication: EML -->
|
||||||
<uap:Extension Category="windows.fileTypeAssociation">
|
<uap:Extension Category="windows.fileTypeAssociation">
|
||||||
<uap:FileTypeAssociation Name="eml">
|
<uap:FileTypeAssociation Name="eml">
|
||||||
@@ -109,55 +102,43 @@
|
|||||||
</Extensions>
|
</Extensions>
|
||||||
</Application>
|
</Application>
|
||||||
|
|
||||||
<Application Id="CalendarApp"
|
<Application Id="CalendarApp"
|
||||||
Executable="$targetnametoken$.exe"
|
Executable="$targetnametoken$.exe"
|
||||||
EntryPoint="$targetentrypoint$"
|
EntryPoint="$targetentrypoint$"
|
||||||
uap10:Parameters="--wino-calendar">
|
uap10:Parameters="--wino-calendar">
|
||||||
<uap:VisualElements
|
<uap:VisualElements
|
||||||
DisplayName="Wino Calendar"
|
DisplayName="Wino Calendar"
|
||||||
Description="Wino.Mail.WinUI.Calendar"
|
Description="Wino Calendar"
|
||||||
BackgroundColor="transparent"
|
BackgroundColor="transparent"
|
||||||
Square150x150Logo="Assets\AppEntries\CalendarAssets\Square150x150Logo.png"
|
Square150x150Logo="Assets\AppEntries\CalendarAssets\Square150x150Logo.png"
|
||||||
Square44x44Logo="Assets\AppEntries\CalendarAssets\Square44x44Logo.png">
|
Square44x44Logo="Assets\AppEntries\CalendarAssets\Square44x44Logo.png">
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\AppEntries\CalendarAssets\Wide310x150Logo.png" Square71x71Logo="Assets\AppEntries\CalendarAssets\SmallTile.png" Square310x310Logo="Assets\AppEntries\CalendarAssets\LargeTile.png"/>
|
<uap:DefaultTile Wide310x150Logo="Assets\AppEntries\CalendarAssets\Wide310x150Logo.png" Square71x71Logo="Assets\AppEntries\CalendarAssets\SmallTile.png" Square310x310Logo="Assets\AppEntries\CalendarAssets\LargeTile.png"/>
|
||||||
<uap:SplashScreen Image="Assets\AppEntries\CalendarAssets\SplashScreen.png" />
|
<uap:SplashScreen Image="Assets\AppEntries\CalendarAssets\SplashScreen.png" />
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<desktop:Extension Category="windows.toastNotificationActivation">
|
<uap:Extension Category="windows.protocol">
|
||||||
<desktop:ToastNotificationActivation ToastActivatorCLSID="44c05d2b-aa1d-4e59-9d7d-8b4c8607cb8d" />
|
<uap:Protocol Name="webcal">
|
||||||
</desktop:Extension>
|
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
|
||||||
|
</uap:Protocol>
|
||||||
|
</uap:Extension>
|
||||||
|
|
||||||
<com:Extension Category="windows.comServer">
|
<uap:Extension Category="windows.protocol">
|
||||||
<com:ComServer>
|
<uap:Protocol Name="webcals">
|
||||||
<com:ExeServer Executable="Wino.Mail.WinUI.exe" Arguments="----AppNotificationActivated:" DisplayName="Calendar toast activator">
|
<uap:DisplayName>Calendar Protocol (Secure)</uap:DisplayName>
|
||||||
<com:Class Id="44c05d2b-aa1d-4e59-9d7d-8b4c8607cb8d" DisplayName="Calendar toast activator"/>
|
</uap:Protocol>
|
||||||
</com:ExeServer>
|
</uap:Extension>
|
||||||
</com:ComServer>
|
|
||||||
</com:Extension>
|
|
||||||
|
|
||||||
<uap:Extension Category="windows.protocol">
|
<uap:Extension Category="windows.fileTypeAssociation">
|
||||||
<uap:Protocol Name="webcal">
|
<uap:FileTypeAssociation Name="ics">
|
||||||
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
|
<uap:Logo>Assets\AppEntries\CalendarAssets\Square44x44Logo.png</uap:Logo>
|
||||||
</uap:Protocol>
|
<uap:SupportedFileTypes>
|
||||||
</uap:Extension>
|
<uap:FileType>.ics</uap:FileType>
|
||||||
|
</uap:SupportedFileTypes>
|
||||||
<uap:Extension Category="windows.protocol">
|
</uap:FileTypeAssociation>
|
||||||
<uap:Protocol Name="webcals">
|
</uap:Extension>
|
||||||
<uap:DisplayName>Calendar Protocol (Secure)</uap:DisplayName>
|
</Extensions>
|
||||||
</uap:Protocol>
|
</Application>
|
||||||
</uap:Extension>
|
|
||||||
|
|
||||||
<uap:Extension Category="windows.fileTypeAssociation">
|
|
||||||
<uap:FileTypeAssociation Name="ics">
|
|
||||||
<uap:Logo>Assets\AppEntries\CalendarAssets\Square44x44Logo.png</uap:Logo>
|
|
||||||
<uap:SupportedFileTypes>
|
|
||||||
<uap:FileType>.ics</uap:FileType>
|
|
||||||
</uap:SupportedFileTypes>
|
|
||||||
</uap:FileTypeAssociation>
|
|
||||||
</uap:Extension>
|
|
||||||
</Extensions>
|
|
||||||
</Application>
|
|
||||||
</Applications>
|
</Applications>
|
||||||
|
|
||||||
<Capabilities>
|
<Capabilities>
|
||||||
|
|||||||
@@ -207,6 +207,13 @@ public class Program
|
|||||||
: true;
|
: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.Data is Windows.ApplicationModel.Activation.ToastNotificationActivatedEventArgs classicToastArgs)
|
||||||
|
{
|
||||||
|
return ToastActivationResolver.TryParse(classicToastArgs.Argument, out var toastArguments)
|
||||||
|
? ToastActivationResolver.ShouldBringToForeground(toastArguments)
|
||||||
|
: true;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.Kind == ExtendedActivationKind.Launch &&
|
if (args.Kind == ExtendedActivationKind.Launch &&
|
||||||
args.Data is Windows.ApplicationModel.Activation.ILaunchActivatedEventArgs launchArgs &&
|
args.Data is Windows.ApplicationModel.Activation.ILaunchActivatedEventArgs launchArgs &&
|
||||||
ToastActivationResolver.TryParse(launchArgs.Arguments, out var launchToastArguments))
|
ToastActivationResolver.TryParse(launchArgs.Arguments, out var launchToastArguments))
|
||||||
|
|||||||
@@ -587,6 +587,31 @@ public class NewThemeService : INewThemeService
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteCustomThemeAsync(Guid themeId)
|
||||||
|
{
|
||||||
|
var themeFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(CustomThemeFolderName, CreationCollisionOption.OpenIfExists);
|
||||||
|
var metadataFileName = $"{themeId}.json";
|
||||||
|
var themeItem = await themeFolder.TryGetItemAsync(metadataFileName);
|
||||||
|
|
||||||
|
if (themeItem == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentApplicationThemeId == themeId)
|
||||||
|
{
|
||||||
|
currentApplicationThemeId = preDefinedThemes[0].Id;
|
||||||
|
_configurationService.Set(CurrentApplicationThemeKey, currentApplicationThemeId);
|
||||||
|
await ApplyCustomThemeAsync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DeleteThemeAssetIfExistsAsync(themeFolder, metadataFileName);
|
||||||
|
await DeleteThemeAssetIfExistsAsync(themeFolder, $"{themeId}.jpg");
|
||||||
|
await DeleteThemeAssetIfExistsAsync(themeFolder, $"{themeId}_preview.jpg");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<CustomThemeMetadata?> GetCustomMetadataAsync(IStorageFile file)
|
private async Task<CustomThemeMetadata?> GetCustomMetadataAsync(IStorageFile file)
|
||||||
{
|
{
|
||||||
var fileContent = await FileIO.ReadTextAsync(file);
|
var fileContent = await FileIO.ReadTextAsync(file);
|
||||||
@@ -594,6 +619,16 @@ public class NewThemeService : INewThemeService
|
|||||||
return JsonSerializer.Deserialize(fileContent, DomainModelsJsonContext.Default.CustomThemeMetadata);
|
return JsonSerializer.Deserialize(fileContent, DomainModelsJsonContext.Default.CustomThemeMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task DeleteThemeAssetIfExistsAsync(StorageFolder themeFolder, string fileName)
|
||||||
|
{
|
||||||
|
var item = await themeFolder.TryGetItemAsync(fileName);
|
||||||
|
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
await item.DeleteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string GetSystemAccentColorHex()
|
public string GetSystemAccentColorHex()
|
||||||
=> uiSettings.GetColorValue(UIColorType.Accent).ToHex();
|
=> uiSettings.GetColorValue(UIColorType.Accent).ToHex();
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Windows.AppNotifications;
|
using CommunityToolkit.WinUI.Notifications;
|
||||||
using Microsoft.Windows.AppNotifications.Builder;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Windows.Data.Xml.Dom;
|
using Windows.Data.Xml.Dom;
|
||||||
using Windows.UI.Notifications;
|
using Windows.UI.Notifications;
|
||||||
@@ -76,9 +75,9 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddText(Translator.Notifications_MultipleNotificationsTitle);
|
builder.AddText(Translator.Notifications_MultipleNotificationsTitle);
|
||||||
builder.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount));
|
builder.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount));
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
builder.AddAudio(new Uri("ms-winsoundevent:Notification.Mail"));
|
||||||
|
|
||||||
ShowNotification(builder);
|
ShowNotification(builder, WinoApplicationMode.Mail);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -146,9 +145,9 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
AppNotificationManager.Default.RemoveByTagAsync(mailUniqueId.ToString()).AsTask().GetAwaiter().GetResult();
|
ToastNotificationManager.History.Remove(mailUniqueId.ToString());
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (Exception ex) when (ex is ArgumentException or InvalidOperationException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -164,11 +163,12 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddText(string.Format(Translator.Exception_AccountNeedsAttention_Message, account.Name));
|
builder.AddText(string.Format(Translator.Exception_AccountNeedsAttention_Message, account.Name));
|
||||||
builder.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString());
|
builder.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString());
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
builder.AddButton(new AppNotificationButton(Translator.Buttons_FixAccount)
|
builder.AddButton(new ToastButton()
|
||||||
|
.SetContent(Translator.Buttons_FixAccount)
|
||||||
.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString())
|
.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail));
|
||||||
|
|
||||||
ShowNotification(builder);
|
ShowNotification(builder, WinoApplicationMode.Mail);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateWebView2RuntimeMissingNotification()
|
public void CreateWebView2RuntimeMissingNotification()
|
||||||
@@ -178,7 +178,7 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Message);
|
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Message);
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
ShowNotification(builder);
|
ShowNotification(builder, WinoApplicationMode.Mail);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateStoreUpdateNotification()
|
public void CreateStoreUpdateNotification()
|
||||||
@@ -189,7 +189,7 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall);
|
builder.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall);
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
ShowNotification(builder, "store-update-available");
|
ShowNotification(builder, WinoApplicationMode.Mail, "store-update-available");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
|
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
|
||||||
@@ -197,7 +197,7 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
if (calendarItem == null)
|
if (calendarItem == null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
var builder = CreateBuilder(AppNotificationScenario.Reminder);
|
var builder = CreateBuilder(ToastScenario.Reminder);
|
||||||
var localStart = calendarItem.GetLocalStartDate();
|
var localStart = calendarItem.GetLocalStartDate();
|
||||||
var reminderContext = GetCalendarReminderContext(localStart, DateTime.Now);
|
var reminderContext = GetCalendarReminderContext(localStart, DateTime.Now);
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction);
|
builder.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction);
|
||||||
builder.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString());
|
builder.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString());
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar);
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Reminder"));
|
builder.AddAudio(new Uri("ms-winsoundevent:Notification.Reminder"));
|
||||||
|
|
||||||
var allowedSnoozeMinutes = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
|
var allowedSnoozeMinutes = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
|
||||||
reminderDurationInSeconds,
|
reminderDurationInSeconds,
|
||||||
@@ -223,40 +223,33 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
? preferredSnoozeMinutes
|
? preferredSnoozeMinutes
|
||||||
: allowedSnoozeMinutes[0];
|
: allowedSnoozeMinutes[0];
|
||||||
|
|
||||||
var selectionBox = new AppNotificationComboBox(Constants.ToastCalendarSnoozeDurationInputId)
|
builder.AddButton(new ToastButton()
|
||||||
.SetSelectedItem(defaultSnoozeMinutes.ToString());
|
.SetContent(Translator.CalendarReminder_SnoozeAction)
|
||||||
|
.SetImageUri(GetNotificationIconUri("calendar-snooze"))
|
||||||
foreach (var snoozeMinutes in allowedSnoozeMinutes)
|
|
||||||
{
|
|
||||||
selectionBox.AddItem(
|
|
||||||
snoozeMinutes.ToString(),
|
|
||||||
string.Format(Translator.CalendarReminder_SnoozeMinutesOption, snoozeMinutes));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.AddComboBox(selectionBox);
|
|
||||||
builder.AddButton(new AppNotificationButton(Translator.CalendarReminder_SnoozeAction)
|
|
||||||
.SetIcon(GetNotificationIconUri("calendar-snooze"))
|
|
||||||
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarSnoozeAction)
|
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarSnoozeAction)
|
||||||
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
||||||
|
.AddArgument(Constants.ToastCalendarSnoozeDurationMinutesKey, defaultSnoozeMinutes.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddButton(new AppNotificationButton(Translator.Buttons_Open)
|
builder.AddButton(new ToastButton()
|
||||||
|
.SetContent(Translator.Buttons_Open)
|
||||||
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction)
|
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction)
|
||||||
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
||||||
|
|
||||||
if (Uri.TryCreate(calendarItem.HtmlLink, UriKind.Absolute, out _))
|
if (Uri.TryCreate(calendarItem.HtmlLink, UriKind.Absolute, out _))
|
||||||
{
|
{
|
||||||
builder.AddButton(new AppNotificationButton(Translator.CalendarEventDetails_JoinOnline)
|
builder.AddButton(new ToastButton()
|
||||||
.SetIcon(GetNotificationIconUri("calendar-join"))
|
.SetContent(Translator.CalendarEventDetails_JoinOnline)
|
||||||
|
.SetImageUri(GetNotificationIconUri("calendar-join"))
|
||||||
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarJoinOnlineAction)
|
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarJoinOnlineAction)
|
||||||
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
||||||
}
|
}
|
||||||
|
|
||||||
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
|
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
|
||||||
ShowNotification(builder, tag);
|
ShowNotification(builder, WinoApplicationMode.Calendar, tag);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -278,10 +271,10 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
await stream.WriteAsync(bytes);
|
await stream.WriteAsync(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.SetAppLogoOverride(new Uri($"ms-appdata:///temp/{tempFile.Name}"), AppNotificationImageCrop.Default);
|
builder.AddAppLogoOverride(new Uri($"ms-appdata:///temp/{tempFile.Name}"), ToastGenericAppLogoCrop.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.SetTimeStamp(mailItem.CreationDate.ToLocalTime());
|
builder.AddCustomTimeStamp(mailItem.CreationDate.ToLocalTime());
|
||||||
builder.AddText(mailItem.FromName);
|
builder.AddText(mailItem.FromName);
|
||||||
builder.AddText(mailItem.Subject);
|
builder.AddText(mailItem.Subject);
|
||||||
builder.AddText(mailItem.PreviewText);
|
builder.AddText(mailItem.PreviewText);
|
||||||
@@ -291,9 +284,9 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddButton(GetMarkAsReadButton(mailItem.UniqueId));
|
builder.AddButton(GetMarkAsReadButton(mailItem.UniqueId));
|
||||||
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
||||||
builder.AddButton(GetArchiveButton(mailItem.UniqueId));
|
builder.AddButton(GetArchiveButton(mailItem.UniqueId));
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
builder.AddAudio(new Uri("ms-winsoundevent:Notification.Mail"));
|
||||||
|
|
||||||
ShowNotification(builder, mailItem.UniqueId.ToString());
|
ShowNotification(builder, WinoApplicationMode.Mail, mailItem.UniqueId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBadge(string applicationId, int? badgeCount)
|
private void UpdateBadge(string applicationId, int? badgeCount)
|
||||||
@@ -347,40 +340,43 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
return string.Format(Translator.CalendarReminder_StartedMinutesAgo, minutesAgo);
|
return string.Format(Translator.CalendarReminder_StartedMinutesAgo, minutesAgo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppNotificationButton GetArchiveButton(Guid mailUniqueId)
|
private ToastButton GetArchiveButton(Guid mailUniqueId)
|
||||||
=> new AppNotificationButton(Translator.MailOperation_Archive)
|
=> new ToastButton()
|
||||||
.SetIcon(GetNotificationIconUri("mail-archive"))
|
.SetContent(Translator.MailOperation_Archive)
|
||||||
|
.SetImageUri(GetNotificationIconUri("mail-archive"))
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.Archive.ToString())
|
.AddArgument(Constants.ToastActionKey, MailOperation.Archive.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
private AppNotificationButton GetDeleteButton(Guid mailUniqueId)
|
private ToastButton GetDeleteButton(Guid mailUniqueId)
|
||||||
=> new AppNotificationButton(Translator.MailOperation_Delete)
|
=> new ToastButton()
|
||||||
.SetIcon(GetNotificationIconUri("mail-delete"))
|
.SetContent(Translator.MailOperation_Delete)
|
||||||
|
.SetImageUri(GetNotificationIconUri("mail-delete"))
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete.ToString())
|
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
private AppNotificationButton GetMarkAsReadButton(Guid mailUniqueId)
|
private ToastButton GetMarkAsReadButton(Guid mailUniqueId)
|
||||||
=> new AppNotificationButton(Translator.MailOperation_MarkAsRead)
|
=> new ToastButton()
|
||||||
.SetIcon(GetNotificationIconUri("mail-markread"))
|
.SetContent(Translator.MailOperation_MarkAsRead)
|
||||||
|
.SetImageUri(GetNotificationIconUri("mail-markread"))
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead.ToString())
|
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
private static AppNotificationBuilder CreateBuilder(AppNotificationScenario scenario = AppNotificationScenario.Default)
|
private static ToastContentBuilder CreateBuilder(ToastScenario scenario = ToastScenario.Default)
|
||||||
=> new AppNotificationBuilder().SetScenario(scenario);
|
=> new ToastContentBuilder().SetToastScenario(scenario);
|
||||||
|
|
||||||
private static void ShowNotification(AppNotificationBuilder builder, string? tag = null)
|
private static void ShowNotification(ToastContentBuilder builder, WinoApplicationMode mode, string? tag = null)
|
||||||
{
|
{
|
||||||
var notification = builder.BuildNotification();
|
var notification = new ToastNotification(builder.GetXml());
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(tag))
|
if (!string.IsNullOrWhiteSpace(tag))
|
||||||
{
|
{
|
||||||
notification.Tag = tag;
|
notification.Tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppNotificationManager.Default.Show(notification);
|
ToastNotificationManager.CreateToastNotifier(AppEntryConstants.GetAppUserModelId(mode)).Show(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Uri GetNotificationIconUri(string iconName)
|
private static Uri GetNotificationIconUri(string iconName)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -37,7 +36,6 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
|
|||||||
_configurationService.Set(propertyName, value ?? string.Empty);
|
_configurationService.Set(propertyName, value ?? string.Empty);
|
||||||
|
|
||||||
OnPropertyChanged(propertyName);
|
OnPropertyChanged(propertyName);
|
||||||
Debug.WriteLine($"PreferencesService -> {propertyName}:{value?.ToString()}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MailRenderingOptions GetRenderingOptions()
|
public MailRenderingOptions GetRenderingOptions()
|
||||||
|
|||||||
@@ -210,6 +210,13 @@
|
|||||||
</TransitionCollection>
|
</TransitionCollection>
|
||||||
</StackPanel.ChildrenTransitions>
|
</StackPanel.ChildrenTransitions>
|
||||||
|
|
||||||
|
<muxc:InfoBar
|
||||||
|
IsOpen="True"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Message="{x:Bind ViewModel.InitialSynchronizationSummary, Mode=OneWay}"
|
||||||
|
Severity="Informational"
|
||||||
|
Title="{x:Bind domain:Translator.AccountDetailsPage_InitialSynchronization_Title}"
|
||||||
|
Visibility="{x:Bind ViewModel.IsInitialSynchronizationSummaryVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
<controls:SettingsCard
|
<controls:SettingsCard
|
||||||
Command="{x:Bind ViewModel.EditAliasesCommand}"
|
Command="{x:Bind ViewModel.EditAliasesCommand}"
|
||||||
|
|||||||
@@ -4,267 +4,267 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:abstract="using:Wino.Views.Abstract"
|
xmlns:abstract="using:Wino.Views.Abstract"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:helpers="using:Wino.Helpers"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel
|
<Grid
|
||||||
|
MaxWidth="980"
|
||||||
Padding="36,28,36,36"
|
Padding="36,28,36,36"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Center">
|
||||||
Spacing="24">
|
<StackPanel Spacing="20">
|
||||||
|
<StackPanel Spacing="4">
|
||||||
<!-- Page Header -->
|
<TextBlock
|
||||||
<StackPanel Spacing="4">
|
FontSize="28"
|
||||||
<TextBlock
|
FontWeight="SemiBold"
|
||||||
FontSize="28"
|
Text="{x:Bind ViewModel.PageTitle, Mode=OneWay}" />
|
||||||
FontWeight="SemiBold"
|
<TextBlock
|
||||||
Text="{x:Bind ViewModel.PageTitle, Mode=OneWay}" />
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
<TextBlock
|
Style="{StaticResource BodyTextBlockStyle}"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Text="{x:Bind ViewModel.SubtitleText, Mode=OneWay}"
|
||||||
Style="{StaticResource BodyTextBlockStyle}"
|
TextWrapping="WrapWholeWords" />
|
||||||
Text="{x:Bind ViewModel.SubtitleText, Mode=OneWay}"
|
<TextBlock
|
||||||
TextWrapping="WrapWholeWords" />
|
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
|
||||||
<TextBlock
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
|
Text="{x:Bind ViewModel.ProviderHint, Mode=OneWay}"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
TextWrapping="WrapWholeWords"
|
||||||
Text="{x:Bind ViewModel.ProviderHint, Mode=OneWay}"
|
Visibility="{x:Bind ViewModel.HasProviderHint, Mode=OneWay}" />
|
||||||
TextWrapping="WrapWholeWords"
|
|
||||||
Visibility="{x:Bind ViewModel.HasProviderHint, Mode=OneWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- Setup Mode Selector -->
|
|
||||||
<SelectorBar x:Name="SetupModeSelector" SelectionChanged="OnSetupModeSelectionChanged">
|
|
||||||
<SelectorBarItem Icon="Library" Text="{x:Bind ViewModel.BasicTabText, Mode=OneWay}" />
|
|
||||||
<SelectorBarItem Icon="Setting" Text="{x:Bind ViewModel.AdvancedTabText, Mode=OneWay}" />
|
|
||||||
</SelectorBar>
|
|
||||||
|
|
||||||
<!-- Basic Setup Card -->
|
|
||||||
<Border
|
|
||||||
Padding="20"
|
|
||||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
|
||||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
|
||||||
BorderThickness="1"
|
|
||||||
CornerRadius="8"
|
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsAdvancedSetupSelected), Mode=OneWay}">
|
|
||||||
<StackPanel Spacing="16">
|
|
||||||
<StackPanel Spacing="2">
|
|
||||||
<TextBlock
|
|
||||||
FontSize="16"
|
|
||||||
FontWeight="SemiBold"
|
|
||||||
Text="{x:Bind ViewModel.BasicSectionTitleText, Mode=OneWay}" />
|
|
||||||
<TextBlock
|
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
|
||||||
Text="{x:Bind ViewModel.BasicSectionDescriptionText, Mode=OneWay}"
|
|
||||||
TextWrapping="WrapWholeWords" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<Grid ColumnSpacing="12">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBox
|
|
||||||
Grid.Column="0"
|
|
||||||
Header="{x:Bind ViewModel.DisplayNameHeaderText, Mode=OneWay}"
|
|
||||||
PlaceholderText="{x:Bind ViewModel.DisplayNamePlaceholderText, Mode=OneWay}"
|
|
||||||
Text="{x:Bind ViewModel.DisplayName, Mode=TwoWay}" />
|
|
||||||
<TextBox
|
|
||||||
Grid.Column="1"
|
|
||||||
Header="{x:Bind ViewModel.EmailAddressHeaderText, Mode=OneWay}"
|
|
||||||
PlaceholderText="{x:Bind ViewModel.EmailAddressPlaceholderText, Mode=OneWay}"
|
|
||||||
Text="{x:Bind ViewModel.EmailAddress, Mode=TwoWay}" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<PasswordBox Header="{x:Bind ViewModel.PasswordHeaderText, Mode=OneWay}" Password="{x:Bind ViewModel.Password, Mode=TwoWay}" />
|
|
||||||
|
|
||||||
<CheckBox Content="{x:Bind ViewModel.EnableCalendarSupportText, Mode=OneWay}" IsChecked="{x:Bind ViewModel.IsCalendarSupportEnabled, Mode=TwoWay}" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Command="{x:Bind ViewModel.AutoDiscoverSettingsCommand}"
|
|
||||||
Content="{x:Bind ViewModel.AutoDiscoverButtonText, Mode=OneWay}"
|
|
||||||
Style="{ThemeResource AccentButtonStyle}" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Advanced Setup Card -->
|
<Border
|
||||||
<Border
|
Padding="20"
|
||||||
Padding="20"
|
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
BorderThickness="1"
|
||||||
BorderThickness="1"
|
CornerRadius="8">
|
||||||
CornerRadius="8"
|
<StackPanel Spacing="16">
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsBasicSetupSelected), Mode=OneWay}">
|
<StackPanel Spacing="2">
|
||||||
<StackPanel Spacing="20">
|
|
||||||
<StackPanel Spacing="2">
|
|
||||||
<TextBlock
|
|
||||||
FontSize="16"
|
|
||||||
FontWeight="SemiBold"
|
|
||||||
Text="{x:Bind ViewModel.AdvancedSectionTitleText, Mode=OneWay}" />
|
|
||||||
<TextBlock
|
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
|
||||||
Text="{x:Bind ViewModel.AdvancedSectionDescriptionText, Mode=OneWay}"
|
|
||||||
TextWrapping="WrapWholeWords" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<Grid ColumnSpacing="24">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- Incoming (IMAP) Settings -->
|
|
||||||
<Border
|
|
||||||
Padding="16"
|
|
||||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
|
||||||
CornerRadius="6">
|
|
||||||
<StackPanel Spacing="10">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<FontIcon FontSize="14" Glyph="" />
|
|
||||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.IncomingSectionTitleText, Mode=OneWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
<TextBox Header="{x:Bind ViewModel.IncomingServerHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.IncomingServer, Mode=TwoWay}" />
|
|
||||||
<TextBox Header="{x:Bind ViewModel.PortHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.IncomingServerPort, Mode=TwoWay}" />
|
|
||||||
<TextBox Header="{x:Bind ViewModel.IncomingUsernameHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.IncomingServerUsername, Mode=TwoWay}" />
|
|
||||||
<PasswordBox Header="{x:Bind ViewModel.IncomingPasswordHeaderText, Mode=OneWay}" Password="{x:Bind ViewModel.IncomingServerPassword, Mode=TwoWay}" />
|
|
||||||
<ComboBox
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Header="{x:Bind ViewModel.ConnectionSecurityHeaderText, Mode=OneWay}"
|
|
||||||
ItemsSource="{x:Bind ViewModel.AvailableConnectionSecurityDisplayNames}"
|
|
||||||
SelectedIndex="{x:Bind ViewModel.SelectedIncomingServerConnectionSecurityIndex, Mode=TwoWay}" />
|
|
||||||
<ComboBox
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Header="{x:Bind ViewModel.AuthenticationMethodHeaderText, Mode=OneWay}"
|
|
||||||
ItemsSource="{x:Bind ViewModel.AvailableAuthenticationMethodDisplayNames}"
|
|
||||||
SelectedIndex="{x:Bind ViewModel.SelectedIncomingServerAuthenticationMethodIndex, Mode=TwoWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Outgoing (SMTP) Settings -->
|
|
||||||
<Border
|
|
||||||
Grid.Column="1"
|
|
||||||
Padding="16"
|
|
||||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
|
||||||
CornerRadius="6">
|
|
||||||
<StackPanel Spacing="10">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<FontIcon FontSize="14" Glyph="" />
|
|
||||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.OutgoingSectionTitleText, Mode=OneWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
<TextBox Header="{x:Bind ViewModel.OutgoingServerHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.OutgoingServer, Mode=TwoWay}" />
|
|
||||||
<TextBox Header="{x:Bind ViewModel.PortHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.OutgoingServerPort, Mode=TwoWay}" />
|
|
||||||
<TextBox Header="{x:Bind ViewModel.OutgoingUsernameHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.OutgoingServerUsername, Mode=TwoWay}" />
|
|
||||||
<PasswordBox Header="{x:Bind ViewModel.OutgoingPasswordHeaderText, Mode=OneWay}" Password="{x:Bind ViewModel.OutgoingServerPassword, Mode=TwoWay}" />
|
|
||||||
<ComboBox
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Header="{x:Bind ViewModel.ConnectionSecurityHeaderText, Mode=OneWay}"
|
|
||||||
ItemsSource="{x:Bind ViewModel.AvailableConnectionSecurityDisplayNames}"
|
|
||||||
SelectedIndex="{x:Bind ViewModel.SelectedOutgoingServerConnectionSecurityIndex, Mode=TwoWay}" />
|
|
||||||
<ComboBox
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Header="{x:Bind ViewModel.AuthenticationMethodHeaderText, Mode=OneWay}"
|
|
||||||
ItemsSource="{x:Bind ViewModel.AvailableAuthenticationMethodDisplayNames}"
|
|
||||||
SelectedIndex="{x:Bind ViewModel.SelectedOutgoingServerAuthenticationMethodIndex, Mode=TwoWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Calendar Settings Card -->
|
|
||||||
<Border
|
|
||||||
Padding="20"
|
|
||||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
|
||||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
|
||||||
BorderThickness="1"
|
|
||||||
CornerRadius="8">
|
|
||||||
<StackPanel Spacing="16">
|
|
||||||
<StackPanel Spacing="2">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<FontIcon FontSize="16" Glyph="" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="16"
|
FontSize="16"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
Text="{x:Bind ViewModel.CalendarSectionTitleText, Mode=OneWay}" />
|
Text="{x:Bind ViewModel.BasicSectionTitleText, Mode=OneWay}" />
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind ViewModel.BasicSectionDescriptionText, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid ColumnSpacing="12" RowSpacing="12">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Header="{x:Bind ViewModel.DisplayNameHeaderText, Mode=OneWay}"
|
||||||
|
PlaceholderText="{x:Bind ViewModel.DisplayNamePlaceholderText, Mode=OneWay}"
|
||||||
|
Text="{x:Bind ViewModel.DisplayName, Mode=TwoWay}" />
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Header="{x:Bind ViewModel.EmailAddressHeaderText, Mode=OneWay}"
|
||||||
|
PlaceholderText="{x:Bind ViewModel.EmailAddressPlaceholderText, Mode=OneWay}"
|
||||||
|
Text="{x:Bind ViewModel.EmailAddress, Mode=TwoWay}" />
|
||||||
|
<PasswordBox
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Header="{x:Bind ViewModel.PasswordHeaderText, Mode=OneWay}"
|
||||||
|
Password="{x:Bind ViewModel.Password, Mode=TwoWay}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
Content="{x:Bind ViewModel.EnableCalendarSupportText, Mode=OneWay}"
|
||||||
|
IsChecked="{x:Bind ViewModel.IsCalendarSupportEnabled, Mode=TwoWay}" />
|
||||||
|
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind ViewModel.AdvancedSectionDescriptionText, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
|
<Button
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Command="{x:Bind ViewModel.AutoDiscoverSettingsCommand}"
|
||||||
|
Content="{x:Bind ViewModel.AutoDiscoverButtonText, Mode=OneWay}"
|
||||||
|
Style="{ThemeResource AccentButtonStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
Padding="20"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8">
|
||||||
|
<StackPanel Spacing="16">
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind ViewModel.AdvancedSectionTitleText, Mode=OneWay}" />
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind ViewModel.AdvancedSectionDescriptionText, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid ColumnSpacing="16">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
Padding="16"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||||
|
CornerRadius="6">
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.IncomingSectionTitleText, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox Header="{x:Bind ViewModel.IncomingServerHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.IncomingServer, Mode=TwoWay}" />
|
||||||
|
<TextBox Header="{x:Bind ViewModel.PortHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.IncomingServerPort, Mode=TwoWay}" />
|
||||||
|
<TextBox Header="{x:Bind ViewModel.IncomingUsernameHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.IncomingServerUsername, Mode=TwoWay}" />
|
||||||
|
<PasswordBox Header="{x:Bind ViewModel.IncomingPasswordHeaderText, Mode=OneWay}" Password="{x:Bind ViewModel.IncomingServerPassword, Mode=TwoWay}" />
|
||||||
|
<ComboBox
|
||||||
|
Header="{x:Bind ViewModel.ConnectionSecurityHeaderText, Mode=OneWay}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.AvailableConnectionSecurityDisplayNames}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.SelectedIncomingServerConnectionSecurityIndex, Mode=TwoWay}" />
|
||||||
|
<ComboBox
|
||||||
|
Header="{x:Bind ViewModel.AuthenticationMethodHeaderText, Mode=OneWay}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.AvailableAuthenticationMethodDisplayNames}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.SelectedIncomingServerAuthenticationMethodIndex, Mode=TwoWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
Grid.Column="1"
|
||||||
|
Padding="16"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||||
|
CornerRadius="6">
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.OutgoingSectionTitleText, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox Header="{x:Bind ViewModel.OutgoingServerHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.OutgoingServer, Mode=TwoWay}" />
|
||||||
|
<TextBox Header="{x:Bind ViewModel.PortHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.OutgoingServerPort, Mode=TwoWay}" />
|
||||||
|
<TextBox Header="{x:Bind ViewModel.OutgoingUsernameHeaderText, Mode=OneWay}" Text="{x:Bind ViewModel.OutgoingServerUsername, Mode=TwoWay}" />
|
||||||
|
<PasswordBox Header="{x:Bind ViewModel.OutgoingPasswordHeaderText, Mode=OneWay}" Password="{x:Bind ViewModel.OutgoingServerPassword, Mode=TwoWay}" />
|
||||||
|
<ComboBox
|
||||||
|
Header="{x:Bind ViewModel.ConnectionSecurityHeaderText, Mode=OneWay}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.AvailableConnectionSecurityDisplayNames}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.SelectedOutgoingServerConnectionSecurityIndex, Mode=TwoWay}" />
|
||||||
|
<ComboBox
|
||||||
|
Header="{x:Bind ViewModel.AuthenticationMethodHeaderText, Mode=OneWay}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.AvailableAuthenticationMethodDisplayNames}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.SelectedOutgoingServerAuthenticationMethodIndex, Mode=TwoWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Command="{x:Bind ViewModel.TestImapConnectionCommand}"
|
||||||
|
Content="{x:Bind ViewModel.TestImapButtonText, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
Padding="20"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8">
|
||||||
|
<StackPanel Spacing="16">
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="16" Glyph="" />
|
||||||
|
<TextBlock
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind ViewModel.CalendarSectionTitleText, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind ViewModel.CalendarSectionDescriptionText, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ComboBox
|
||||||
|
Header="{x:Bind ViewModel.CalendarModeHeaderText, Mode=OneWay}"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsCalendarModeSelectionVisible, Mode=OneWay}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.AvailableCalendarSupportModeTitles}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.SelectedCalendarSupportModeIndex, Mode=TwoWay}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{x:Bind ViewModel.CalendarSectionDescriptionText, Mode=OneWay}"
|
Text="{x:Bind ViewModel.SelectedCalendarSupportDescription, Mode=OneWay}"
|
||||||
TextWrapping="WrapWholeWords" />
|
TextWrapping="WrapWholeWords" />
|
||||||
|
|
||||||
|
<HyperlinkButton
|
||||||
|
Command="{x:Bind ViewModel.ShowLocalCalendarExplanationCommand}"
|
||||||
|
Content="{x:Bind ViewModel.LocalCalendarLearnMoreText, Mode=OneWay}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsLocalCalendarModeSelected, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<Grid ColumnSpacing="12" Visibility="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="2*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox
|
||||||
|
Grid.Column="0"
|
||||||
|
Header="{x:Bind ViewModel.CalDavServiceUrlHeaderText, Mode=OneWay}"
|
||||||
|
Text="{x:Bind ViewModel.CalDavServiceUrl, Mode=TwoWay}" />
|
||||||
|
<TextBox
|
||||||
|
Grid.Column="1"
|
||||||
|
Header="{x:Bind ViewModel.CalDavUsernameHeaderText, Mode=OneWay}"
|
||||||
|
Text="{x:Bind ViewModel.CalDavUsername, Mode=TwoWay}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<PasswordBox
|
||||||
|
Header="{x:Bind ViewModel.CalDavPasswordHeaderText, Mode=OneWay}"
|
||||||
|
Password="{x:Bind ViewModel.CalDavPassword, Mode=TwoWay}"
|
||||||
|
Visibility="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Command="{x:Bind ViewModel.TestCalDavConnectionCommand}"
|
||||||
|
Content="{x:Bind ViewModel.TestCalDavButtonText, Mode=OneWay}"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<ComboBox
|
<Grid Margin="0,4,0,0" ColumnSpacing="8">
|
||||||
HorizontalAlignment="Stretch"
|
<Grid.ColumnDefinitions>
|
||||||
Header="{x:Bind ViewModel.CalendarModeHeaderText, Mode=OneWay}"
|
<ColumnDefinition Width="*" />
|
||||||
IsEnabled="{x:Bind ViewModel.IsCalendarModeSelectionVisible, Mode=OneWay}"
|
<ColumnDefinition Width="Auto" />
|
||||||
ItemsSource="{x:Bind ViewModel.AvailableCalendarSupportModeTitles}"
|
<ColumnDefinition Width="Auto" />
|
||||||
SelectedIndex="{x:Bind ViewModel.SelectedCalendarSupportModeIndex, Mode=TwoWay}" />
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button
|
||||||
<TextBlock
|
Grid.Column="1"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Command="{x:Bind ViewModel.CancelCommand}"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Content="{x:Bind ViewModel.CancelButtonText, Mode=OneWay}" />
|
||||||
Text="{x:Bind ViewModel.SelectedCalendarSupportDescription, Mode=OneWay}"
|
<Button
|
||||||
TextWrapping="WrapWholeWords" />
|
Grid.Column="2"
|
||||||
|
Command="{x:Bind ViewModel.SaveCommand}"
|
||||||
<HyperlinkButton
|
Content="{x:Bind ViewModel.SaveButtonText, Mode=OneWay}"
|
||||||
Command="{x:Bind ViewModel.ShowLocalCalendarExplanationCommand}"
|
Style="{ThemeResource AccentButtonStyle}" />
|
||||||
Content="{x:Bind ViewModel.LocalCalendarLearnMoreText, Mode=OneWay}"
|
</Grid>
|
||||||
IsEnabled="{x:Bind ViewModel.IsLocalCalendarModeSelected, Mode=OneWay}" />
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
<Grid ColumnSpacing="12" Visibility="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="2*" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBox
|
|
||||||
Grid.Column="0"
|
|
||||||
Header="{x:Bind ViewModel.CalDavServiceUrlHeaderText, Mode=OneWay}"
|
|
||||||
Text="{x:Bind ViewModel.CalDavServiceUrl, Mode=TwoWay}" />
|
|
||||||
<TextBox
|
|
||||||
Grid.Column="1"
|
|
||||||
Header="{x:Bind ViewModel.CalDavUsernameHeaderText, Mode=OneWay}"
|
|
||||||
Text="{x:Bind ViewModel.CalDavUsername, Mode=TwoWay}" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<PasswordBox
|
|
||||||
Header="{x:Bind ViewModel.CalDavPasswordHeaderText, Mode=OneWay}"
|
|
||||||
Password="{x:Bind ViewModel.CalDavPassword, Mode=TwoWay}"
|
|
||||||
Visibility="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Action Bar -->
|
|
||||||
<Grid Margin="0,4,0,0" ColumnSpacing="8">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Button
|
|
||||||
Grid.Column="0"
|
|
||||||
Command="{x:Bind ViewModel.TestImapConnectionCommand}"
|
|
||||||
Content="{x:Bind ViewModel.TestImapButtonText, Mode=OneWay}" />
|
|
||||||
<Button
|
|
||||||
Grid.Column="1"
|
|
||||||
Command="{x:Bind ViewModel.TestCalDavConnectionCommand}"
|
|
||||||
Content="{x:Bind ViewModel.TestCalDavButtonText, Mode=OneWay}"
|
|
||||||
IsEnabled="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}" />
|
|
||||||
<Button
|
|
||||||
Grid.Column="3"
|
|
||||||
Command="{x:Bind ViewModel.CancelCommand}"
|
|
||||||
Content="{x:Bind ViewModel.CancelButtonText, Mode=OneWay}" />
|
|
||||||
<Button
|
|
||||||
Grid.Column="4"
|
|
||||||
Command="{x:Bind ViewModel.SaveCommand}"
|
|
||||||
Content="{x:Bind ViewModel.SaveButtonText, Mode=OneWay}"
|
|
||||||
Style="{ThemeResource AccentButtonStyle}" />
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</abstract:ImapCalDavSettingsPageAbstract>
|
</abstract:ImapCalDavSettingsPageAbstract>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
|
||||||
using Wino.Views.Abstract;
|
using Wino.Views.Abstract;
|
||||||
|
|
||||||
namespace Wino.Views;
|
namespace Wino.Views;
|
||||||
@@ -10,22 +8,4 @@ public sealed partial class ImapCalDavSettingsPage : ImapCalDavSettingsPageAbstr
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSetupModeSelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
ViewModel.SelectedSetupTabIndex = SetupModeSelector.SelectedItem == null ? 0 : SetupModeSelector.Items.IndexOf(SetupModeSelector.SelectedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnNavigatedTo(e);
|
|
||||||
|
|
||||||
var tabIndex = ViewModel.SelectedSetupTabIndex;
|
|
||||||
if (tabIndex < 0 || tabIndex >= SetupModeSelector.Items.Count)
|
|
||||||
{
|
|
||||||
tabIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetupModeSelector.SelectedItem = SetupModeSelector.Items[tabIndex];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,12 +210,12 @@
|
|||||||
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||||
|
|
||||||
<!-- Test Notification -->
|
<!-- Test Notification -->
|
||||||
<!--<Button Command="{x:Bind ViewModel.CreateTestNotificationCommand}" Style="{StaticResource TransparentActionButtonStyle}">
|
<Button Command="{x:Bind ViewModel.CreateTestNotificationCommand}" Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<coreControls:WinoFontIcon FontSize="16" Icon="Reminder" />
|
<coreControls:WinoFontIcon FontSize="16" Icon="Reminder" />
|
||||||
<TextBlock VerticalAlignment="Center" Text="Test notification" />
|
<TextBlock VerticalAlignment="Center" Text="Test notification" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>-->
|
</Button>
|
||||||
|
|
||||||
<!-- Edit Series -->
|
<!-- Edit Series -->
|
||||||
<Border
|
<Border
|
||||||
|
|||||||
@@ -10,15 +10,12 @@
|
|||||||
xmlns:helpers="using:Wino.Helpers"
|
xmlns:helpers="using:Wino.Helpers"
|
||||||
xmlns:interfaces="using:Wino.Core.Domain.Interfaces"
|
xmlns:interfaces="using:Wino.Core.Domain.Interfaces"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer
|
<ScrollViewer HorizontalAlignment="Center" VerticalScrollBarVisibility="Auto">
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
VerticalScrollBarVisibility="Auto">
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
MaxWidth="480"
|
Margin="0,12"
|
||||||
Margin="0,24,0,24"
|
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Spacing="20">
|
Spacing="20">
|
||||||
|
|
||||||
@@ -76,6 +73,50 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
MaxWidth="600"
|
||||||
|
Padding="12"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8">
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind domain:Translator.AccountCreation_InitialSynchronization_Title}" />
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind domain:Translator.AccountCreation_InitialSynchronization_Description}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
ItemsSource="{x:Bind ViewModel.InitialSynchronizationRanges, Mode=OneWay}"
|
||||||
|
SelectedItem="{x:Bind ViewModel.SelectedInitialSynchronizationRange, Mode=TwoWay}">
|
||||||
|
<ListView.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<ItemsStackPanel Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListView.ItemsPanel>
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="accounts:InitialSynchronizationRangeOption">
|
||||||
|
<TextBlock Text="{x:Bind DisplayText}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
<muxc:InfoBar
|
||||||
|
Title="{x:Bind domain:Translator.GeneralTitle_Warning}"
|
||||||
|
Margin="0,2,0,0"
|
||||||
|
IsOpen="True"
|
||||||
|
Message="{x:Bind domain:Translator.AccountCreation_InitialSynchronization_EverythingWarning}"
|
||||||
|
Severity="Warning"
|
||||||
|
Visibility="{x:Bind ViewModel.IsInitialSynchronizationWarningVisible, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- Provider List -->
|
<!-- Provider List -->
|
||||||
<ItemsView
|
<ItemsView
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
|
|||||||
@@ -49,6 +49,68 @@
|
|||||||
CompactTemplate="{StaticResource CompactDisplayModePreviewTemplate}"
|
CompactTemplate="{StaticResource CompactDisplayModePreviewTemplate}"
|
||||||
MediumTemplate="{StaticResource MediumDisplayModePreviewTemplate}"
|
MediumTemplate="{StaticResource MediumDisplayModePreviewTemplate}"
|
||||||
SpaciousTemplate="{StaticResource SpaciousDisplayModePreviewTemplate}" />
|
SpaciousTemplate="{StaticResource SpaciousDisplayModePreviewTemplate}" />
|
||||||
|
|
||||||
|
<DataTemplate x:Key="PersonalizationCustomAppThemeTemplate" x:DataType="personalization:CustomAppTheme">
|
||||||
|
<Grid
|
||||||
|
Width="175"
|
||||||
|
CornerRadius="6"
|
||||||
|
RowSpacing="0">
|
||||||
|
<Grid.ContextFlyout>
|
||||||
|
<MenuFlyout Placement="BottomEdgeAlignedRight">
|
||||||
|
<MenuFlyoutItem
|
||||||
|
Command="{Binding ElementName=root, Path=ViewModel.DeleteCustomThemeCommand}"
|
||||||
|
CommandParameter="{x:Bind}"
|
||||||
|
Text="{x:Bind domain:Translator.Buttons_Delete}">
|
||||||
|
<MenuFlyoutItem.Icon>
|
||||||
|
<SymbolIcon Symbol="Delete" />
|
||||||
|
</MenuFlyoutItem.Icon>
|
||||||
|
</MenuFlyoutItem>
|
||||||
|
</MenuFlyout>
|
||||||
|
</Grid.ContextFlyout>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="125" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid CornerRadius="6,6,0,0">
|
||||||
|
<Grid.Background>
|
||||||
|
<ImageBrush ImageSource="{x:Bind BackgroundPreviewImage}" />
|
||||||
|
</Grid.Background>
|
||||||
|
<Grid x:Name="AccentAssignedGrid" x:Load="{x:Bind IsAccentColorAssigned}">
|
||||||
|
<Grid
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="0,12,12,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(AccentColor)}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid x:Name="AccentNotAssignedGrid" x:Load="{x:Bind IsAccentColorAssigned, Converter={StaticResource ReverseBooleanConverter}}">
|
||||||
|
<Grid
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="0,12,12,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Background="{ThemeResource SystemAccentColor}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
<TextBlock
|
||||||
|
Padding="0,8"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Text="{x:Bind ThemeName}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<coreSelectors:AppThemePreviewTemplateSelector
|
||||||
|
x:Key="PersonalizationAppThemePreviewTemplateSelector"
|
||||||
|
CustomAppTemplate="{StaticResource PersonalizationCustomAppThemeTemplate}"
|
||||||
|
PreDefinedThemeTemplate="{StaticResource PreDefinedAppThemeTemplate}"
|
||||||
|
SystemThemeTemplate="{StaticResource SystemAppThemeTemplate}" />
|
||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
@@ -147,7 +209,7 @@
|
|||||||
<!-- TODO: Group by custom/wino -->
|
<!-- TODO: Group by custom/wino -->
|
||||||
<GridView
|
<GridView
|
||||||
toolkitExt:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
|
toolkitExt:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
|
||||||
ItemTemplateSelector="{StaticResource AppThemePreviewTemplateSelector}"
|
ItemTemplateSelector="{StaticResource PersonalizationAppThemePreviewTemplateSelector}"
|
||||||
ItemsSource="{x:Bind ViewModel.AppThemes, Mode=OneWay}"
|
ItemsSource="{x:Bind ViewModel.AppThemes, Mode=OneWay}"
|
||||||
SelectedItem="{x:Bind ViewModel.SelectedAppTheme, Mode=TwoWay}" />
|
SelectedItem="{x:Bind ViewModel.SelectedAppTheme, Mode=TwoWay}" />
|
||||||
</controls:SettingsCard>
|
</controls:SettingsCard>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,6 +11,9 @@
|
|||||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||||
<UseWinUI>true</UseWinUI>
|
<UseWinUI>true</UseWinUI>
|
||||||
<EnableMsixTooling>true</EnableMsixTooling>
|
<EnableMsixTooling>true</EnableMsixTooling>
|
||||||
|
<!-- Bundle the Windows App SDK and .NET runtime into per-architecture MSIX outputs. -->
|
||||||
|
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||||
|
<SelfContained Condition="'$(RuntimeIdentifier)' != ''">true</SelfContained>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<!-- Single instancing -->
|
<!-- Single instancing -->
|
||||||
@@ -182,6 +185,7 @@
|
|||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" />
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
|
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Lottie" />
|
<PackageReference Include="CommunityToolkit.WinUI.Lottie" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Notifications" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.Graphics.Win2D" />
|
<PackageReference Include="Microsoft.Graphics.Win2D" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||||
|
|||||||
@@ -475,7 +475,7 @@ public class AccountService : BaseDatabaseService, IAccountService
|
|||||||
|
|
||||||
public async Task UpdateAccountCustomServerInformationAsync(CustomServerInformation customServerInformation)
|
public async Task UpdateAccountCustomServerInformationAsync(CustomServerInformation customServerInformation)
|
||||||
{
|
{
|
||||||
await Connection.UpdateAsync(customServerInformation, typeof(CustomServerInformation)).ConfigureAwait(false);
|
await Connection.InsertOrReplaceAsync(customServerInformation, typeof(CustomServerInformation)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases)
|
public async Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases)
|
||||||
@@ -590,6 +590,11 @@ public class AccountService : BaseDatabaseService, IAccountService
|
|||||||
{
|
{
|
||||||
Guard.IsNotNull(account);
|
Guard.IsNotNull(account);
|
||||||
|
|
||||||
|
if (!account.CreatedAt.HasValue)
|
||||||
|
{
|
||||||
|
account.CreatedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
var accountCount = await Connection.Table<MailAccount>().CountAsync();
|
var accountCount = await Connection.Table<MailAccount>().CountAsync();
|
||||||
|
|
||||||
// If there are no accounts before this one, set it as startup account.
|
// If there are no accounts before this one, set it as startup account.
|
||||||
|
|||||||
@@ -79,6 +79,22 @@ public class DatabaseService : IDatabaseService
|
|||||||
{
|
{
|
||||||
await EnsureKeyboardShortcutSchemaAsync().ConfigureAwait(false);
|
await EnsureKeyboardShortcutSchemaAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var accountColumns = await Connection.GetTableInfoAsync(nameof(MailAccount)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!accountColumns.Any(c => c.Name == nameof(MailAccount.CreatedAt)))
|
||||||
|
{
|
||||||
|
await Connection
|
||||||
|
.ExecuteAsync($"ALTER TABLE {nameof(MailAccount)} ADD COLUMN {nameof(MailAccount.CreatedAt)} TEXT NULL")
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accountColumns.Any(c => c.Name == nameof(MailAccount.InitialSynchronizationRange)))
|
||||||
|
{
|
||||||
|
await Connection
|
||||||
|
.ExecuteAsync($"ALTER TABLE {nameof(MailAccount)} ADD COLUMN {nameof(MailAccount.InitialSynchronizationRange)} INTEGER NOT NULL DEFAULT {(int)InitialSynchronizationRange.SixMonths}")
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
var folderColumns = await Connection.GetTableInfoAsync(nameof(MailItemFolder)).ConfigureAwait(false);
|
var folderColumns = await Connection.GetTableInfoAsync(nameof(MailItemFolder)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!folderColumns.Any(c => c.Name == nameof(MailItemFolder.HighestKnownUid)))
|
if (!folderColumns.Any(c => c.Name == nameof(MailItemFolder.HighestKnownUid)))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public static class ServicesContainerSetup
|
|||||||
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
|
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
|
||||||
services.AddSingleton<IWinoLogger, WinoLogger>();
|
services.AddSingleton<IWinoLogger, WinoLogger>();
|
||||||
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
|
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
|
||||||
|
services.AddSingleton<IShareActivationService, ShareActivationService>();
|
||||||
services.AddSingleton<IMimeFileService, MimeFileService>();
|
services.AddSingleton<IMimeFileService, MimeFileService>();
|
||||||
services.AddSingleton<ICalendarIcsFileService, CalendarIcsFileService>();
|
services.AddSingleton<ICalendarIcsFileService, CalendarIcsFileService>();
|
||||||
services.AddTransient<IMimeStorageService, MimeStorageService>();
|
services.AddTransient<IMimeStorageService, MimeStorageService>();
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Launch;
|
||||||
|
|
||||||
|
namespace Wino.Services;
|
||||||
|
|
||||||
|
public class ShareActivationService : IShareActivationService
|
||||||
|
{
|
||||||
|
private readonly object _syncRoot = new();
|
||||||
|
private MailShareRequest? _pendingShareRequest;
|
||||||
|
private PendingComposeMailShareRequest? _pendingComposeShareRequest;
|
||||||
|
|
||||||
|
public MailShareRequest? PendingShareRequest
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
return _pendingShareRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
_pendingShareRequest = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailShareRequest? ConsumePendingShareRequest()
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
var pendingRequest = _pendingShareRequest;
|
||||||
|
_pendingShareRequest = null;
|
||||||
|
return pendingRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearPendingShareRequest()
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
_pendingShareRequest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StagePendingComposeShareRequest(Guid draftUniqueId, MailShareRequest shareRequest)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
_pendingComposeShareRequest = new PendingComposeMailShareRequest(draftUniqueId, shareRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailShareRequest? ConsumePendingComposeShareRequest(Guid draftUniqueId)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_pendingComposeShareRequest?.DraftUniqueId != draftUniqueId)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var pendingRequest = _pendingComposeShareRequest.ShareRequest;
|
||||||
|
_pendingComposeShareRequest = null;
|
||||||
|
return pendingRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -211,6 +211,8 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
|||||||
SpecialImapProvider = (SpecialImapProvider)mailbox.SpecialImapProvider,
|
SpecialImapProvider = (SpecialImapProvider)mailbox.SpecialImapProvider,
|
||||||
AccountColorHex = mailbox.AccountColorHex?.Trim(),
|
AccountColorHex = mailbox.AccountColorHex?.Trim(),
|
||||||
Base64ProfilePictureData = string.Empty,
|
Base64ProfilePictureData = string.Empty,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
InitialSynchronizationRange = InitialSynchronizationRange.SixMonths,
|
||||||
IsCalendarAccessGranted = mailbox.IsCalendarAccessGranted,
|
IsCalendarAccessGranted = mailbox.IsCalendarAccessGranted,
|
||||||
SynchronizationDeltaIdentifier = string.Empty,
|
SynchronizationDeltaIdentifier = string.Empty,
|
||||||
CalendarSynchronizationDeltaIdentifier = string.Empty,
|
CalendarSynchronizationDeltaIdentifier = string.Empty,
|
||||||
|
|||||||
Reference in New Issue
Block a user