Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb2335893c |
@@ -0,0 +1,125 @@
|
|||||||
|
name: PR WinUI Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
- ready_for_review
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-winui:
|
||||||
|
name: Build project (${{ matrix.platform }})
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
runs-on: windows-latest
|
||||||
|
continue-on-error: ${{ contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association) }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- platform: x86
|
||||||
|
rid: win-x86
|
||||||
|
- platform: x64
|
||||||
|
rid: win-x64
|
||||||
|
- platform: ARM64
|
||||||
|
rid: win-arm64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup .NET SDK
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: 10.0.x
|
||||||
|
source-url: https://nuget.pkg.github.com/bkaankose/index.json
|
||||||
|
env:
|
||||||
|
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Restore WinUI project dependencies
|
||||||
|
run: dotnet restore Wino.Mail.WinUI/Wino.Mail.WinUI.csproj --configfile nuget.config -p:Platform=${{ matrix.platform }} -p:RuntimeIdentifier=${{ matrix.rid }}
|
||||||
|
|
||||||
|
- name: Build WinUI project
|
||||||
|
run: dotnet build Wino.Mail.WinUI/Wino.Mail.WinUI.csproj --configuration Release --no-restore -p:Platform=${{ matrix.platform }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:GenerateAppxPackageOnBuild=false -p:AppxPackageSigningEnabled=false
|
||||||
|
|
||||||
|
core-tests:
|
||||||
|
name: Run Core tests
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
runs-on: windows-latest
|
||||||
|
continue-on-error: ${{ contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association) }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup .NET SDK
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: 10.0.x
|
||||||
|
source-url: https://nuget.pkg.github.com/bkaankose/index.json
|
||||||
|
env:
|
||||||
|
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Restore Core test projects
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$coreTests = Get-ChildItem -Path . -Recurse -Filter "*Core*.Tests.csproj" | ForEach-Object { $_.FullName }
|
||||||
|
if (-not $coreTests) {
|
||||||
|
throw "No Core test projects were found."
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($project in $coreTests) {
|
||||||
|
dotnet restore $project --configfile nuget.config
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Run Core test projects
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
New-Item -ItemType Directory -Path TestResults -Force | Out-Null
|
||||||
|
$coreTests = Get-ChildItem -Path . -Recurse -Filter "*Core*.Tests.csproj"
|
||||||
|
if (-not $coreTests) {
|
||||||
|
throw "No Core test projects were found."
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($project in $coreTests) {
|
||||||
|
$name = $project.BaseName
|
||||||
|
dotnet test $project.FullName --configuration Release --no-restore --verbosity normal --logger "trx;LogFileName=$name.trx" --results-directory TestResults
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Upload Core test result artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: core-test-results
|
||||||
|
path: TestResults/*.trx
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
|
- name: Publish Core test report
|
||||||
|
if: always()
|
||||||
|
uses: EnricoMi/publish-unit-test-result-action/windows@v2
|
||||||
|
with:
|
||||||
|
trx_files: TestResults/*.trx
|
||||||
|
check_name: Core test results
|
||||||
|
|
||||||
|
enforce-for-non-maintainers:
|
||||||
|
name: Enforce required checks (non-maintainers)
|
||||||
|
if: github.event.pull_request.draft == false && !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build-winui
|
||||||
|
- core-tests
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Fail when build or tests fail for non-maintainers
|
||||||
|
if: needs.build-winui.result != 'success' || needs.core-tests.result != 'success'
|
||||||
|
run: |
|
||||||
|
echo "WinUI build and Core tests must pass for non-maintainer pull requests."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Confirm build and test success for non-maintainers
|
||||||
|
run: echo "WinUI build and Core tests passed."
|
||||||
@@ -150,8 +150,6 @@ private string searchQuery = string.Empty;
|
|||||||
- For dependency properties in WinUI code, always prefer `[GeneratedDependencyProperty]` from CommunityToolkit over manual `DependencyProperty.Register(...)` declarations.
|
- For dependency properties in WinUI code, always prefer `[GeneratedDependencyProperty]` from CommunityToolkit over manual `DependencyProperty.Register(...)` declarations.
|
||||||
- When a `[RelayCommand]` needs enable/disable logic, prefer the command's `CanExecute` over binding `Button.IsEnabled` in XAML; use `[NotifyCanExecuteChangedFor]` on dependent properties and call `NotifyCanExecuteChanged()` explicitly when non-generated state affects the command.
|
- When a `[RelayCommand]` needs enable/disable logic, prefer the command's `CanExecute` over binding `Button.IsEnabled` in XAML; use `[NotifyCanExecuteChangedFor]` on dependent properties and call `NotifyCanExecuteChanged()` explicitly when non-generated state affects the command.
|
||||||
- In ViewModels, update all UI-bound properties/collections via `ExecuteUIThread(...)` (especially after awaited calls and any use of `ConfigureAwait(false)`).
|
- In ViewModels, update all UI-bound properties/collections via `ExecuteUIThread(...)` (especially after awaited calls and any use of `ConfigureAwait(false)`).
|
||||||
- `ConfigureAwait(false)` continues execution on a background thread. Any UI-bound property change, `INotifyPropertyChanged` notification, collection mutation, or similar UI-facing state update after that point must be marshaled back with `ExecuteUIThread(...)` or the appropriate dispatcher call, otherwise the app can crash.
|
|
||||||
- Messenger messages are raised from a background thread by default, while UI control event handlers such as `Button.Click` start on the UI thread. Be deliberate when combining dispatcher usage with `ConfigureAwait(false)` so post-await UI updates always return to the UI thread.
|
|
||||||
- ViewModels should only handle UI interaction/state and delegate business logic to services; account-management work belongs in `WinoAccountProfileService`, and preferences import/export/apply logic belongs in `PreferencesService`.
|
- ViewModels should only handle UI interaction/state and delegate business logic to services; account-management work belongs in `WinoAccountProfileService`, and preferences import/export/apply logic belongs in `PreferencesService`.
|
||||||
- In `EventDetailsPageViewModel.LoadAttendeesAsync`, never mutate `CurrentEvent.Attendees` outside `ExecuteUIThread(...)`.
|
- In `EventDetailsPageViewModel.LoadAttendeesAsync`, never mutate `CurrentEvent.Attendees` outside `ExecuteUIThread(...)`.
|
||||||
- Never create pure C# controls or controls that heavily manipulate UI structure from `.cs` files. Define controls in XAML and keep UI composition in XAML.
|
- Never create pure C# controls or controls that heavily manipulate UI structure from `.cs` files. Define controls in XAML and keep UI composition in XAML.
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Released on April 15. Second bugfix/improvements update for v2 beta testing.
|
||||||
|
|
||||||
|
- [Ability to delete custom themes](https://github.com/bkaankose/Wino-Mail/issues/844)
|
||||||
|
- [[Proposal] Reply/Reply all sets focus to the "To" line versus Body](https://github.com/bkaankose/Wino-Mail/issues/844274)
|
||||||
|
- Email categories. Online sync for Outlook, offline use for IMAP/Gmail.
|
||||||
|
- Handling of read-only calendars.
|
||||||
|
- Implemented a new Github action workflow to trigger beta releases on demand.
|
||||||
@@ -169,7 +169,6 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
|||||||
|
|
||||||
var activationContext = parameters as ShellModeActivationContext;
|
var activationContext = parameters as ShellModeActivationContext;
|
||||||
var shouldRunStartupFlows = activationContext?.IsInitialActivation ?? true;
|
var shouldRunStartupFlows = activationContext?.IsInitialActivation ?? true;
|
||||||
var navigationArgs = activationContext?.Parameter as CalendarPageNavigationArgs;
|
|
||||||
|
|
||||||
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
|
PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
|
||||||
PreferencesService.PreferenceChanged += PreferencesServiceChanged;
|
PreferencesService.PreferenceChanged += PreferencesServiceChanged;
|
||||||
@@ -179,14 +178,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
|||||||
await InitializeAccountCalendarsAsync();
|
await InitializeAccountCalendarsAsync();
|
||||||
ValidateConfiguredNewEventCalendar();
|
ValidateConfiguredNewEventCalendar();
|
||||||
|
|
||||||
if (navigationArgs != null)
|
TodayClicked();
|
||||||
{
|
|
||||||
NavigationService.Navigate(WinoPage.CalendarPage, navigationArgs);
|
|
||||||
}
|
|
||||||
else if (shouldRunStartupFlows || _calendarPageViewModel.CurrentVisibleRange == null)
|
|
||||||
{
|
|
||||||
TodayClicked();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApplyDisplayRequestAsync(CalendarDisplayRequest request, bool forceReload = false, CalendarItemTarget pendingTarget = null)
|
public async Task ApplyDisplayRequestAsync(CalendarDisplayRequest request, bool forceReload = false)
|
||||||
{
|
{
|
||||||
var lifetimeVersion = CurrentPageLifetimeVersion;
|
var lifetimeVersion = CurrentPageLifetimeVersion;
|
||||||
var hasLoadingLock = await WaitForCalendarLoadingLockAsync(lifetimeVersion).ConfigureAwait(false);
|
var hasLoadingLock = await WaitForCalendarLoadingLockAsync(lifetimeVersion).ConfigureAwait(false);
|
||||||
@@ -718,11 +718,6 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
await _notificationBuilder.ClearCalendarTaskbarBadgeAsync().ConfigureAwait(false);
|
await _notificationBuilder.ClearCalendarTaskbarBadgeAsync().ConfigureAwait(false);
|
||||||
_isCalendarBadgeClearedForPageLifetime = true;
|
_isCalendarBadgeClearedForPageLifetime = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadSucceeded && pendingTarget != null && IsPageActive(lifetimeVersion))
|
|
||||||
{
|
|
||||||
await NavigateToPendingCalendarTargetAsync(pendingTarget).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ReloadCurrentVisibleRangeAsync()
|
public Task ReloadCurrentVisibleRangeAsync()
|
||||||
@@ -750,31 +745,6 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
NavigateEvent(new CalendarItemViewModel(calendarItem), CalendarEventTargetType.Single);
|
NavigateEvent(new CalendarItemViewModel(calendarItem), CalendarEventTargetType.Single);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task NavigateToPendingCalendarTargetAsync(CalendarItemTarget target)
|
|
||||||
{
|
|
||||||
CalendarItemViewModel calendarItemViewModel = null;
|
|
||||||
|
|
||||||
if (_loadedCalendarItems.TryGetValue(target.Item.Id, out var loadedCalendarItemViewModel))
|
|
||||||
{
|
|
||||||
calendarItemViewModel = loadedCalendarItemViewModel;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var targetItem = await _calendarService.GetCalendarItemTargetAsync(target).ConfigureAwait(false);
|
|
||||||
if (targetItem == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
targetItem.AssignedCalendar ??= AccountCalendarStateService.ActiveCalendars.FirstOrDefault(calendar => calendar.Id == targetItem.CalendarId);
|
|
||||||
calendarItemViewModel = new CalendarItemViewModel(targetItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
await ExecuteUIThread(() =>
|
|
||||||
{
|
|
||||||
DisplayDetailsCalendarItemViewModel = calendarItemViewModel;
|
|
||||||
NavigateEvent(calendarItemViewModel, target.TargetType);
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<CalendarItemViewModel>> LoadCalendarItemsAsync(DateRange loadedDateWindow, long lifetimeVersion)
|
private async Task<List<CalendarItemViewModel>> LoadCalendarItemsAsync(DateRange loadedDateWindow, long lifetimeVersion)
|
||||||
{
|
{
|
||||||
var loadedItems = new Dictionary<Guid, CalendarItemViewModel>();
|
var loadedItems = new Dictionary<Guid, CalendarItemViewModel>();
|
||||||
@@ -849,7 +819,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async void Receive(LoadCalendarMessage message)
|
public async void Receive(LoadCalendarMessage message)
|
||||||
=> await ApplyDisplayRequestAsync(message.DisplayRequest, message.ForceReload, message.PendingTarget);
|
=> await ApplyDisplayRequestAsync(message.DisplayRequest, message.ForceReload);
|
||||||
|
|
||||||
public void Receive(CalendarSettingsUpdatedMessage message)
|
public void Receive(CalendarSettingsUpdatedMessage message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ public static class Constants
|
|||||||
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);
|
||||||
public const string ToastDismissActionKey = nameof(ToastDismissActionKey);
|
|
||||||
public const string ToastStoreUpdateActionKey = nameof(ToastStoreUpdateActionKey);
|
public const string ToastStoreUpdateActionKey = nameof(ToastStoreUpdateActionKey);
|
||||||
public const string ToastStoreUpdateActionInstall = nameof(ToastStoreUpdateActionInstall);
|
public const string ToastStoreUpdateActionInstall = nameof(ToastStoreUpdateActionInstall);
|
||||||
public const string ClientLogFile = "Client_.log";
|
public const string ClientLogFile = "Client_.log";
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public enum WinoPage
|
|||||||
AboutPage,
|
AboutPage,
|
||||||
PersonalizationPage,
|
PersonalizationPage,
|
||||||
MessageListPage,
|
MessageListPage,
|
||||||
MailNotificationSettingsPage,
|
|
||||||
MailListPage,
|
MailListPage,
|
||||||
ReadComposePanePage,
|
ReadComposePanePage,
|
||||||
AppPreferencesPage,
|
AppPreferencesPage,
|
||||||
|
|||||||
@@ -192,16 +192,6 @@ public interface IPreferencesService : INotifyPropertyChanged
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Guid? StartupEntityId { get; set; }
|
Guid? StartupEntityId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setting: First action button displayed on mail toast notifications.
|
|
||||||
/// </summary>
|
|
||||||
MailOperation FirstMailNotificationAction { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setting: Second action button displayed on mail toast notifications.
|
|
||||||
/// </summary>
|
|
||||||
MailOperation SecondMailNotificationAction { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#nullable enable
|
using System;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Calendar;
|
namespace Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
@@ -19,9 +18,4 @@ public class CalendarPageNavigationArgs
|
|||||||
/// Force reloading the calendar data even when the target range does not change.
|
/// Force reloading the calendar data even when the target range does not change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ForceReload { get; set; }
|
public bool ForceReload { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional event target to navigate to after the calendar page loads the requested range.
|
|
||||||
/// </summary>
|
|
||||||
public CalendarItemTarget? PendingTarget { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,11 +68,6 @@ public static class SettingsNavigationInfoProvider
|
|||||||
Translator.SettingsMessageList_Description,
|
Translator.SettingsMessageList_Description,
|
||||||
"\uE8C4",
|
"\uE8C4",
|
||||||
searchKeywords: Translator.SettingsSearch_MessageList_Keywords),
|
searchKeywords: Translator.SettingsSearch_MessageList_Keywords),
|
||||||
new(WinoPage.MailNotificationSettingsPage,
|
|
||||||
Translator.SettingsMailNotifications_Title,
|
|
||||||
Translator.SettingsMailNotifications_Description,
|
|
||||||
"\uE7F4",
|
|
||||||
searchKeywords: Translator.SettingsSearch_MailNotifications_Keywords),
|
|
||||||
new(WinoPage.ReadComposePanePage,
|
new(WinoPage.ReadComposePanePage,
|
||||||
Translator.SettingsReadComposePane_Title,
|
Translator.SettingsReadComposePane_Title,
|
||||||
Translator.SettingsReadComposePane_Description,
|
Translator.SettingsReadComposePane_Description,
|
||||||
@@ -154,7 +149,6 @@ public static class SettingsNavigationInfoProvider
|
|||||||
WinoPage.PersonalizationPage => Translator.SettingsPersonalization_Title,
|
WinoPage.PersonalizationPage => Translator.SettingsPersonalization_Title,
|
||||||
WinoPage.AboutPage => Translator.SettingsAbout_Title,
|
WinoPage.AboutPage => Translator.SettingsAbout_Title,
|
||||||
WinoPage.MessageListPage => Translator.SettingsMessageList_Title,
|
WinoPage.MessageListPage => Translator.SettingsMessageList_Title,
|
||||||
WinoPage.MailNotificationSettingsPage => Translator.SettingsMailNotifications_Title,
|
|
||||||
WinoPage.ReadComposePanePage => Translator.SettingsReadComposePane_Title,
|
WinoPage.ReadComposePanePage => Translator.SettingsReadComposePane_Title,
|
||||||
WinoPage.AppPreferencesPage => Translator.SettingsAppPreferences_Title,
|
WinoPage.AppPreferencesPage => Translator.SettingsAppPreferences_Title,
|
||||||
WinoPage.CalendarSettingsPage => Translator.CalendarSettings_Preferences_Title,
|
WinoPage.CalendarSettingsPage => Translator.CalendarSettings_Preferences_Title,
|
||||||
|
|||||||
@@ -84,7 +84,6 @@
|
|||||||
"Buttons_Delete": "Delete",
|
"Buttons_Delete": "Delete",
|
||||||
"Buttons_Deny": "Deny",
|
"Buttons_Deny": "Deny",
|
||||||
"Buttons_Discard": "Discard",
|
"Buttons_Discard": "Discard",
|
||||||
"Buttons_Dismiss": "Dismiss",
|
|
||||||
"Buttons_Edit": "Edit",
|
"Buttons_Edit": "Edit",
|
||||||
"Buttons_EnableImageRendering": "Enable",
|
"Buttons_EnableImageRendering": "Enable",
|
||||||
"Buttons_Multiselect": "Select Multiple",
|
"Buttons_Multiselect": "Select Multiple",
|
||||||
@@ -911,14 +910,6 @@
|
|||||||
"SettingsMarkAsRead_WhenSelected": "When selected",
|
"SettingsMarkAsRead_WhenSelected": "When selected",
|
||||||
"SettingsMessageList_Description": "Change how your messages should be organized in mail list.",
|
"SettingsMessageList_Description": "Change how your messages should be organized in mail list.",
|
||||||
"SettingsMessageList_Title": "Message List",
|
"SettingsMessageList_Title": "Message List",
|
||||||
"SettingsMailNotifications_Title": "Notifications",
|
|
||||||
"SettingsMailNotifications_Description": "Notification settings and preferences for mails.",
|
|
||||||
"SettingsMailNotifications_Actions_Title": "App notification actions.",
|
|
||||||
"SettingsMailNotifications_Actions_Description": "Customize the button behaviors on the notifications as you like.",
|
|
||||||
"SettingsMailNotifications_FirstAction_Title": "First notification action",
|
|
||||||
"SettingsMailNotifications_FirstAction_Description": "Choose the first button shown on mail notifications.",
|
|
||||||
"SettingsMailNotifications_SecondAction_Title": "Second notification action",
|
|
||||||
"SettingsMailNotifications_SecondAction_Description": "Choose the second button shown on mail notifications.",
|
|
||||||
"SettingsNoAccountSetupMessage": "You didn't setup any accounts yet.",
|
"SettingsNoAccountSetupMessage": "You didn't setup any accounts yet.",
|
||||||
"SettingsNotifications_Description": "Turn on or off notifications for this account.",
|
"SettingsNotifications_Description": "Turn on or off notifications for this account.",
|
||||||
"SettingsNotifications_Title": "Notifications",
|
"SettingsNotifications_Title": "Notifications",
|
||||||
@@ -955,7 +946,6 @@
|
|||||||
"SettingsSearch_About_Keywords": "about;version;website;privacy;github;donate;store;support",
|
"SettingsSearch_About_Keywords": "about;version;website;privacy;github;donate;store;support",
|
||||||
"SettingsSearch_KeyboardShortcuts_Keywords": "shortcut;shortcuts;hotkey;hotkeys;keyboard;keys",
|
"SettingsSearch_KeyboardShortcuts_Keywords": "shortcut;shortcuts;hotkey;hotkeys;keyboard;keys",
|
||||||
"SettingsSearch_MessageList_Keywords": "message;messages;list;threading;threads;avatar;preview;sender",
|
"SettingsSearch_MessageList_Keywords": "message;messages;list;threading;threads;avatar;preview;sender",
|
||||||
"SettingsSearch_MailNotifications_Keywords": "mail;notification;notifications;toast;action;actions;reply;reply all;forward;archive;delete;junk;read",
|
|
||||||
"SettingsSearch_ReadComposePane_Keywords": "reader;compose;composer;font;fonts;external content;display;reading",
|
"SettingsSearch_ReadComposePane_Keywords": "reader;compose;composer;font;fonts;external content;display;reading",
|
||||||
"SettingsSearch_SignatureAndEncryption_Keywords": "signature;signatures;encryption;certificate;certificates;s mime;smime;security",
|
"SettingsSearch_SignatureAndEncryption_Keywords": "signature;signatures;encryption;certificate;certificates;s mime;smime;security",
|
||||||
"SettingsSearch_Storage_Keywords": "storage;cache;caching;mime;disk;space;cleanup;clean up;local data",
|
"SettingsSearch_Storage_Keywords": "storage;cache;caching;mime;disk;space;cleanup;clean up;local data",
|
||||||
|
|||||||
@@ -14,32 +14,27 @@ namespace Wino.Core.Extensions;
|
|||||||
|
|
||||||
public static class GoogleIntegratorExtensions
|
public static class GoogleIntegratorExtensions
|
||||||
{
|
{
|
||||||
private static bool TryGetKnownFolderLabelName(string labelName, out string normalizedLabelName)
|
private static string GetNormalizedLabelName(string labelName)
|
||||||
{
|
{
|
||||||
normalizedLabelName = string.Empty;
|
// 1. Remove CATEGORY_ prefix.
|
||||||
|
var normalizedLabelName = labelName.Replace(ServiceConstants.CATEGORY_PREFIX, string.Empty);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(labelName))
|
// 2. Normalize label name by capitalizing first letter.
|
||||||
return false;
|
normalizedLabelName = char.ToUpper(normalizedLabelName[0]) + normalizedLabelName.Substring(1).ToLower();
|
||||||
|
|
||||||
var knownFolderKey = labelName.Replace(ServiceConstants.CATEGORY_PREFIX, string.Empty);
|
return normalizedLabelName;
|
||||||
|
|
||||||
if (!ServiceConstants.KnownFolderDictionary.ContainsKey(knownFolderKey))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
normalizedLabelName = char.ToUpper(knownFolderKey[0]) + knownFolderKey.Substring(1).ToLower();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MailItemFolder GetLocalFolder(this Label label, ListLabelsResponse labelsResponse, Guid accountId)
|
public static MailItemFolder GetLocalFolder(this Label label, ListLabelsResponse labelsResponse, Guid accountId)
|
||||||
{
|
{
|
||||||
var folderName = GetFolderName(label.Name);
|
var normalizedLabelName = GetFolderName(label.Name);
|
||||||
|
|
||||||
var lookupLabelName = GetLookupLabelName(label.Name);
|
// Even though we normalize the label name, check is done by capitalizing the label name.
|
||||||
|
var capitalNormalizedLabelName = normalizedLabelName.ToUpper();
|
||||||
|
|
||||||
bool isSpecialFolder = ServiceConstants.KnownFolderDictionary.ContainsKey(lookupLabelName);
|
bool isSpecialFolder = ServiceConstants.KnownFolderDictionary.ContainsKey(capitalNormalizedLabelName);
|
||||||
|
|
||||||
var specialFolderType = isSpecialFolder ? ServiceConstants.KnownFolderDictionary[lookupLabelName] : SpecialFolderType.Other;
|
var specialFolderType = isSpecialFolder ? ServiceConstants.KnownFolderDictionary[capitalNormalizedLabelName] : SpecialFolderType.Other;
|
||||||
|
|
||||||
// We used to support FOLDER_HIDE_IDENTIFIER to hide invisible folders.
|
// We used to support FOLDER_HIDE_IDENTIFIER to hide invisible folders.
|
||||||
// However, a lot of people complained that they don't see their folders after the initial sync
|
// However, a lot of people complained that they don't see their folders after the initial sync
|
||||||
@@ -64,7 +59,7 @@ public static class GoogleIntegratorExtensions
|
|||||||
{
|
{
|
||||||
TextColorHex = label.Color?.TextColor,
|
TextColorHex = label.Color?.TextColor,
|
||||||
BackgroundColorHex = label.Color?.BackgroundColor,
|
BackgroundColorHex = label.Color?.BackgroundColor,
|
||||||
FolderName = folderName,
|
FolderName = normalizedLabelName,
|
||||||
RemoteFolderId = label.Id,
|
RemoteFolderId = label.Id,
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
MailAccountId = accountId,
|
MailAccountId = accountId,
|
||||||
@@ -109,29 +104,7 @@ public static class GoogleIntegratorExtensions
|
|||||||
return labelsResponse.Labels.FirstOrDefault(a => a.Name == parentLabelName)?.Id ?? string.Empty;
|
return labelsResponse.Labels.FirstOrDefault(a => a.Name == parentLabelName)?.Id ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetLookupLabelName(string fullFolderName)
|
|
||||||
{
|
|
||||||
var folderName = GetLastFolderName(fullFolderName);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(folderName))
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
return folderName.Replace(ServiceConstants.CATEGORY_PREFIX, string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetFolderName(string fullFolderName)
|
public static string GetFolderName(string fullFolderName)
|
||||||
{
|
|
||||||
var lastPart = GetLastFolderName(fullFolderName);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(lastPart))
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
return TryGetKnownFolderLabelName(lastPart, out var normalizedLabelName)
|
|
||||||
? normalizedLabelName
|
|
||||||
: lastPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetLastFolderName(string fullFolderName)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(fullFolderName)) return string.Empty;
|
if (string.IsNullOrEmpty(fullFolderName)) return string.Empty;
|
||||||
|
|
||||||
@@ -140,7 +113,9 @@ public static class GoogleIntegratorExtensions
|
|||||||
|
|
||||||
string[] parts = fullFolderName.Split(ServiceConstants.FOLDER_SEPERATOR_CHAR);
|
string[] parts = fullFolderName.Split(ServiceConstants.FOLDER_SEPERATOR_CHAR);
|
||||||
|
|
||||||
return parts[parts.Length - 1];
|
var lastPart = parts[parts.Length - 1];
|
||||||
|
|
||||||
|
return GetNormalizedLabelName(lastPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<RemoteAccountAlias> GetRemoteAliases(this ListSendAsResponse response)
|
public static List<RemoteAccountAlias> GetRemoteAliases(this ListSendAsResponse response)
|
||||||
|
|||||||
@@ -904,7 +904,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
if (ShouldUpdateFolder(remoteFolder, existingLocalFolder))
|
if (ShouldUpdateFolder(remoteFolder, existingLocalFolder))
|
||||||
{
|
{
|
||||||
existingLocalFolder.FolderName = GoogleIntegratorExtensions.GetFolderName(remoteFolder.Name);
|
existingLocalFolder.FolderName = remoteFolder.Name;
|
||||||
existingLocalFolder.TextColorHex = remoteFolder.Color?.TextColor;
|
existingLocalFolder.TextColorHex = remoteFolder.Color?.TextColor;
|
||||||
existingLocalFolder.BackgroundColorHex = remoteFolder.Color?.BackgroundColor;
|
existingLocalFolder.BackgroundColorHex = remoteFolder.Color?.BackgroundColor;
|
||||||
|
|
||||||
@@ -998,9 +998,9 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
private bool ShouldUpdateFolder(Label remoteFolder, MailItemFolder existingLocalFolder)
|
private bool ShouldUpdateFolder(Label remoteFolder, MailItemFolder existingLocalFolder)
|
||||||
{
|
{
|
||||||
var remoteFolderName = GoogleIntegratorExtensions.GetFolderName(remoteFolder.Name);
|
var remoteFolderName = GoogleIntegratorExtensions.GetFolderName(remoteFolder.Name);
|
||||||
var localFolderName = existingLocalFolder.FolderName ?? string.Empty;
|
var localFolderName = GoogleIntegratorExtensions.GetFolderName(existingLocalFolder.FolderName);
|
||||||
|
|
||||||
bool isNameChanged = !localFolderName.Equals(remoteFolderName, StringComparison.Ordinal);
|
bool isNameChanged = !localFolderName.Equals(remoteFolderName, StringComparison.OrdinalIgnoreCase);
|
||||||
bool isColorChanged = existingLocalFolder.BackgroundColorHex != remoteFolder.Color?.BackgroundColor ||
|
bool isColorChanged = existingLocalFolder.BackgroundColorHex != remoteFolder.Color?.BackgroundColor ||
|
||||||
existingLocalFolder.TextColorHex != remoteFolder.Color?.TextColor;
|
existingLocalFolder.TextColorHex != remoteFolder.Color?.TextColor;
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using Wino.Core.Domain.Models.MailItem;
|
|||||||
using Wino.Core.Domain.Models.Menus;
|
using Wino.Core.Domain.Models.Menus;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Reader;
|
using Wino.Core.Domain.Models.Reader;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Core.Requests.Mail;
|
using Wino.Core.Requests.Mail;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.ViewModels.Collections;
|
using Wino.Mail.ViewModels.Collections;
|
||||||
@@ -485,29 +486,26 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
if (!CanSynchronize) return;
|
if (!CanSynchronize) return;
|
||||||
|
|
||||||
_notificationBuilder.CreateNotificationsAsync(MailCollection.SelectedItems.Select(a => a.MailCopy));
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Only synchronize listed folders.
|
// Only synchronize listed folders.
|
||||||
|
|
||||||
// When doing linked inbox sync, we need to save the sync id to report progress back only once.
|
// When doing linked inbox sync, we need to save the sync id to report progress back only once.
|
||||||
// Otherwise, we will report progress for each folder and that's what we don't want.
|
// Otherwise, we will report progress for each folder and that's what we don't want.
|
||||||
|
|
||||||
//trackingSynchronizationId = Guid.NewGuid();
|
trackingSynchronizationId = Guid.NewGuid();
|
||||||
//completedTrackingSynchronizationCount = 0;
|
completedTrackingSynchronizationCount = 0;
|
||||||
|
|
||||||
//foreach (var folder in ActiveFolder.HandlingFolders)
|
foreach (var folder in ActiveFolder.HandlingFolders)
|
||||||
//{
|
{
|
||||||
// var options = new MailSynchronizationOptions()
|
var options = new MailSynchronizationOptions()
|
||||||
// {
|
{
|
||||||
// AccountId = folder.MailAccountId,
|
AccountId = folder.MailAccountId,
|
||||||
// Type = MailSynchronizationType.CustomFolders,
|
Type = MailSynchronizationType.CustomFolders,
|
||||||
// SynchronizationFolderIds = [folder.Id],
|
SynchronizationFolderIds = [folder.Id],
|
||||||
// GroupedSynchronizationTrackingId = trackingSynchronizationId
|
GroupedSynchronizationTrackingId = trackingSynchronizationId
|
||||||
// };
|
};
|
||||||
|
|
||||||
// Messenger.Send(new NewMailSynchronizationRequested(options));
|
Messenger.Send(new NewMailSynchronizationRequested(options));
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
using Wino.Core.Domain;
|
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
|
||||||
|
|
||||||
public partial class MailNotificationSettingsPageViewModel : MailBaseViewModel
|
|
||||||
{
|
|
||||||
private static readonly MailOperation[] SupportedMailNotificationActions =
|
|
||||||
[
|
|
||||||
MailOperation.MarkAsRead,
|
|
||||||
MailOperation.SoftDelete,
|
|
||||||
MailOperation.MoveToJunk,
|
|
||||||
MailOperation.Archive,
|
|
||||||
MailOperation.Reply,
|
|
||||||
MailOperation.ReplyAll,
|
|
||||||
MailOperation.Forward
|
|
||||||
];
|
|
||||||
|
|
||||||
private readonly IPreferencesService _preferencesService;
|
|
||||||
private bool _isUpdatingSelection;
|
|
||||||
private bool _isLoaded;
|
|
||||||
|
|
||||||
public ObservableCollection<MailNotificationActionOption> AvailableNotificationActions { get; } = [];
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MailNotificationActionOption SelectedFirstAction { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MailNotificationActionOption SelectedSecondAction { get; set; }
|
|
||||||
|
|
||||||
public MailNotificationSettingsPageViewModel(IPreferencesService preferencesService)
|
|
||||||
{
|
|
||||||
_preferencesService = preferencesService;
|
|
||||||
|
|
||||||
foreach (var action in SupportedMailNotificationActions)
|
|
||||||
{
|
|
||||||
AvailableNotificationActions.Add(new MailNotificationActionOption(action, GetOperationDisplayText(action)));
|
|
||||||
}
|
|
||||||
|
|
||||||
InitializeSelections();
|
|
||||||
_isLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnSelectedFirstActionChanged(MailNotificationActionOption value)
|
|
||||||
{
|
|
||||||
if (!_isLoaded || _isUpdatingSelection || value == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
EnsureDistinctSelections(changedSelection: value, isFirstSelection: true);
|
|
||||||
_preferencesService.FirstMailNotificationAction = value.Operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnSelectedSecondActionChanged(MailNotificationActionOption value)
|
|
||||||
{
|
|
||||||
if (!_isLoaded || _isUpdatingSelection || value == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
EnsureDistinctSelections(changedSelection: value, isFirstSelection: false);
|
|
||||||
_preferencesService.SecondMailNotificationAction = value.Operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeSelections()
|
|
||||||
{
|
|
||||||
var firstAction = ResolveSupportedAction(_preferencesService.FirstMailNotificationAction, MailOperation.MarkAsRead);
|
|
||||||
var secondAction = ResolveSupportedAction(_preferencesService.SecondMailNotificationAction, MailOperation.SoftDelete);
|
|
||||||
|
|
||||||
if (secondAction == firstAction)
|
|
||||||
{
|
|
||||||
secondAction = GetFallbackDistinctAction(firstAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectedFirstAction = GetOption(firstAction);
|
|
||||||
SelectedSecondAction = GetOption(secondAction);
|
|
||||||
|
|
||||||
_preferencesService.FirstMailNotificationAction = firstAction;
|
|
||||||
_preferencesService.SecondMailNotificationAction = secondAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureDistinctSelections(MailNotificationActionOption changedSelection, bool isFirstSelection)
|
|
||||||
{
|
|
||||||
var otherSelection = isFirstSelection ? SelectedSecondAction : SelectedFirstAction;
|
|
||||||
if (otherSelection?.Operation != changedSelection.Operation)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_isUpdatingSelection = true;
|
|
||||||
|
|
||||||
var fallbackAction = GetFallbackDistinctAction(changedSelection.Operation);
|
|
||||||
var fallbackOption = GetOption(fallbackAction);
|
|
||||||
|
|
||||||
if (isFirstSelection)
|
|
||||||
{
|
|
||||||
SelectedSecondAction = fallbackOption;
|
|
||||||
_preferencesService.SecondMailNotificationAction = fallbackAction;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedFirstAction = fallbackOption;
|
|
||||||
_preferencesService.FirstMailNotificationAction = fallbackAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isUpdatingSelection = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MailNotificationActionOption GetOption(MailOperation action)
|
|
||||||
=> AvailableNotificationActions.First(option => option.Operation == action);
|
|
||||||
|
|
||||||
private static MailOperation ResolveSupportedAction(MailOperation action, MailOperation fallbackAction)
|
|
||||||
=> SupportedMailNotificationActions.Contains(action) ? action : fallbackAction;
|
|
||||||
|
|
||||||
private static MailOperation GetFallbackDistinctAction(MailOperation excludedAction)
|
|
||||||
=> SupportedMailNotificationActions.First(action => action != excludedAction);
|
|
||||||
|
|
||||||
private static string GetOperationDisplayText(MailOperation action)
|
|
||||||
=> action switch
|
|
||||||
{
|
|
||||||
MailOperation.MarkAsRead => Translator.MailOperation_MarkAsRead,
|
|
||||||
MailOperation.SoftDelete => Translator.MailOperation_Delete,
|
|
||||||
MailOperation.MoveToJunk => Translator.MailOperation_MarkAsJunk,
|
|
||||||
MailOperation.Archive => Translator.MailOperation_Archive,
|
|
||||||
MailOperation.Reply => Translator.MailOperation_Reply,
|
|
||||||
MailOperation.ReplyAll => Translator.MailOperation_ReplyAll,
|
|
||||||
MailOperation.Forward => Translator.MailOperation_Forward,
|
|
||||||
_ => action.ToString()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class MailNotificationActionOption(MailOperation operation, string displayText)
|
|
||||||
{
|
|
||||||
public MailOperation Operation { get; } = operation;
|
|
||||||
public string DisplayText { get; } = displayText;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Windows.AppNotifications;
|
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Activation;
|
|
||||||
|
|
||||||
internal sealed record BufferedAppNotificationActivation(string Argument, IReadOnlyDictionary<string, string>? UserInput);
|
|
||||||
|
|
||||||
internal sealed class AppNotificationActivationBuffer
|
|
||||||
{
|
|
||||||
private readonly ConcurrentQueue<BufferedAppNotificationActivation> _pendingActivations = new();
|
|
||||||
private readonly SemaphoreSlim _pendingSignal = new(0);
|
|
||||||
|
|
||||||
public void Enqueue(AppNotificationActivatedEventArgs args)
|
|
||||||
{
|
|
||||||
var copiedUserInput = args.UserInput == null
|
|
||||||
? null
|
|
||||||
: new Dictionary<string, string>(args.UserInput, StringComparer.Ordinal);
|
|
||||||
|
|
||||||
_pendingActivations.Enqueue(new BufferedAppNotificationActivation(args.Argument, copiedUserInput));
|
|
||||||
_pendingSignal.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDequeue(out BufferedAppNotificationActivation activation)
|
|
||||||
=> _pendingActivations.TryDequeue(out activation!);
|
|
||||||
|
|
||||||
public async Task<BufferedAppNotificationActivation?> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (!_pendingActivations.IsEmpty && TryDequeue(out var queuedActivation))
|
|
||||||
return queuedActivation;
|
|
||||||
|
|
||||||
if (!await _pendingSignal.WaitAsync(timeout, cancellationToken))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return TryDequeue(out var activation) ? activation : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.Windows.AppLifecycle;
|
|
||||||
using Windows.ApplicationModel;
|
|
||||||
using Windows.ApplicationModel.Activation;
|
|
||||||
using Windows.Storage;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Activation;
|
|
||||||
|
|
||||||
internal enum PendingBootstrapActivationKind
|
|
||||||
{
|
|
||||||
Launch,
|
|
||||||
Protocol,
|
|
||||||
File
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class PendingBootstrapActivation
|
|
||||||
{
|
|
||||||
public PendingBootstrapActivationKind Kind { get; init; }
|
|
||||||
public WinoApplicationMode Mode { get; init; } = WinoApplicationMode.Mail;
|
|
||||||
public string? LaunchArguments { get; init; }
|
|
||||||
public string? TileId { get; init; }
|
|
||||||
public string? ProtocolUri { get; init; }
|
|
||||||
public string[] FilePaths { get; init; } = [];
|
|
||||||
public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class CalendarEntryBootstrapActivation
|
|
||||||
{
|
|
||||||
private const string PendingActivationKey = "PendingCalendarEntryBootstrapActivation";
|
|
||||||
private const string KindKey = "Kind";
|
|
||||||
private const string ModeKey = "Mode";
|
|
||||||
private const string LaunchArgumentsKey = "LaunchArguments";
|
|
||||||
private const string TileIdKey = "TileId";
|
|
||||||
private const string ProtocolUriKey = "ProtocolUri";
|
|
||||||
private const string FilePathsKey = "FilePaths";
|
|
||||||
private const string CreatedAtUtcKey = "CreatedAtUtc";
|
|
||||||
private static readonly TimeSpan PendingActivationLifetime = TimeSpan.FromMinutes(1);
|
|
||||||
|
|
||||||
public static bool ShouldBootstrapToMailHost(AppActivationArguments activationArgs)
|
|
||||||
=> TryCreatePendingActivation(activationArgs, out _);
|
|
||||||
|
|
||||||
public static bool QueuePendingActivation(AppActivationArguments activationArgs)
|
|
||||||
{
|
|
||||||
if (!TryCreatePendingActivation(activationArgs, out var pendingActivation))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
ApplicationData.Current.LocalSettings.Values[PendingActivationKey] = CreateCompositeValue(pendingActivation!);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ClearPendingActivation()
|
|
||||||
=> ApplicationData.Current.LocalSettings.Values.Remove(PendingActivationKey);
|
|
||||||
|
|
||||||
public static PendingBootstrapActivation? ConsumePendingActivation()
|
|
||||||
{
|
|
||||||
if (!ApplicationData.Current.LocalSettings.Values.TryGetValue(PendingActivationKey, out var pendingActivationValue) ||
|
|
||||||
pendingActivationValue is not ApplicationDataCompositeValue compositeValue)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearPendingActivation();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pendingActivation = ParseCompositeValue(compositeValue);
|
|
||||||
if (pendingActivation == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (DateTimeOffset.UtcNow - pendingActivation.CreatedAtUtc > PendingActivationLifetime)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return pendingActivation;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ApplicationDataCompositeValue CreateCompositeValue(PendingBootstrapActivation pendingActivation)
|
|
||||||
{
|
|
||||||
var compositeValue = new ApplicationDataCompositeValue
|
|
||||||
{
|
|
||||||
[KindKey] = pendingActivation.Kind.ToString(),
|
|
||||||
[ModeKey] = pendingActivation.Mode.ToString(),
|
|
||||||
[LaunchArgumentsKey] = pendingActivation.LaunchArguments ?? string.Empty,
|
|
||||||
[TileIdKey] = pendingActivation.TileId ?? string.Empty,
|
|
||||||
[ProtocolUriKey] = pendingActivation.ProtocolUri ?? string.Empty,
|
|
||||||
[FilePathsKey] = string.Join("\n", pendingActivation.FilePaths),
|
|
||||||
[CreatedAtUtcKey] = pendingActivation.CreatedAtUtc.ToString("o")
|
|
||||||
};
|
|
||||||
|
|
||||||
return compositeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PendingBootstrapActivation? ParseCompositeValue(ApplicationDataCompositeValue compositeValue)
|
|
||||||
{
|
|
||||||
if (!Enum.TryParse(compositeValue[KindKey]?.ToString(), ignoreCase: true, out PendingBootstrapActivationKind kind) ||
|
|
||||||
!Enum.TryParse(compositeValue[ModeKey]?.ToString(), ignoreCase: true, out WinoApplicationMode mode) ||
|
|
||||||
!DateTimeOffset.TryParse(compositeValue[CreatedAtUtcKey]?.ToString(), out var createdAtUtc))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PendingBootstrapActivation
|
|
||||||
{
|
|
||||||
Kind = kind,
|
|
||||||
Mode = mode,
|
|
||||||
LaunchArguments = GetOptionalCompositeString(compositeValue, LaunchArgumentsKey),
|
|
||||||
TileId = GetOptionalCompositeString(compositeValue, TileIdKey),
|
|
||||||
ProtocolUri = GetOptionalCompositeString(compositeValue, ProtocolUriKey),
|
|
||||||
FilePaths = GetOptionalCompositeString(compositeValue, FilePathsKey)?
|
|
||||||
.Split(['\n'], StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToArray() ?? [],
|
|
||||||
CreatedAtUtc = createdAtUtc
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? GetOptionalCompositeString(ApplicationDataCompositeValue compositeValue, string key)
|
|
||||||
{
|
|
||||||
if (!compositeValue.TryGetValue(key, out var value))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var stringValue = value?.ToString();
|
|
||||||
return string.IsNullOrWhiteSpace(stringValue) ? null : stringValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool LaunchMailHost()
|
|
||||||
{
|
|
||||||
var mailAppUserModelId = AppEntryConstants.GetAppUserModelId(WinoApplicationMode.Mail);
|
|
||||||
var appEntries = Package.Current.GetAppListEntriesAsync().AsTask().GetAwaiter().GetResult();
|
|
||||||
var mailEntry = appEntries.FirstOrDefault(entry =>
|
|
||||||
string.Equals(entry.AppUserModelId, mailAppUserModelId, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
return mailEntry != null && mailEntry.LaunchAsync().AsTask().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryCreatePendingActivation(AppActivationArguments activationArgs, out PendingBootstrapActivation? pendingActivation)
|
|
||||||
{
|
|
||||||
pendingActivation = null;
|
|
||||||
|
|
||||||
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
|
||||||
activationArgs.Data is ILaunchActivatedEventArgs launchArgs)
|
|
||||||
{
|
|
||||||
var resolvedMode = AppModeActivationResolver.Resolve(launchArgs.Arguments, launchArgs.TileId, Environment.CommandLine);
|
|
||||||
if (resolvedMode != WinoApplicationMode.Calendar)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
pendingActivation = new PendingBootstrapActivation
|
|
||||||
{
|
|
||||||
Kind = PendingBootstrapActivationKind.Launch,
|
|
||||||
Mode = resolvedMode,
|
|
||||||
LaunchArguments = launchArgs.Arguments,
|
|
||||||
TileId = launchArgs.TileId
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activationArgs.Kind == ExtendedActivationKind.Protocol &&
|
|
||||||
activationArgs.Data is IProtocolActivatedEventArgs protocolArgs &&
|
|
||||||
protocolArgs.Uri != null &&
|
|
||||||
(string.Equals(protocolArgs.Uri.Scheme, "webcal", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(protocolArgs.Uri.Scheme, "webcals", StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
|
||||||
pendingActivation = new PendingBootstrapActivation
|
|
||||||
{
|
|
||||||
Kind = PendingBootstrapActivationKind.Protocol,
|
|
||||||
Mode = WinoApplicationMode.Calendar,
|
|
||||||
ProtocolUri = protocolArgs.Uri.AbsoluteUri
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activationArgs.Kind == ExtendedActivationKind.File &&
|
|
||||||
activationArgs.Data is IFileActivatedEventArgs fileArgs)
|
|
||||||
{
|
|
||||||
var filePaths = fileArgs.Files?
|
|
||||||
.OfType<IStorageItem>()
|
|
||||||
.Where(item => string.Equals(Path.GetExtension(item.Path), ".ics", StringComparison.OrdinalIgnoreCase))
|
|
||||||
.Select(item => item.Path)
|
|
||||||
.Where(path => !string.IsNullOrWhiteSpace(path))
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (filePaths == null || filePaths.Length == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
pendingActivation = new PendingBootstrapActivation
|
|
||||||
{
|
|
||||||
Kind = PendingBootstrapActivationKind.File,
|
|
||||||
Mode = WinoApplicationMode.Calendar,
|
|
||||||
FilePaths = filePaths
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,14 +41,9 @@ internal static class ToastActivationResolver
|
|||||||
return calendarAction == Constants.ToastCalendarNavigateAction;
|
return calendarAction == Constants.ToastCalendarNavigateAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastDismissActionKey, out string _))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation mailAction))
|
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation mailAction))
|
||||||
{
|
{
|
||||||
return mailAction is MailOperation.Navigate or MailOperation.Reply or MailOperation.ReplyAll or MailOperation.Forward;
|
return mailAction == MailOperation.Navigate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -57,6 +52,5 @@ internal static class ToastActivationResolver
|
|||||||
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.ToastCalendarActionKey, out string _) ||
|
toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string _) ||
|
||||||
toastArguments.TryGetValue(Constants.ToastDismissActionKey, out string _) ||
|
|
||||||
toastArguments.TryGetValue(Constants.ToastActionKey, out string _);
|
toastArguments.TryGetValue(Constants.ToastActionKey, out string _);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ 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.Extensions;
|
||||||
using Wino.Mail.WinUI.Helpers;
|
|
||||||
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;
|
||||||
@@ -67,19 +66,13 @@ public partial class App : WinoApplication,
|
|||||||
private bool _hasConfiguredAccounts;
|
private bool _hasConfiguredAccounts;
|
||||||
private bool _isExiting;
|
private bool _isExiting;
|
||||||
private bool _activationInfrastructureInitialized;
|
private bool _activationInfrastructureInitialized;
|
||||||
private bool _appHostInfrastructureInitialized;
|
|
||||||
private bool _appNotificationsRegistered;
|
|
||||||
private int _initialNotificationActivationHandled;
|
private int _initialNotificationActivationHandled;
|
||||||
private int _initialShareActivationHandled;
|
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);
|
||||||
private readonly SemaphoreSlim _appHostInfrastructureSemaphore = new(1, 1);
|
|
||||||
private readonly ConcurrentDictionary<Guid, int> _inboxSyncCounters = [];
|
private readonly ConcurrentDictionary<Guid, int> _inboxSyncCounters = [];
|
||||||
private readonly AppNotificationActivationBuffer _bufferedAppNotificationActivations = new();
|
|
||||||
private NativeTrayIcon? _trayIcon;
|
private NativeTrayIcon? _trayIcon;
|
||||||
private readonly record struct NotificationActivationRoute(bool RequiresForegroundWindow, Func<Task>? ExecuteAsync);
|
|
||||||
private readonly record struct ShellWindowActivationResult(IWinoShellWindow? ShellWindow, bool WasCreated);
|
|
||||||
|
|
||||||
internal bool IsExiting => _isExiting;
|
internal bool IsExiting => _isExiting;
|
||||||
|
|
||||||
@@ -90,7 +83,6 @@ public partial class App : WinoApplication,
|
|||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
CryptographyContext.Register(typeof(WindowsSecureMimeContext));
|
CryptographyContext.Register(typeof(WindowsSecureMimeContext));
|
||||||
|
|
||||||
EnsureAppNotificationRegistration();
|
|
||||||
RegisterRecipients();
|
RegisterRecipients();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,16 +271,12 @@ public partial class App : WinoApplication,
|
|||||||
shellWindow.Close();
|
shellWindow.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ActivateWindowAsync(WindowEx window, bool applyThemeToWindow = true)
|
private async Task ActivateWindowAsync(WindowEx window)
|
||||||
{
|
{
|
||||||
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
||||||
MainWindow = window;
|
MainWindow = window;
|
||||||
windowManager.ActivateWindow(window);
|
windowManager.ActivateWindow(window);
|
||||||
|
await NewThemeService.ApplyThemeToActiveWindowAsync();
|
||||||
if (applyThemeToWindow)
|
|
||||||
{
|
|
||||||
await NewThemeService.ApplyThemeToActiveWindowAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ExitApplicationAsync()
|
private Task ExitApplicationAsync()
|
||||||
@@ -370,7 +358,6 @@ public partial class App : WinoApplication,
|
|||||||
services.AddTransient(typeof(AccountDetailsPageViewModel));
|
services.AddTransient(typeof(AccountDetailsPageViewModel));
|
||||||
services.AddTransient(typeof(SignatureManagementPageViewModel));
|
services.AddTransient(typeof(SignatureManagementPageViewModel));
|
||||||
services.AddTransient(typeof(MessageListPageViewModel));
|
services.AddTransient(typeof(MessageListPageViewModel));
|
||||||
services.AddTransient(typeof(MailNotificationSettingsPageViewModel));
|
|
||||||
services.AddTransient(typeof(ReadComposePanePageViewModel));
|
services.AddTransient(typeof(ReadComposePanePageViewModel));
|
||||||
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
|
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
|
||||||
services.AddTransient(typeof(AppPreferencesPageViewModel));
|
services.AddTransient(typeof(AppPreferencesPageViewModel));
|
||||||
@@ -422,12 +409,6 @@ public partial class App : WinoApplication,
|
|||||||
=> Services.GetRequiredService<IWinoWindowManager>().GetWindow(WinoWindowKind.Shell) is IWinoShellWindow;
|
=> Services.GetRequiredService<IWinoWindowManager>().GetWindow(WinoWindowKind.Shell) is IWinoShellWindow;
|
||||||
|
|
||||||
private async Task EnsureActivationInfrastructureAsync()
|
private async Task EnsureActivationInfrastructureAsync()
|
||||||
{
|
|
||||||
await EnsureCoreActivationInfrastructureAsync();
|
|
||||||
await EnsureAppHostInfrastructureAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task EnsureCoreActivationInfrastructureAsync()
|
|
||||||
{
|
{
|
||||||
if (_activationInfrastructureInitialized)
|
if (_activationInfrastructureInitialized)
|
||||||
return;
|
return;
|
||||||
@@ -439,7 +420,7 @@ public partial class App : WinoApplication,
|
|||||||
if (_activationInfrastructureInitialized)
|
if (_activationInfrastructureInitialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
EnsureAppNotificationRegistration();
|
TryRegisterAppNotifications();
|
||||||
|
|
||||||
await Services.GetRequiredService<ReleaseLocalAccountDataCleanupService>()
|
await Services.GetRequiredService<ReleaseLocalAccountDataCleanupService>()
|
||||||
.RunIfNeededAsync();
|
.RunIfNeededAsync();
|
||||||
@@ -450,8 +431,18 @@ public partial class App : WinoApplication,
|
|||||||
_preferencesService = Services.GetRequiredService<IPreferencesService>();
|
_preferencesService = Services.GetRequiredService<IPreferencesService>();
|
||||||
_accountService = Services.GetRequiredService<IAccountService>();
|
_accountService = Services.GetRequiredService<IAccountService>();
|
||||||
|
|
||||||
|
EnsureWindowManagerConfigured();
|
||||||
|
EnsureTrayIconCreated();
|
||||||
|
|
||||||
_hasConfiguredAccounts = (await _accountService.GetAccountsAsync()).Any();
|
_hasConfiguredAccounts = (await _accountService.GetAccountsAsync()).Any();
|
||||||
|
|
||||||
|
if (_hasConfiguredAccounts)
|
||||||
|
{
|
||||||
|
_preferencesService.PreferenceChanged -= PreferencesServiceChanged;
|
||||||
|
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
|
||||||
|
RestartAutoSynchronizationLoop();
|
||||||
|
}
|
||||||
|
|
||||||
_activationInfrastructureInitialized = true;
|
_activationInfrastructureInitialized = true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -460,38 +451,6 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureAppHostInfrastructureAsync()
|
|
||||||
{
|
|
||||||
await EnsureCoreActivationInfrastructureAsync();
|
|
||||||
|
|
||||||
if (_appHostInfrastructureInitialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await _appHostInfrastructureSemaphore.WaitAsync();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_appHostInfrastructureInitialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
EnsureWindowManagerConfigured();
|
|
||||||
EnsureTrayIconCreated();
|
|
||||||
|
|
||||||
if (_hasConfiguredAccounts)
|
|
||||||
{
|
|
||||||
_preferencesService!.PreferenceChanged -= PreferencesServiceChanged;
|
|
||||||
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
|
|
||||||
RestartAutoSynchronizationLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
_appHostInfrastructureInitialized = true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_appHostInfrastructureSemaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryMarkInitialNotificationActivationHandled()
|
private bool TryMarkInitialNotificationActivationHandled()
|
||||||
=> Interlocked.Exchange(ref _initialNotificationActivationHandled, 1) == 0;
|
=> Interlocked.Exchange(ref _initialNotificationActivationHandled, 1) == 0;
|
||||||
|
|
||||||
@@ -502,123 +461,115 @@ public partial class App : WinoApplication,
|
|||||||
{
|
{
|
||||||
base.OnLaunched(args);
|
base.OnLaunched(args);
|
||||||
|
|
||||||
await EnsureCoreActivationInfrastructureAsync();
|
await EnsureActivationInfrastructureAsync();
|
||||||
|
|
||||||
var activationArgs = ResolveStartupActivation();
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
|
|
||||||
if (await TryHandleStartupAppNotificationActivationAsync(activationArgs))
|
if (activationArgs.Kind == ExtendedActivationKind.ShareTarget &&
|
||||||
return;
|
TryMarkInitialShareActivationHandled())
|
||||||
|
{
|
||||||
|
LogActivation("Processing share target activation from OnLaunched.");
|
||||||
|
|
||||||
await EnsureAppHostInfrastructureAsync();
|
if (await HandleShareTargetActivationAsync(activationArgs, activateWindow: true))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var hasAnyAccount = _hasConfiguredAccounts;
|
var hasAnyAccount = _hasConfiguredAccounts;
|
||||||
var isStartupTaskLaunch = activationArgs.Kind == ExtendedActivationKind.StartupTask;
|
if (!IsStartupTaskLaunch() && !hasAnyAccount)
|
||||||
|
|
||||||
if (!hasAnyAccount && !isStartupTaskLaunch)
|
|
||||||
{
|
{
|
||||||
await LaunchWelcomeWindowAsync();
|
CreateWelcomeWindow();
|
||||||
|
await NewThemeService.InitializeAsync();
|
||||||
|
MainWindow?.Activate();
|
||||||
|
LogActivation("Welcome window created and activated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await TryHandleLaunchActivationAsync(args, activationArgs))
|
// Check if launched from toast notification.
|
||||||
|
if (IsNotificationActivation(out AppNotificationActivatedEventArgs toastArgs) &&
|
||||||
|
TryMarkInitialNotificationActivationHandled())
|
||||||
|
{
|
||||||
|
LogActivation($"Processing notification activation from OnLaunched. Arguments: {toastArgs.Argument}");
|
||||||
|
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await CompleteStandardLaunchAsync(args, hasAnyAccount, isStartupTaskLaunch);
|
if (ToastActivationResolver.TryParse(args.Arguments, out var launchToastArguments) &&
|
||||||
|
TryMarkInitialNotificationActivationHandled())
|
||||||
|
{
|
||||||
|
LogActivation($"Processing toast launch activation from OnLaunched. Arguments: {args.Arguments}");
|
||||||
|
await HandleToastActivationAsync(launchToastArguments);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if launched by startup task.
|
||||||
|
bool isStartupTaskLaunch = IsStartupTaskLaunch();
|
||||||
|
|
||||||
|
if (isStartupTaskLaunch && !hasAnyAccount)
|
||||||
|
{
|
||||||
|
CreateWelcomeWindow();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CreateWindow(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize theme service after window creation.
|
||||||
|
// Theme service requires the window to exist to properly load and apply themes.
|
||||||
|
await NewThemeService.InitializeAsync();
|
||||||
|
|
||||||
|
if (hasAnyAccount)
|
||||||
|
{
|
||||||
|
// Wino account loading and activation.
|
||||||
|
await LoadInitialWinoAccountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogActivation("Theme service initialized.");
|
||||||
|
|
||||||
|
// If startup task launch, keep window hidden (system tray only).
|
||||||
|
// Otherwise, activate the window normally.
|
||||||
|
if (isStartupTaskLaunch)
|
||||||
|
{
|
||||||
|
LogActivation("Launched by startup task. Window created but hidden (system tray only).");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Normal launch - show and activate the window.
|
||||||
|
// The What's New dialog is shown from MailAppShellViewModel.OnNavigatedTo once XamlRoot is ready.
|
||||||
|
MainWindow?.Activate();
|
||||||
|
LogActivation("Window created and activated.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppActivationArguments ResolveStartupActivation()
|
public async Task HandleInitialActivationAsync()
|
||||||
{
|
{
|
||||||
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
if (Program.TryConsumeDeferredAppNotificationStartup())
|
|
||||||
{
|
|
||||||
LogActivation($"Resolved deferred COM activation after notification registration. Kind: {activationArgs.Kind}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return activationArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> TryHandleStartupAppNotificationActivationAsync(AppActivationArguments activationArgs)
|
|
||||||
{
|
|
||||||
if (activationArgs.Kind != ExtendedActivationKind.AppNotification ||
|
if (activationArgs.Kind != ExtendedActivationKind.AppNotification ||
|
||||||
activationArgs.Data is not AppNotificationActivatedEventArgs toastArgs ||
|
activationArgs.Data is not AppNotificationActivatedEventArgs toastArgs ||
|
||||||
!TryMarkInitialNotificationActivationHandled())
|
!TryMarkInitialNotificationActivationHandled())
|
||||||
{
|
{
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogActivation($"Processing app-notification activation from startup. Arguments: {toastArgs.Argument}");
|
LogActivation($"Processing initial notification activation from application startup. Arguments: {toastArgs.Argument}");
|
||||||
|
|
||||||
if (!TryResolveNotificationActivationRoute(toastArgs, out var route))
|
await EnsureActivationInfrastructureAsync();
|
||||||
{
|
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
||||||
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
|
||||||
|
|
||||||
if (!IsAppRunning())
|
|
||||||
{
|
|
||||||
LogActivation("Startup app-notification activation completed without a window. Exiting transient process.");
|
|
||||||
ExitApplication();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.RequiresForegroundWindow)
|
|
||||||
{
|
|
||||||
await EnsureAppHostInfrastructureAsync();
|
|
||||||
await route.ExecuteAsync!.Invoke();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await route.ExecuteAsync!.Invoke();
|
|
||||||
|
|
||||||
if (!IsAppRunning())
|
|
||||||
{
|
|
||||||
LogActivation("Background startup app-notification activation completed. Exiting without creating app host.");
|
|
||||||
ExitApplication();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
private void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
if (!_activationInfrastructureInitialized)
|
|
||||||
{
|
|
||||||
LogActivation($"Buffering app notification activation until infrastructure is ready. Arguments: {args.Argument}");
|
|
||||||
_bufferedAppNotificationActivations.Enqueue(args);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppNotification callbacks are not guaranteed to run on the UI thread.
|
// AppNotification callbacks are not guaranteed to run on the UI thread.
|
||||||
// Marshal toast handling to the window dispatcher before touching window APIs.
|
// Marshal toast handling to the window dispatcher before touching window APIs.
|
||||||
if (TryEnqueueActivationOnUiThread(() => _ = HandleToastActivationAsync(args.Argument, args.UserInput)))
|
if (MainWindow?.DispatcherQueue?.TryEnqueue(() => _ = HandleToastActivationAsync(args.Argument, args.UserInput)) == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LogActivation($"Processing notification activation from NotificationInvoked. Arguments: {args.Argument}");
|
LogActivation($"Processing notification activation from NotificationInvoked. Arguments: {args.Argument}");
|
||||||
_ = HandleToastActivationAsync(args.Argument, args.UserInput);
|
_ = HandleToastActivationAsync(args.Argument, args.UserInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryResolveNotificationActivationRoute(AppNotificationActivatedEventArgs notificationArgs,
|
private void TryRegisterAppNotifications()
|
||||||
out NotificationActivationRoute route)
|
|
||||||
{
|
{
|
||||||
route = default;
|
|
||||||
|
|
||||||
if (!ToastActivationResolver.TryParse(notificationArgs.Argument, out var toastArguments))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return TryCreateNotificationActivationRoute(toastArguments, notificationArgs.UserInput, out route);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureAppNotificationRegistration()
|
|
||||||
{
|
|
||||||
if (!Program.ShouldRegisterAppNotifications())
|
|
||||||
{
|
|
||||||
LogActivation("Skipping app notification registration for non-host entry activation.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_appNotificationsRegistered)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var notificationManager = AppNotificationManager.Default;
|
var notificationManager = AppNotificationManager.Default;
|
||||||
|
|
||||||
notificationManager.NotificationInvoked -= AppNotificationInvoked;
|
notificationManager.NotificationInvoked -= AppNotificationInvoked;
|
||||||
@@ -627,7 +578,6 @@ public partial class App : WinoApplication,
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
notificationManager.Register();
|
notificationManager.Register();
|
||||||
_appNotificationsRegistered = true;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -640,17 +590,60 @@ public partial class App : WinoApplication,
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task HandleToastActivationAsync(NotificationArguments toastArguments, IDictionary<string, string>? userInput = null)
|
private async Task HandleToastActivationAsync(NotificationArguments toastArguments, IDictionary<string, string>? userInput = null)
|
||||||
{
|
{
|
||||||
if (!TryCreateNotificationActivationRoute(toastArguments, userInput, out var route))
|
LogActivation("Handling app notification activation.");
|
||||||
|
|
||||||
|
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
|
||||||
|
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
|
||||||
{
|
{
|
||||||
LogActivation("App notification activation did not match any known handler.");
|
await HandleStoreUpdateToastAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogActivation(route.RequiresForegroundWindow
|
// Check calendar reminder toast activation first.
|
||||||
? "Handling foreground app notification activation."
|
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
|
||||||
: "Handling background app notification activation.");
|
toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) &&
|
||||||
|
Guid.TryParse(calendarItemIdString, out Guid calendarItemId))
|
||||||
|
{
|
||||||
|
if (calendarAction == Constants.ToastCalendarNavigateAction)
|
||||||
|
{
|
||||||
|
await HandleCalendarToastNavigationAsync(calendarItemId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await route.ExecuteAsync!.Invoke();
|
if (calendarAction == Constants.ToastCalendarSnoozeAction)
|
||||||
|
{
|
||||||
|
await HandleCalendarToastSnoozeAsync(userInput, calendarItemId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendarAction == Constants.ToastCalendarJoinOnlineAction)
|
||||||
|
{
|
||||||
|
await HandleCalendarToastJoinOnlineAsync(calendarItemId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a navigation toast (user clicked the notification).
|
||||||
|
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation action) &&
|
||||||
|
Guid.TryParse(toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
|
||||||
|
{
|
||||||
|
if (action == MailOperation.Navigate)
|
||||||
|
{
|
||||||
|
// User clicked notification - create window if needed and navigate.
|
||||||
|
await HandleToastNavigationAsync(mailItemUniqueId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User clicked action button (Mark as Read, Delete, etc.)
|
||||||
|
// Execute action without window and exit.
|
||||||
|
|
||||||
|
await HandleToastActionAsync(action, mailItemUniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogActivation("App notification activation did not match any known handler.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HandleToastActivationAsync(string toastArgument, IDictionary<string, string>? userInput = null)
|
private Task HandleToastActivationAsync(string toastArgument, IDictionary<string, string>? userInput = null)
|
||||||
@@ -701,37 +694,6 @@ public partial class App : WinoApplication,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> TryHandleLaunchActivationAsync(Microsoft.UI.Xaml.LaunchActivatedEventArgs args,
|
|
||||||
AppActivationArguments activationArgs)
|
|
||||||
{
|
|
||||||
if (activationArgs.Kind == ExtendedActivationKind.ShareTarget &&
|
|
||||||
TryMarkInitialShareActivationHandled())
|
|
||||||
{
|
|
||||||
LogActivation("Processing share target activation from OnLaunched.");
|
|
||||||
|
|
||||||
if (await HandleShareTargetActivationAsync(activationArgs, activateWindow: true))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Program.TryConsumePendingBootstrapActivation(out var pendingBootstrapActivation))
|
|
||||||
{
|
|
||||||
LogActivation($"Processing pending bootstrap activation. Kind: {pendingBootstrapActivation.Kind}, Mode: {pendingBootstrapActivation.Mode}");
|
|
||||||
|
|
||||||
if (await HandlePendingBootstrapActivationAsync(pendingBootstrapActivation))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ToastActivationResolver.TryParse(args.Arguments, out var launchToastArguments) &&
|
|
||||||
TryMarkInitialNotificationActivationHandled())
|
|
||||||
{
|
|
||||||
LogActivation($"Processing toast launch activation from OnLaunched. Arguments: {args.Arguments}");
|
|
||||||
await HandleToastActivationAsync(launchToastArguments);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<MailShareRequest?> ExtractMailShareRequestAsync(ShareTargetActivatedEventArgs shareTargetArgs)
|
private async Task<MailShareRequest?> ExtractMailShareRequestAsync(ShareTargetActivatedEventArgs shareTargetArgs)
|
||||||
{
|
{
|
||||||
var shareOperation = shareTargetArgs.ShareOperation;
|
var shareOperation = shareTargetArgs.ShareOperation;
|
||||||
@@ -778,36 +740,22 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LaunchWelcomeWindowAsync()
|
private async Task<IWinoShellWindow?> EnsureShellWindowAsync(WinoApplicationMode mode, bool activateWindow, bool suppressStartupFlows = true)
|
||||||
{
|
|
||||||
CreateWelcomeWindow();
|
|
||||||
await NewThemeService.InitializeAsync();
|
|
||||||
MainWindow?.Activate();
|
|
||||||
LogActivation("Welcome window created and activated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ShellWindowActivationResult> EnsureShellWindowAsync(WinoApplicationMode mode,
|
|
||||||
bool activateWindow,
|
|
||||||
bool suppressStartupFlows = true,
|
|
||||||
object? activationParameter = null)
|
|
||||||
{
|
{
|
||||||
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
||||||
var navigationService = Services.GetRequiredService<INavigationService>();
|
var navigationService = Services.GetRequiredService<INavigationService>();
|
||||||
var shellWindow = windowManager.GetWindow(WinoWindowKind.Shell) as IWinoShellWindow;
|
var shellWindow = windowManager.GetWindow(WinoWindowKind.Shell) as IWinoShellWindow;
|
||||||
var wasCreated = false;
|
|
||||||
|
|
||||||
if (shellWindow == null)
|
if (shellWindow == null)
|
||||||
{
|
{
|
||||||
LogActivation($"Creating shell window for {mode} activation.");
|
LogActivation($"Creating shell window for {mode} activation.");
|
||||||
wasCreated = true;
|
|
||||||
|
|
||||||
CreateWindow(
|
CreateWindow(
|
||||||
null,
|
null,
|
||||||
AppEntryConstants.GetModeLaunchArgument(mode),
|
AppEntryConstants.GetModeLaunchArgument(mode),
|
||||||
new ShellModeActivationContext
|
new ShellModeActivationContext
|
||||||
{
|
{
|
||||||
SuppressStartupFlows = suppressStartupFlows,
|
SuppressStartupFlows = suppressStartupFlows
|
||||||
Parameter = activationParameter
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await NewThemeService.InitializeAsync();
|
await NewThemeService.InitializeAsync();
|
||||||
@@ -821,22 +769,18 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ApplyShellWindowTaskbarIdentity(shellWindow, mode);
|
|
||||||
navigationService.ChangeApplicationMode(mode, new ShellModeActivationContext
|
navigationService.ChangeApplicationMode(mode, new ShellModeActivationContext
|
||||||
{
|
{
|
||||||
SuppressStartupFlows = suppressStartupFlows,
|
SuppressStartupFlows = suppressStartupFlows
|
||||||
Parameter = activationParameter
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyShellWindowTaskbarIdentity(shellWindow, mode);
|
|
||||||
|
|
||||||
if (activateWindow && shellWindow is WindowEx window)
|
if (activateWindow && shellWindow is WindowEx window)
|
||||||
{
|
{
|
||||||
await ActivateWindowAsync(window, applyThemeToWindow: wasCreated);
|
await ActivateWindowAsync(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ShellWindowActivationResult(shellWindow, wasCreated);
|
return shellWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleStoreUpdateToastAsync()
|
private async Task HandleStoreUpdateToastAsync()
|
||||||
@@ -853,42 +797,18 @@ public partial class App : WinoApplication,
|
|||||||
private async Task HandleCalendarToastNavigationAsync(Guid calendarItemId)
|
private async Task HandleCalendarToastNavigationAsync(Guid calendarItemId)
|
||||||
{
|
{
|
||||||
var calendarService = Services.GetRequiredService<ICalendarService>();
|
var calendarService = Services.GetRequiredService<ICalendarService>();
|
||||||
var fallbackNavigationArgs = new CalendarPageNavigationArgs
|
var navigationService = Services.GetRequiredService<INavigationService>();
|
||||||
{
|
|
||||||
RequestDefaultNavigation = true
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!HasShellWindow())
|
|
||||||
{
|
|
||||||
await EnsureShellWindowAsync(
|
|
||||||
WinoApplicationMode.Calendar,
|
|
||||||
activateWindow: true,
|
|
||||||
activationParameter: fallbackNavigationArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
var calendarItem = await calendarService.GetCalendarItemAsync(calendarItemId);
|
var calendarItem = await calendarService.GetCalendarItemAsync(calendarItemId);
|
||||||
if (calendarItem == null)
|
if (calendarItem == null)
|
||||||
{
|
|
||||||
LogActivation($"Calendar notification navigation item was not found for {calendarItemId}. Opening calendar shell only.");
|
|
||||||
|
|
||||||
await EnsureShellWindowAsync(
|
|
||||||
WinoApplicationMode.Calendar,
|
|
||||||
activateWindow: true,
|
|
||||||
activationParameter: fallbackNavigationArgs);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var target = new CalendarItemTarget(calendarItem, CalendarEventTargetType.Single);
|
var target = new CalendarItemTarget(calendarItem, CalendarEventTargetType.Single);
|
||||||
var navigationArgs = new CalendarPageNavigationArgs
|
|
||||||
{
|
|
||||||
NavigationDate = calendarItem.LocalStartDate,
|
|
||||||
PendingTarget = target
|
|
||||||
};
|
|
||||||
|
|
||||||
await EnsureShellWindowAsync(
|
await EnsureShellWindowAsync(WinoApplicationMode.Calendar, activateWindow: true);
|
||||||
WinoApplicationMode.Calendar,
|
|
||||||
activateWindow: true,
|
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Calendar);
|
||||||
activationParameter: navigationArgs);
|
navigationService.Navigate(WinoPage.EventDetailsPage, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleCalendarToastSnoozeAsync(IDictionary<string, string>? userInput, Guid calendarItemId)
|
private async Task HandleCalendarToastSnoozeAsync(IDictionary<string, string>? userInput, Guid calendarItemId)
|
||||||
@@ -917,38 +837,6 @@ public partial class App : WinoApplication,
|
|||||||
await nativeAppService.LaunchUriAsync(joinUri);
|
await nativeAppService.LaunchUriAsync(joinUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CompleteStandardLaunchAsync(Microsoft.UI.Xaml.LaunchActivatedEventArgs args,
|
|
||||||
bool hasAnyAccount,
|
|
||||||
bool isStartupTaskLaunch)
|
|
||||||
{
|
|
||||||
if (isStartupTaskLaunch && !hasAnyAccount)
|
|
||||||
{
|
|
||||||
CreateWelcomeWindow();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CreateWindow(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
await NewThemeService.InitializeAsync();
|
|
||||||
|
|
||||||
if (hasAnyAccount)
|
|
||||||
{
|
|
||||||
await LoadInitialWinoAccountAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
LogActivation("Theme service initialized.");
|
|
||||||
|
|
||||||
if (isStartupTaskLaunch)
|
|
||||||
{
|
|
||||||
LogActivation("Launched by startup task. Window created but hidden (system tray only).");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MainWindow?.Activate();
|
|
||||||
LogActivation("Window created and activated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetSnoozeDurationMinutes(IDictionary<string, string>? userInput, out int snoozeDurationMinutes)
|
private bool TryGetSnoozeDurationMinutes(IDictionary<string, string>? userInput, out int snoozeDurationMinutes)
|
||||||
{
|
{
|
||||||
snoozeDurationMinutes = _preferencesService?.DefaultSnoozeDurationInMinutes ?? 0;
|
snoozeDurationMinutes = _preferencesService?.DefaultSnoozeDurationInMinutes ?? 0;
|
||||||
@@ -972,6 +860,7 @@ public partial class App : WinoApplication,
|
|||||||
private async Task HandleToastNavigationAsync(Guid mailItemUniqueId)
|
private async Task HandleToastNavigationAsync(Guid mailItemUniqueId)
|
||||||
{
|
{
|
||||||
var mailService = Services.GetRequiredService<IMailService>();
|
var mailService = Services.GetRequiredService<IMailService>();
|
||||||
|
var navigationService = Services.GetRequiredService<INavigationService>();
|
||||||
|
|
||||||
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId);
|
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId);
|
||||||
if (account == null)
|
if (account == null)
|
||||||
@@ -996,6 +885,7 @@ public partial class App : WinoApplication,
|
|||||||
var shellWindowAlreadyExists = HasShellWindow();
|
var shellWindowAlreadyExists = HasShellWindow();
|
||||||
|
|
||||||
await EnsureShellWindowAsync(WinoApplicationMode.Mail, activateWindow: true);
|
await EnsureShellWindowAsync(WinoApplicationMode.Mail, activateWindow: true);
|
||||||
|
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
|
||||||
|
|
||||||
if (shellWindowAlreadyExists)
|
if (shellWindowAlreadyExists)
|
||||||
{
|
{
|
||||||
@@ -1095,86 +985,6 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleToastComposeActionAsync(MailOperation action, Guid mailItemUniqueId)
|
|
||||||
{
|
|
||||||
LogActivation($"Handling compose toast action: {action} for mail {mailItemUniqueId}");
|
|
||||||
|
|
||||||
var mailService = Services.GetRequiredService<IMailService>();
|
|
||||||
var folderService = Services.GetRequiredService<IFolderService>();
|
|
||||||
var mimeFileService = Services.GetRequiredService<IMimeFileService>();
|
|
||||||
var navigationService = Services.GetRequiredService<INavigationService>();
|
|
||||||
var requestDelegator = Services.GetRequiredService<IWinoRequestDelegator>();
|
|
||||||
var mailShellViewModel = Services.GetRequiredService<MailAppShellViewModel>();
|
|
||||||
|
|
||||||
var mailItem = await mailService.GetSingleMailItemAsync(mailItemUniqueId);
|
|
||||||
if (mailItem == null)
|
|
||||||
{
|
|
||||||
LogActivation($"Compose toast mail item was not found for {mailItemUniqueId}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId) ?? mailItem.AssignedAccount;
|
|
||||||
if (account == null)
|
|
||||||
{
|
|
||||||
LogActivation($"Compose toast account was not found for {mailItemUniqueId}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var draftFolder = await folderService.GetSpecialFolderByAccountIdAsync(account.Id, SpecialFolderType.Draft);
|
|
||||||
if (draftFolder == null)
|
|
||||||
{
|
|
||||||
LogActivation($"Compose toast draft folder is missing for account {account.Id}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mimeInformation = await mimeFileService.GetMimeMessageInformationAsync(mailItem.FileId, account.Id);
|
|
||||||
if (mimeInformation?.MimeMessage == null)
|
|
||||||
{
|
|
||||||
LogActivation($"Compose toast MIME payload was not found for mail {mailItemUniqueId}.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await EnsureShellWindowAsync(WinoApplicationMode.Mail, activateWindow: true);
|
|
||||||
|
|
||||||
if (mailShellViewModel.MenuItems.TryGetAccountMenuItem(account.Id, out IAccountMenuItem accountMenuItem))
|
|
||||||
{
|
|
||||||
await mailShellViewModel.ChangeLoadedAccountAsync(accountMenuItem, navigateInbox: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mailShellViewModel.MenuItems.TryGetSpecialFolderMenuItem(account.Id, SpecialFolderType.Draft, out var draftFolderMenuItem))
|
|
||||||
{
|
|
||||||
await mailShellViewModel.NavigateFolderAsync(draftFolderMenuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
var draftOptions = new DraftCreationOptions
|
|
||||||
{
|
|
||||||
Reason = action switch
|
|
||||||
{
|
|
||||||
MailOperation.Reply => DraftCreationReason.Reply,
|
|
||||||
MailOperation.ReplyAll => DraftCreationReason.ReplyAll,
|
|
||||||
MailOperation.Forward => DraftCreationReason.Forward,
|
|
||||||
_ => DraftCreationReason.Empty
|
|
||||||
},
|
|
||||||
ReferencedMessage = new ReferencedMessage
|
|
||||||
{
|
|
||||||
MimeMessage = mimeInformation.MimeMessage,
|
|
||||||
MailCopy = mailItem
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var (draftMailCopy, draftBase64MimeMessage) = await mailService.CreateDraftAsync(account.Id, draftOptions);
|
|
||||||
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, mailItem);
|
|
||||||
|
|
||||||
await requestDelegator.ExecuteAsync(draftPreparationRequest);
|
|
||||||
navigationService.Navigate(WinoPage.ComposePage,
|
|
||||||
new MailItemViewModel(draftMailCopy),
|
|
||||||
NavigationReferenceFrame.RenderingFrame,
|
|
||||||
NavigationTransitionType.DrillIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsComposeToastAction(MailOperation action)
|
|
||||||
=> action is MailOperation.Reply or MailOperation.ReplyAll or MailOperation.Forward;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the main window and activates it.
|
/// Creates the main window and activates it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1229,7 +1039,6 @@ public partial class App : WinoApplication,
|
|||||||
? resolvedActivationMode
|
? resolvedActivationMode
|
||||||
: AppModeActivationResolver.Resolve(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine, defaultMode);
|
: AppModeActivationResolver.Resolve(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine, defaultMode);
|
||||||
|
|
||||||
ApplyShellWindowTaskbarIdentity(shellWindow, targetMode);
|
|
||||||
navigationService.ChangeApplicationMode(targetMode, activationContextOverride);
|
navigationService.ChangeApplicationMode(targetMode, activationContextOverride);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1263,18 +1072,6 @@ public partial class App : WinoApplication,
|
|||||||
shellWindow.HandleAppActivation(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine);
|
shellWindow.HandleAppActivation(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplyShellWindowTaskbarIdentity(IWinoShellWindow? shellWindow, WinoApplicationMode mode)
|
|
||||||
{
|
|
||||||
if (shellWindow is not WindowEx window)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var packagedApplicationId = AppEntryConstants.GetPackagedApplicationId(mode);
|
|
||||||
if (packagedApplicationId == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
WindowAppUserModelIdHelper.TrySet(window, AppEntryConstants.GetAppUserModelId(mode));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateWelcomeWindow()
|
private void CreateWelcomeWindow()
|
||||||
{
|
{
|
||||||
LogActivation("Creating welcome window.");
|
LogActivation("Creating welcome window.");
|
||||||
@@ -1753,7 +1550,7 @@ public partial class App : WinoApplication,
|
|||||||
// Handle toast notification activation
|
// Handle toast notification activation
|
||||||
var toastArgs = (AppNotificationActivatedEventArgs)args.Data;
|
var toastArgs = (AppNotificationActivatedEventArgs)args.Data;
|
||||||
LogActivation($"Processing redirected notification activation. Arguments: {toastArgs.Argument}");
|
LogActivation($"Processing redirected notification activation. Arguments: {toastArgs.Argument}");
|
||||||
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
_ = HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
||||||
}
|
}
|
||||||
else if (args.Kind == ExtendedActivationKind.ShareTarget)
|
else if (args.Kind == ExtendedActivationKind.ShareTarget)
|
||||||
{
|
{
|
||||||
@@ -1773,7 +1570,7 @@ public partial class App : WinoApplication,
|
|||||||
{
|
{
|
||||||
shouldActivateWindow = ToastActivationResolver.ShouldBringToForeground(launchToastArguments);
|
shouldActivateWindow = ToastActivationResolver.ShouldBringToForeground(launchToastArguments);
|
||||||
LogActivation($"Processing redirected toast launch activation. Arguments: {launchArgs.Arguments}");
|
LogActivation($"Processing redirected toast launch activation. Arguments: {launchArgs.Arguments}");
|
||||||
await HandleToastActivationAsync(launchToastArguments);
|
_ = HandleToastActivationAsync(launchToastArguments);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1803,89 +1600,12 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch to UI thread since this is called from Program.OnActivated.
|
// Dispatch to UI thread since this is called from Program.OnActivated.
|
||||||
if (TryEnqueueActivationOnUiThread(() => _ = HandleRedirectedActivationAsync()))
|
if (MainWindow?.DispatcherQueue.TryEnqueue(() => _ = HandleRedirectedActivationAsync()) == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_ = HandleRedirectedActivationAsync();
|
_ = HandleRedirectedActivationAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryCreateNotificationActivationRoute(NotificationArguments toastArguments,
|
|
||||||
IDictionary<string, string>? userInput,
|
|
||||||
out NotificationActivationRoute route)
|
|
||||||
{
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
|
|
||||||
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
|
|
||||||
{
|
|
||||||
route = new NotificationActivationRoute(true, HandleStoreUpdateToastAsync);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastDismissActionKey, out string _))
|
|
||||||
{
|
|
||||||
route = new NotificationActivationRoute(false, () =>
|
|
||||||
{
|
|
||||||
LogActivation("Handling notification dismiss action.");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
|
|
||||||
toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) &&
|
|
||||||
Guid.TryParse(calendarItemIdString, out Guid calendarItemId))
|
|
||||||
{
|
|
||||||
route = calendarAction switch
|
|
||||||
{
|
|
||||||
Constants.ToastCalendarNavigateAction => new NotificationActivationRoute(true, () => HandleCalendarToastNavigationAsync(calendarItemId)),
|
|
||||||
Constants.ToastCalendarSnoozeAction => new NotificationActivationRoute(false, () => HandleCalendarToastSnoozeAsync(userInput, calendarItemId)),
|
|
||||||
Constants.ToastCalendarJoinOnlineAction => new NotificationActivationRoute(false, () => HandleCalendarToastJoinOnlineAsync(calendarItemId)),
|
|
||||||
_ => default
|
|
||||||
};
|
|
||||||
|
|
||||||
return route.ExecuteAsync != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation action) &&
|
|
||||||
Guid.TryParse(toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
|
|
||||||
{
|
|
||||||
if (action == MailOperation.Navigate)
|
|
||||||
{
|
|
||||||
route = new NotificationActivationRoute(true, () => HandleToastNavigationAsync(mailItemUniqueId));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsComposeToastAction(action))
|
|
||||||
{
|
|
||||||
route = new NotificationActivationRoute(true, () => HandleToastComposeActionAsync(action, mailItemUniqueId));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
route = new NotificationActivationRoute(false, () => HandleToastActionAsync(action, mailItemUniqueId));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
route = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> HandlePendingBootstrapActivationAsync(PendingBootstrapActivation pendingBootstrapActivation)
|
|
||||||
{
|
|
||||||
if (pendingBootstrapActivation.Mode != WinoApplicationMode.Calendar)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var navigationArgs = new CalendarPageNavigationArgs
|
|
||||||
{
|
|
||||||
RequestDefaultNavigation = true
|
|
||||||
};
|
|
||||||
|
|
||||||
await EnsureShellWindowAsync(
|
|
||||||
WinoApplicationMode.Calendar,
|
|
||||||
activateWindow: true,
|
|
||||||
activationParameter: navigationArgs);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string AppendLaunchArgument(string? launchArguments, string launchArgument)
|
private static string AppendLaunchArgument(string? launchArguments, string launchArgument)
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(launchArguments)
|
return string.IsNullOrWhiteSpace(launchArguments)
|
||||||
@@ -1966,32 +1686,6 @@ public partial class App : WinoApplication,
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryEnqueueActivationOnUiThread(Action action)
|
|
||||||
{
|
|
||||||
var dispatcherQueue = MainWindow?.DispatcherQueue;
|
|
||||||
|
|
||||||
if (dispatcherQueue == null)
|
|
||||||
{
|
|
||||||
var windowManager = Services.GetService<IWinoWindowManager>();
|
|
||||||
var currentWindow = windowManager?.ActiveWindow
|
|
||||||
?? windowManager?.GetWindow(WinoWindowKind.Shell)
|
|
||||||
?? windowManager?.GetWindow(WinoWindowKind.Welcome);
|
|
||||||
|
|
||||||
dispatcherQueue = currentWindow?.DispatcherQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispatcherQueue == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (dispatcherQueue.HasThreadAccess)
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatcherQueue.TryEnqueue(() => action());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 426 B |
|
Before Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 327 B |
|
Before Width: | Height: | Size: 506 B |
|
Before Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 337 B |
|
Before Width: | Height: | Size: 399 B |
|
Before Width: | Height: | Size: 608 B |
|
Before Width: | Height: | Size: 601 B |
|
Before Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 455 B |
|
Before Width: | Height: | Size: 776 B |
|
Before Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 481 B |
|
Before Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 767 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 473 B |
|
Before Width: | Height: | Size: 481 B |
|
Before Width: | Height: | Size: 318 B |
|
Before Width: | Height: | Size: 363 B |
|
Before Width: | Height: | Size: 558 B |
|
Before Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 424 B |
|
Before Width: | Height: | Size: 691 B |
|
Before Width: | Height: | Size: 697 B |
|
Before Width: | Height: | Size: 452 B |
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 705 B |
|
Before Width: | Height: | Size: 974 B |
@@ -20,7 +20,8 @@
|
|||||||
CornerRadius="4"
|
CornerRadius="4"
|
||||||
DoubleTapped="ControlDoubleTapped"
|
DoubleTapped="ControlDoubleTapped"
|
||||||
RightTapped="ControlRightTapped"
|
RightTapped="ControlRightTapped"
|
||||||
Tapped="ControlTapped">
|
Tapped="ControlTapped"
|
||||||
|
ToolTipService.ToolTip="{x:Bind CalendarItem.DisplayTitle, Mode=OneWay}">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="5" />
|
<ColumnDefinition Width="5" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
@@ -35,9 +36,6 @@
|
|||||||
Placement="BottomEdgeAlignedLeft"
|
Placement="BottomEdgeAlignedLeft"
|
||||||
ShowMode="Auto" />
|
ShowMode="Auto" />
|
||||||
</Grid.ContextFlyout>
|
</Grid.ContextFlyout>
|
||||||
<ToolTipService.ToolTip>
|
|
||||||
<ToolTip Content="{x:Bind CalendarItem.DisplayTitle, Mode=OneWay}" />
|
|
||||||
</ToolTipService.ToolTip>
|
|
||||||
|
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -443,13 +443,10 @@ public sealed partial class CalendarPeriodControl : UserControl, INotifyProperty
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatcherQueue.TryEnqueue(() =>
|
calendarItemViewModel.DisplayingPeriod = new TimeRange(
|
||||||
{
|
|
||||||
calendarItemViewModel.DisplayingPeriod = new TimeRange(
|
|
||||||
date.ToDateTime(TimeOnly.MinValue),
|
date.ToDateTime(TimeOnly.MinValue),
|
||||||
date.AddDays(1).ToDateTime(TimeOnly.MinValue));
|
date.AddDays(1).ToDateTime(TimeOnly.MinValue));
|
||||||
calendarItemViewModel.CalendarSettings = CalendarSettings;
|
calendarItemViewModel.CalendarSettings = CalendarSettings;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VisibleDateRange CreateLayoutRange(VisibleDateRange visibleRange, CalendarSettings calendarSettings)
|
private static VisibleDateRange CreateLayoutRange(VisibleDateRange visibleRange, CalendarSettings calendarSettings)
|
||||||
|
|||||||
@@ -23,7 +23,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.3.0" />
|
Version="2.0.2.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"/>
|
||||||
|
|
||||||
@@ -133,6 +133,18 @@
|
|||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
|
<desktop:Extension Category="windows.toastNotificationActivation">
|
||||||
|
<desktop:ToastNotificationActivation ToastActivatorCLSID="44c05d2b-aa1d-4e59-9d7d-8b4c8607cb8d" />
|
||||||
|
</desktop:Extension>
|
||||||
|
|
||||||
|
<com:Extension Category="windows.comServer">
|
||||||
|
<com:ComServer>
|
||||||
|
<com:ExeServer Executable="Wino.Mail.WinUI.exe" Arguments="----AppNotificationActivated:" DisplayName="Calendar toast activator">
|
||||||
|
<com:Class Id="44c05d2b-aa1d-4e59-9d7d-8b4c8607cb8d" DisplayName="Calendar toast activator"/>
|
||||||
|
</com:ExeServer>
|
||||||
|
</com:ComServer>
|
||||||
|
</com:Extension>
|
||||||
|
|
||||||
<uap:Extension Category="windows.protocol">
|
<uap:Extension Category="windows.protocol">
|
||||||
<uap:Protocol Name="webcal">
|
<uap:Protocol Name="webcal">
|
||||||
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
|
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
|
||||||
|
|||||||
@@ -13,87 +13,38 @@ namespace Wino.Mail.WinUI;
|
|||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
private const string AppNotificationActivatedCommandLinePrefix = "----AppNotificationActivated:";
|
|
||||||
private const string SingleInstanceKey = "WinoMailSingleInstance";
|
private const string SingleInstanceKey = "WinoMailSingleInstance";
|
||||||
private const string ForceAlternateModeSignalEventName = "Local\\WinoMailForceAlternateMode";
|
private const string ForceAlternateModeSignalEventName = "Local\\WinoMailForceAlternateMode";
|
||||||
private const string MailHostRunningMutexName = "Local\\WinoMailMailHostRunning";
|
|
||||||
private const int VkControl = 0x11;
|
private const int VkControl = 0x11;
|
||||||
|
|
||||||
private static bool _forceAlternateModeOnLaunch;
|
private static bool _forceAlternateModeOnLaunch;
|
||||||
private static EventWaitHandle? _forceAlternateModeSignalHandle;
|
private static EventWaitHandle? _forceAlternateModeSignalHandle;
|
||||||
private static Mutex? _mailHostRunningMutex;
|
|
||||||
private static PendingBootstrapActivation? _pendingBootstrapActivation;
|
|
||||||
private static bool _hasDeferredAppNotificationStartup;
|
|
||||||
private static bool _shouldRegisterAppNotifications;
|
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static int Main(string[] args)
|
static int Main(string[] args)
|
||||||
{
|
{
|
||||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||||
|
bool isRedirect = DecideRedirection();
|
||||||
if (TryCaptureCommandLineToastActivation(args))
|
|
||||||
{
|
|
||||||
_shouldRegisterAppNotifications = true;
|
|
||||||
EnsureMailHostRunningMutex();
|
|
||||||
StartApplication();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
|
||||||
var shouldBootstrapCalendarEntry = CalendarEntryBootstrapActivation.ShouldBootstrapToMailHost(activationArgs);
|
|
||||||
_shouldRegisterAppNotifications = !shouldBootstrapCalendarEntry;
|
|
||||||
|
|
||||||
if (shouldBootstrapCalendarEntry && !IsMailHostRunning())
|
|
||||||
{
|
|
||||||
if (CalendarEntryBootstrapActivation.QueuePendingActivation(activationArgs) &&
|
|
||||||
CalendarEntryBootstrapActivation.LaunchMailHost())
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CalendarEntryBootstrapActivation.ClearPendingActivation();
|
|
||||||
}
|
|
||||||
|
|
||||||
_pendingBootstrapActivation = CalendarEntryBootstrapActivation.ConsumePendingActivation();
|
|
||||||
bool isRedirect = DecideRedirection(activationArgs);
|
|
||||||
|
|
||||||
if (!isRedirect)
|
if (!isRedirect)
|
||||||
{
|
{
|
||||||
EnsureMailHostRunningMutex();
|
Application.Start((p) =>
|
||||||
StartApplication();
|
{
|
||||||
|
var context = new DispatcherQueueSynchronizationContext(
|
||||||
|
DispatcherQueue.GetForCurrentThread());
|
||||||
|
SynchronizationContext.SetSynchronizationContext(context);
|
||||||
|
var app = new App();
|
||||||
|
_ = app.HandleInitialActivationAsync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ShouldRegisterAppNotifications()
|
private static bool DecideRedirection()
|
||||||
=> _shouldRegisterAppNotifications;
|
|
||||||
|
|
||||||
internal static bool TryConsumeDeferredAppNotificationStartup()
|
|
||||||
{
|
|
||||||
if (!_hasDeferredAppNotificationStartup)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_hasDeferredAppNotificationStartup = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool TryConsumePendingBootstrapActivation(out PendingBootstrapActivation activation)
|
|
||||||
{
|
|
||||||
if (_pendingBootstrapActivation == null)
|
|
||||||
{
|
|
||||||
activation = null!;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
activation = _pendingBootstrapActivation;
|
|
||||||
_pendingBootstrapActivation = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool DecideRedirection(AppActivationArguments args)
|
|
||||||
{
|
{
|
||||||
bool isRedirect = false;
|
bool isRedirect = false;
|
||||||
|
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
_forceAlternateModeOnLaunch = args.Kind == ExtendedActivationKind.Launch && IsCtrlKeyDown();
|
_forceAlternateModeOnLaunch = args.Kind == ExtendedActivationKind.Launch && IsCtrlKeyDown();
|
||||||
|
|
||||||
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(SingleInstanceKey);
|
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(SingleInstanceKey);
|
||||||
@@ -118,60 +69,6 @@ public class Program
|
|||||||
return isRedirect;
|
return isRedirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryCaptureCommandLineToastActivation(string[] args)
|
|
||||||
{
|
|
||||||
var commandLine = Environment.CommandLine;
|
|
||||||
var prefixIndex = commandLine.IndexOf(AppNotificationActivatedCommandLinePrefix, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (prefixIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Do not touch AppInstance.GetActivatedEventArgs here. For app-notification cold starts,
|
|
||||||
// Windows App SDK expects the app to register AppNotificationManager first and then
|
|
||||||
// resolve the activation inside App.OnLaunched.
|
|
||||||
_hasDeferredAppNotificationStartup = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void StartApplication()
|
|
||||||
{
|
|
||||||
Application.Start((p) =>
|
|
||||||
{
|
|
||||||
var context = new DispatcherQueueSynchronizationContext(
|
|
||||||
DispatcherQueue.GetForCurrentThread());
|
|
||||||
SynchronizationContext.SetSynchronizationContext(context);
|
|
||||||
_ = new App();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsMailHostRunning()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!Mutex.TryOpenExisting(MailHostRunningMutexName, out var existingMutex))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
existingMutex.Dispose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EnsureMailHostRunningMutex()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_mailHostRunningMutex ??= new Mutex(false, MailHostRunningMutexName);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_mailHostRunningMutex = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
private static extern IntPtr CreateEvent(
|
private static extern IntPtr CreateEvent(
|
||||||
IntPtr lpEventAttributes, bool bManualReset,
|
IntPtr lpEventAttributes, bool bManualReset,
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
WinoPage.AboutPage,
|
WinoPage.AboutPage,
|
||||||
WinoPage.PersonalizationPage,
|
WinoPage.PersonalizationPage,
|
||||||
WinoPage.MessageListPage,
|
WinoPage.MessageListPage,
|
||||||
WinoPage.MailNotificationSettingsPage,
|
|
||||||
WinoPage.ReadComposePanePage,
|
WinoPage.ReadComposePanePage,
|
||||||
WinoPage.AppPreferencesPage,
|
WinoPage.AppPreferencesPage,
|
||||||
WinoPage.AliasManagementPage,
|
WinoPage.AliasManagementPage,
|
||||||
@@ -143,7 +142,6 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
WinoPage.AboutPage => typeof(AboutPage),
|
WinoPage.AboutPage => typeof(AboutPage),
|
||||||
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
|
WinoPage.PersonalizationPage => typeof(PersonalizationPage),
|
||||||
WinoPage.MessageListPage => typeof(MessageListPage),
|
WinoPage.MessageListPage => typeof(MessageListPage),
|
||||||
WinoPage.MailNotificationSettingsPage => typeof(MailNotificationSettingsPage),
|
|
||||||
WinoPage.ReadComposePanePage => typeof(ReadComposePanePage),
|
WinoPage.ReadComposePanePage => typeof(ReadComposePanePage),
|
||||||
WinoPage.MailRenderingPage => typeof(MailRenderingPage),
|
WinoPage.MailRenderingPage => typeof(MailRenderingPage),
|
||||||
WinoPage.ComposePage => typeof(ComposePage),
|
WinoPage.ComposePage => typeof(ComposePage),
|
||||||
@@ -243,19 +241,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
_statePersistanceService.AppModeTitle = GetApplicationModeTitle(mode);
|
_statePersistanceService.AppModeTitle = GetApplicationModeTitle(mode);
|
||||||
|
|
||||||
if (coreFrame.Content is IShellHost activeShell && activeShell.HasShellContent && currentMode == mode)
|
if (coreFrame.Content is IShellHost activeShell && activeShell.HasShellContent && currentMode == mode)
|
||||||
{
|
|
||||||
if (activationContext?.Parameter != null)
|
|
||||||
{
|
|
||||||
activeShell.ActivateMode(mode, new ShellModeActivationContext
|
|
||||||
{
|
|
||||||
IsInitialActivation = false,
|
|
||||||
SuppressStartupFlows = activationContext.SuppressStartupFlows,
|
|
||||||
Parameter = activationContext.Parameter
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
_pendingInnerShellTransition = isInitialShellNavigation
|
_pendingInnerShellTransition = isInitialShellNavigation
|
||||||
? null
|
? null
|
||||||
@@ -516,7 +502,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
: DateOnly.FromDateTime(args.NavigationDate.Date);
|
: DateOnly.FromDateTime(args.NavigationDate.Date);
|
||||||
|
|
||||||
var displayRequest = new CalendarDisplayRequest(_statePersistanceService.CalendarDisplayType, targetDate);
|
var displayRequest = new CalendarDisplayRequest(_statePersistanceService.CalendarDisplayType, targetDate);
|
||||||
return new LoadCalendarMessage(displayRequest, args.ForceReload, args.PendingTarget);
|
return new LoadCalendarMessage(displayRequest, args.ForceReload);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool NavigateInnerShellFrame(Frame frame, Type pageType, object? parameter, NavigationTransitionType transition)
|
private bool NavigateInnerShellFrame(Frame frame, Type pageType, object? parameter, NavigationTransitionType transition)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ 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.Extensions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Helpers;
|
|
||||||
using Wino.Mail.WinUI.Activation;
|
using Wino.Mail.WinUI.Activation;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
@@ -27,16 +26,6 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
{
|
{
|
||||||
private const string NotificationIconRootUri = "ms-appx:///Assets/NotificationIcons/";
|
private const string NotificationIconRootUri = "ms-appx:///Assets/NotificationIcons/";
|
||||||
private static int _calendarTaskbarBadgeCount;
|
private static int _calendarTaskbarBadgeCount;
|
||||||
private static readonly MailOperation[] SupportedMailNotificationActions =
|
|
||||||
[
|
|
||||||
MailOperation.MarkAsRead,
|
|
||||||
MailOperation.SoftDelete,
|
|
||||||
MailOperation.MoveToJunk,
|
|
||||||
MailOperation.Archive,
|
|
||||||
MailOperation.Reply,
|
|
||||||
MailOperation.ReplyAll,
|
|
||||||
MailOperation.Forward
|
|
||||||
];
|
|
||||||
|
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IFolderService _folderService;
|
private readonly IFolderService _folderService;
|
||||||
@@ -87,7 +76,6 @@ 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.AddButton(CreateDismissButton());
|
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
||||||
|
|
||||||
ShowNotification(builder);
|
ShowNotification(builder);
|
||||||
@@ -179,7 +167,6 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddButton(new AppNotificationButton(Translator.Buttons_FixAccount)
|
builder.AddButton(new AppNotificationButton(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));
|
||||||
builder.AddButton(CreateDismissButton());
|
|
||||||
|
|
||||||
ShowNotification(builder);
|
ShowNotification(builder);
|
||||||
}
|
}
|
||||||
@@ -190,7 +177,6 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Title);
|
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Title);
|
||||||
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Message);
|
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Message);
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
builder.AddButton(CreateDismissButton());
|
|
||||||
|
|
||||||
ShowNotification(builder);
|
ShowNotification(builder);
|
||||||
}
|
}
|
||||||
@@ -202,7 +188,6 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddText(Translator.Notifications_StoreUpdateAvailableMessage);
|
builder.AddText(Translator.Notifications_StoreUpdateAvailableMessage);
|
||||||
builder.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall);
|
builder.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall);
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
builder.AddButton(CreateDismissButton());
|
|
||||||
|
|
||||||
ShowNotification(builder, "store-update-available");
|
ShowNotification(builder, "store-update-available");
|
||||||
}
|
}
|
||||||
@@ -270,8 +255,6 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddButton(CreateDismissButton());
|
|
||||||
|
|
||||||
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
|
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
|
||||||
ShowNotification(builder, tag);
|
ShowNotification(builder, tag);
|
||||||
|
|
||||||
@@ -305,11 +288,9 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
|
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
|
||||||
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate.ToString());
|
builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate.ToString());
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
builder.AddButton(GetMarkAsReadButton(mailItem.UniqueId));
|
||||||
var (firstAction, secondAction) = GetConfiguredMailNotificationActions();
|
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
||||||
builder.AddButton(CreateMailNotificationActionButton(firstAction, mailItem.UniqueId));
|
builder.AddButton(GetArchiveButton(mailItem.UniqueId));
|
||||||
builder.AddButton(CreateMailNotificationActionButton(secondAction, mailItem.UniqueId));
|
|
||||||
builder.AddButton(CreateDismissButton());
|
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
||||||
|
|
||||||
ShowNotification(builder, mailItem.UniqueId.ToString());
|
ShowNotification(builder, mailItem.UniqueId.ToString());
|
||||||
@@ -366,55 +347,26 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
return string.Format(Translator.CalendarReminder_StartedMinutesAgo, minutesAgo);
|
return string.Format(Translator.CalendarReminder_StartedMinutesAgo, minutesAgo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (MailOperation FirstAction, MailOperation SecondAction) GetConfiguredMailNotificationActions()
|
private AppNotificationButton GetArchiveButton(Guid mailUniqueId)
|
||||||
{
|
=> new AppNotificationButton(Translator.MailOperation_Archive)
|
||||||
var firstAction = ResolveMailNotificationAction(_preferencesService.FirstMailNotificationAction, MailOperation.MarkAsRead);
|
.SetIcon(GetNotificationIconUri("mail-archive"))
|
||||||
var secondAction = ResolveMailNotificationAction(_preferencesService.SecondMailNotificationAction, MailOperation.SoftDelete);
|
|
||||||
|
|
||||||
if (secondAction == firstAction)
|
|
||||||
{
|
|
||||||
secondAction = SupportedMailNotificationActions.First(action => action != firstAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (firstAction, secondAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MailOperation ResolveMailNotificationAction(MailOperation configuredAction, MailOperation fallbackAction)
|
|
||||||
=> SupportedMailNotificationActions.Contains(configuredAction) ? configuredAction : fallbackAction;
|
|
||||||
|
|
||||||
private AppNotificationButton CreateMailNotificationActionButton(MailOperation action, Guid mailUniqueId)
|
|
||||||
{
|
|
||||||
var button = new AppNotificationButton(XamlHelpers.GetOperationString(action))
|
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
.AddArgument(Constants.ToastActionKey, action.ToString())
|
.AddArgument(Constants.ToastActionKey, MailOperation.Archive.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
var iconUri = GetMailActionIconUri(action);
|
private AppNotificationButton GetDeleteButton(Guid mailUniqueId)
|
||||||
if (iconUri != null)
|
=> new AppNotificationButton(Translator.MailOperation_Delete)
|
||||||
{
|
.SetIcon(GetNotificationIconUri("mail-delete"))
|
||||||
button.SetIcon(iconUri);
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
}
|
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete.ToString())
|
||||||
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
return button;
|
private AppNotificationButton GetMarkAsReadButton(Guid mailUniqueId)
|
||||||
}
|
=> new AppNotificationButton(Translator.MailOperation_MarkAsRead)
|
||||||
|
.SetIcon(GetNotificationIconUri("mail-markread"))
|
||||||
private static Uri? GetMailActionIconUri(MailOperation action)
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
=> action switch
|
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead.ToString())
|
||||||
{
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
MailOperation.Archive => GetNotificationIconUri("mail-archive"),
|
|
||||||
MailOperation.SoftDelete => GetNotificationIconUri("mail-delete"),
|
|
||||||
MailOperation.MarkAsRead => GetNotificationIconUri("mail-markread"),
|
|
||||||
MailOperation.MoveToJunk => GetNotificationIconUri("mail-junk"),
|
|
||||||
MailOperation.Reply => GetNotificationIconUri("mail-reply"),
|
|
||||||
MailOperation.ReplyAll => GetNotificationIconUri("mail-replyall"),
|
|
||||||
MailOperation.Forward => GetNotificationIconUri("mail-forward"),
|
|
||||||
_ => null
|
|
||||||
};
|
|
||||||
|
|
||||||
private static AppNotificationButton CreateDismissButton()
|
|
||||||
=> new AppNotificationButton(Translator.Buttons_Dismiss)
|
|
||||||
.SetIcon(GetNotificationIconUri("dismiss"))
|
|
||||||
.AddArgument(Constants.ToastDismissActionKey, bool.TrueString);
|
|
||||||
|
|
||||||
private static AppNotificationBuilder CreateBuilder(AppNotificationScenario scenario = AppNotificationScenario.Default)
|
private static AppNotificationBuilder CreateBuilder(AppNotificationScenario scenario = AppNotificationScenario.Default)
|
||||||
=> new AppNotificationBuilder().SetScenario(scenario);
|
=> new AppNotificationBuilder().SetScenario(scenario);
|
||||||
|
|||||||
@@ -237,18 +237,6 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
|
|||||||
set => SaveProperty(propertyName: nameof(StartupEntityId), value);
|
set => SaveProperty(propertyName: nameof(StartupEntityId), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MailOperation FirstMailNotificationAction
|
|
||||||
{
|
|
||||||
get => _configurationService.Get(nameof(FirstMailNotificationAction), MailOperation.MarkAsRead);
|
|
||||||
set => SetPropertyAndSave(nameof(FirstMailNotificationAction), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MailOperation SecondMailNotificationAction
|
|
||||||
{
|
|
||||||
get => _configurationService.Get(nameof(SecondMailNotificationAction), MailOperation.SoftDelete);
|
|
||||||
set => SetPropertyAndSave(nameof(SecondMailNotificationAction), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppLanguage CurrentLanguage
|
public AppLanguage CurrentLanguage
|
||||||
{
|
{
|
||||||
get => _configurationService.Get(nameof(CurrentLanguage), TranslationService.DefaultAppLanguage);
|
get => _configurationService.Get(nameof(CurrentLanguage), TranslationService.DefaultAppLanguage);
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
using Wino.Mail.ViewModels;
|
|
||||||
|
|
||||||
namespace Wino.Views.Abstract;
|
|
||||||
|
|
||||||
public abstract class MailNotificationSettingsPageAbstract : SettingsPageBase<MailNotificationSettingsPageViewModel> { }
|
|
||||||
@@ -71,20 +71,14 @@ public sealed partial class CalendarPage : CalendarPageAbstract, ITitleBarSearch
|
|||||||
}
|
}
|
||||||
|
|
||||||
var anchorDate = DateOnly.FromDateTime(DateTime.Now.Date);
|
var anchorDate = DateOnly.FromDateTime(DateTime.Now.Date);
|
||||||
CalendarItemTarget? pendingTarget = null;
|
|
||||||
|
|
||||||
if (e.Parameter is CalendarPageNavigationArgs args && !args.RequestDefaultNavigation)
|
if (e.Parameter is CalendarPageNavigationArgs args && !args.RequestDefaultNavigation)
|
||||||
{
|
{
|
||||||
anchorDate = DateOnly.FromDateTime(args.NavigationDate.Date);
|
anchorDate = DateOnly.FromDateTime(args.NavigationDate.Date);
|
||||||
pendingTarget = args.PendingTarget;
|
|
||||||
}
|
|
||||||
else if (e.Parameter is CalendarPageNavigationArgs pendingArgs)
|
|
||||||
{
|
|
||||||
pendingTarget = pendingArgs.PendingTarget;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new CalendarDisplayRequest(ViewModel.StatePersistanceService.CalendarDisplayType, anchorDate);
|
var request = new CalendarDisplayRequest(ViewModel.StatePersistanceService.CalendarDisplayType, anchorDate);
|
||||||
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(request, PendingTarget: pendingTarget));
|
WeakReferenceMessenger.Default.Send(new LoadCalendarMessage(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<abstract:MailNotificationSettingsPageAbstract
|
|
||||||
x:Class="Wino.Views.Settings.MailNotificationSettingsPage"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:abstract="using:Wino.Views.Abstract"
|
|
||||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:domain="using:Wino.Core.Domain"
|
|
||||||
xmlns:mailViewModels="using:Wino.Mail.ViewModels"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<ScrollViewer>
|
|
||||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
|
||||||
<controls:SettingsExpander
|
|
||||||
Description="{x:Bind domain:Translator.SettingsMailNotifications_Actions_Description}"
|
|
||||||
Header="{x:Bind domain:Translator.SettingsMailNotifications_Actions_Title}"
|
|
||||||
IsExpanded="True">
|
|
||||||
<controls:SettingsExpander.HeaderIcon>
|
|
||||||
<PathIcon Data="F1 M 6.347656 16.25 L 3.701172 16.25 C 3.375651 16.25 3.064779 16.18327 2.768555 16.049805 C 2.472331 15.916342 2.211914 15.737305 1.987305 15.512695 C 1.762695 15.288086 1.583659 15.02767 1.450195 14.731445 C 1.316732 14.435222 1.25 14.12435 1.25 13.798828 L 1.25 3.701172 C 1.25 3.375652 1.316732 3.064779 1.450195 2.768555 C 1.583659 2.472332 1.762695 2.211914 1.987305 1.987305 C 2.211914 1.762695 2.472331 1.58366 2.768555 1.450195 C 3.064779 1.316732 3.375651 1.25 3.701172 1.25 L 16.298828 1.25 C 16.624348 1.25 16.935221 1.316732 17.231445 1.450195 C 17.527668 1.58366 17.788086 1.762695 18.012695 1.987305 C 18.237305 2.211914 18.41634 2.472332 18.549805 2.768555 C 18.683268 3.064779 18.75 3.375652 18.75 3.701172 L 18.75 13.798828 C 18.75 14.13737 18.681641 14.454753 18.544922 14.750977 C 18.408203 15.047201 18.22591 15.30599 17.998047 15.527344 C 17.770182 15.748698 17.504883 15.924479 17.202148 16.054688 C 16.899414 16.184896 16.582031 16.25 16.25 16.25 L 13.652344 16.25 L 10.46875 19.794922 C 10.345052 19.931641 10.188802 20 10 20 C 9.811197 20 9.654947 19.931641 9.53125 19.794922 Z M 15.625 7.5 C 15.79427 7.5 15.940754 7.438151 16.064453 7.314453 C 16.18815 7.190756 16.25 7.044271 16.25 6.875 C 16.25 6.705729 16.18815 6.559245 16.064453 6.435547 C 15.940754 6.31185 15.79427 6.25 15.625 6.25 L 4.375 6.25 C 4.205729 6.25 4.059245 6.31185 3.935547 6.435547 C 3.811849 6.559245 3.75 6.705729 3.75 6.875 C 3.75 7.044271 3.811849 7.190756 3.935547 7.314453 C 4.059245 7.438151 4.205729 7.5 4.375 7.5 Z M 15.625 11.25 C 15.79427 11.25 15.940754 11.188151 16.064453 11.064453 C 16.18815 10.940756 16.25 10.794271 16.25 10.625 C 16.25 10.455729 16.18815 10.309245 16.064453 10.185547 C 15.940754 10.06185 15.79427 10 15.625 10 L 4.375 10 C 4.205729 10 4.059245 10.06185 3.935547 10.185547 C 3.811849 10.309245 3.75 10.455729 3.75 10.625 C 3.75 10.794271 3.811849 10.940756 3.935547 11.064453 C 4.059245 11.188151 4.205729 11.25 4.375 11.25 Z " />
|
|
||||||
</controls:SettingsExpander.HeaderIcon>
|
|
||||||
<controls:SettingsExpander.Items>
|
|
||||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsMailNotifications_FirstAction_Description}" Header="{x:Bind domain:Translator.SettingsMailNotifications_FirstAction_Title}">
|
|
||||||
|
|
||||||
<ComboBox ItemsSource="{x:Bind ViewModel.AvailableNotificationActions, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedFirstAction, Mode=TwoWay}">
|
|
||||||
<ComboBox.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="mailViewModels:MailNotificationActionOption">
|
|
||||||
<TextBlock Text="{x:Bind DisplayText}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ComboBox.ItemTemplate>
|
|
||||||
</ComboBox>
|
|
||||||
</controls:SettingsCard>
|
|
||||||
|
|
||||||
<controls:SettingsCard Description="{x:Bind domain:Translator.SettingsMailNotifications_SecondAction_Description}" Header="{x:Bind domain:Translator.SettingsMailNotifications_SecondAction_Title}">
|
|
||||||
|
|
||||||
<ComboBox ItemsSource="{x:Bind ViewModel.AvailableNotificationActions, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedSecondAction, Mode=TwoWay}">
|
|
||||||
<ComboBox.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="mailViewModels:MailNotificationActionOption">
|
|
||||||
<TextBlock Text="{x:Bind DisplayText}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ComboBox.ItemTemplate>
|
|
||||||
</ComboBox>
|
|
||||||
</controls:SettingsCard>
|
|
||||||
</controls:SettingsExpander.Items>
|
|
||||||
</controls:SettingsExpander>
|
|
||||||
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
</abstract:MailNotificationSettingsPageAbstract>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Wino.Views.Abstract;
|
|
||||||
|
|
||||||
namespace Wino.Views.Settings;
|
|
||||||
|
|
||||||
public sealed partial class MailNotificationSettingsPage : MailNotificationSettingsPageAbstract
|
|
||||||
{
|
|
||||||
public MailNotificationSettingsPage()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -89,16 +89,7 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
|
|||||||
public void ActivateMode(WinoApplicationMode mode, ShellModeActivationContext activationContext)
|
public void ActivateMode(WinoApplicationMode mode, ShellModeActivationContext activationContext)
|
||||||
{
|
{
|
||||||
if (_activeMode == mode && InnerShellFrame.Content != null)
|
if (_activeMode == mode && InnerShellFrame.Content != null)
|
||||||
{
|
|
||||||
if (activationContext.Parameter != null)
|
|
||||||
{
|
|
||||||
ViewModel.SetCurrentMode(mode);
|
|
||||||
ViewModel.CurrentClient.Activate(activationContext);
|
|
||||||
NotifyTitleBarContentChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
DeactivateCurrentMode();
|
DeactivateCurrentMode();
|
||||||
ResetShellModeNavigationState();
|
ResetShellModeNavigationState();
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#nullable enable
|
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
namespace Wino.Messaging.Client.Calendar;
|
namespace Wino.Messaging.Client.Calendar;
|
||||||
@@ -8,8 +7,4 @@ namespace Wino.Messaging.Client.Calendar;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="DisplayRequest">Display type and anchor date to resolve.</param>
|
/// <param name="DisplayRequest">Display type and anchor date to resolve.</param>
|
||||||
/// <param name="ForceReload">Force a reload even if the resolved range did not change.</param>
|
/// <param name="ForceReload">Force a reload even if the resolved range did not change.</param>
|
||||||
/// <param name="PendingTarget">Optional event target to open after the requested range is loaded.</param>
|
public record LoadCalendarMessage(CalendarDisplayRequest DisplayRequest, bool ForceReload = false);
|
||||||
public record LoadCalendarMessage(
|
|
||||||
CalendarDisplayRequest DisplayRequest,
|
|
||||||
bool ForceReload = false,
|
|
||||||
CalendarItemTarget? PendingTarget = null);
|
|
||||||
|
|||||||
@@ -538,26 +538,6 @@ public class AccountService : BaseDatabaseService, IAccountService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localAliases.Count == 0 && !string.IsNullOrWhiteSpace(account.Address))
|
|
||||||
{
|
|
||||||
var fallbackAddress = account.Address.Trim();
|
|
||||||
var fallbackAlias = new MailAccountAlias()
|
|
||||||
{
|
|
||||||
AccountId = account.Id,
|
|
||||||
AliasAddress = fallbackAddress,
|
|
||||||
IsPrimary = true,
|
|
||||||
IsRootAlias = true,
|
|
||||||
IsVerified = true,
|
|
||||||
ReplyToAddress = fallbackAddress,
|
|
||||||
Id = Guid.NewGuid(),
|
|
||||||
Source = AliasSource.ProviderDiscovered,
|
|
||||||
SendCapability = AliasSendCapability.Confirmed
|
|
||||||
};
|
|
||||||
|
|
||||||
await Connection.InsertAsync(fallbackAlias, typeof(MailAccountAlias)).ConfigureAwait(false);
|
|
||||||
localAliases.Add(fallbackAlias);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure there is only 1 root alias and 1 primary alias selected.
|
// Make sure there is only 1 root alias and 1 primary alias selected.
|
||||||
|
|
||||||
bool shouldUpdatePrimary = localAliases.Count(a => a.IsPrimary) != 1;
|
bool shouldUpdatePrimary = localAliases.Count(a => a.IsPrimary) != 1;
|
||||||
|
|||||||