Improved keyboad shortcuts.
This commit is contained in:
@@ -17,6 +17,7 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Extensions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.ViewModels;
|
||||
@@ -393,6 +394,18 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
||||
});
|
||||
}
|
||||
|
||||
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
||||
{
|
||||
if (args.Handled || args.Mode != WinoApplicationMode.Calendar)
|
||||
return;
|
||||
|
||||
if (args.Action == KeyboardShortcutAction.NewEvent)
|
||||
{
|
||||
await NewEventAsync();
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -14,6 +14,7 @@ using Serilog;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Calendar.ViewModels.Interfaces;
|
||||
using Wino.Calendar.ViewModels.Messages;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Collections;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -21,6 +22,7 @@ using Wino.Core.Domain.Extensions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Calendar.CalendarTypeStrategies;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
@@ -135,6 +137,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
||||
private readonly IMailDialogService _dialogService;
|
||||
|
||||
// Store latest rendered options.
|
||||
private CalendarDisplayType _currentDisplayType;
|
||||
@@ -156,7 +159,8 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
INativeAppService nativeAppService,
|
||||
IAccountCalendarStateService accountCalendarStateService,
|
||||
IPreferencesService preferencesService,
|
||||
IWinoRequestDelegator winoRequestDelegator)
|
||||
IWinoRequestDelegator winoRequestDelegator,
|
||||
IMailDialogService dialogService)
|
||||
{
|
||||
StatePersistanceService = statePersistanceService;
|
||||
AccountCalendarStateService = accountCalendarStateService;
|
||||
@@ -167,6 +171,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
_nativeAppService = nativeAppService;
|
||||
_preferencesService = preferencesService;
|
||||
_winoRequestDelegator = winoRequestDelegator;
|
||||
_dialogService = dialogService;
|
||||
|
||||
AccountCalendarStateService.AccountCalendarSelectionStateChanged += UpdateAccountCalendarRequested;
|
||||
AccountCalendarStateService.CollectiveAccountGroupSelectionStateChanged += AccountCalendarStateCollectivelyChanged;
|
||||
@@ -175,6 +180,35 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
||||
RegisterRecipients();
|
||||
}
|
||||
|
||||
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
||||
{
|
||||
if (args.Handled || args.Mode != WinoApplicationMode.Calendar || args.Action != KeyboardShortcutAction.Delete)
|
||||
return;
|
||||
|
||||
if (DisplayDetailsCalendarItemViewModel?.CalendarItem == null)
|
||||
return;
|
||||
|
||||
if (DisplayDetailsCalendarItemViewModel.CalendarItem.IsRecurringParent)
|
||||
{
|
||||
var confirmed = await _dialogService.ShowConfirmationDialogAsync(
|
||||
Translator.DialogMessage_DeleteRecurringSeriesMessage,
|
||||
Translator.DialogMessage_DeleteRecurringSeriesTitle,
|
||||
Translator.Buttons_Delete);
|
||||
|
||||
if (!confirmed)
|
||||
return;
|
||||
}
|
||||
|
||||
var preparationRequest = new CalendarOperationPreparationRequest(
|
||||
CalendarSynchronizerOperation.DeleteEvent,
|
||||
DisplayDetailsCalendarItemViewModel.CalendarItem,
|
||||
null);
|
||||
|
||||
await _winoRequestDelegator.ExecuteAsync(preparationRequest);
|
||||
DisplayDetailsCalendarItemViewModel = null;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
protected override void RegisterRecipients()
|
||||
{
|
||||
base.RegisterRecipients();
|
||||
|
||||
@@ -15,6 +15,7 @@ using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Services;
|
||||
using Wino.Core.ViewModels;
|
||||
@@ -498,6 +499,15 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
||||
{
|
||||
if (args.Handled || args.Mode != WinoApplicationMode.Calendar || args.Action != KeyboardShortcutAction.Delete)
|
||||
return;
|
||||
|
||||
await DeleteAsync();
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task JoinOnlineAsync()
|
||||
{
|
||||
|
||||
@@ -12,6 +12,11 @@ public class KeyboardShortcut
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The application mode this shortcut applies to.
|
||||
/// </summary>
|
||||
public WinoApplicationMode Mode { get; set; } = WinoApplicationMode.Mail;
|
||||
|
||||
/// <summary>
|
||||
/// The key combination string (e.g., "D", "Delete", "F1").
|
||||
/// </summary>
|
||||
@@ -23,9 +28,9 @@ public class KeyboardShortcut
|
||||
public ModifierKeys ModifierKeys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mail operation this shortcut triggers.
|
||||
/// The shortcut action this shortcut triggers.
|
||||
/// </summary>
|
||||
public MailOperation MailOperation { get; set; }
|
||||
public KeyboardShortcutAction Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this shortcut is enabled.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum KeyboardShortcutAction
|
||||
{
|
||||
None,
|
||||
NewMail,
|
||||
ToggleReadUnread,
|
||||
ToggleFlag,
|
||||
ToggleArchive,
|
||||
Delete,
|
||||
Move,
|
||||
Reply,
|
||||
ReplyAll,
|
||||
Send,
|
||||
NewEvent
|
||||
}
|
||||
@@ -37,21 +37,23 @@ public interface IKeyboardShortcutService
|
||||
Task DeleteKeyboardShortcutAsync(Guid shortcutId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mail operation for the given key combination.
|
||||
/// Gets the keyboard shortcut for the given key combination in a specific mode.
|
||||
/// </summary>
|
||||
/// <param name="mode">The application mode to search within.</param>
|
||||
/// <param name="key">The pressed key.</param>
|
||||
/// <param name="modifierKeys">The modifier keys pressed.</param>
|
||||
/// <returns>The mail operation if found, otherwise null.</returns>
|
||||
Task<MailOperation?> GetMailOperationForKeyAsync(string key, ModifierKeys modifierKeys);
|
||||
/// <returns>The matching shortcut if found, otherwise null.</returns>
|
||||
Task<KeyboardShortcut> GetShortcutForKeyAsync(WinoApplicationMode mode, string key, ModifierKeys modifierKeys);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a key combination is already assigned to another shortcut.
|
||||
/// </summary>
|
||||
/// <param name="mode">The application mode to check within.</param>
|
||||
/// <param name="key">The key to check.</param>
|
||||
/// <param name="modifierKeys">The modifier keys to check.</param>
|
||||
/// <param name="excludeShortcutId">Optional ID to exclude from the check (for updates).</param>
|
||||
/// <returns>True if the combination is already used, false otherwise.</returns>
|
||||
Task<bool> IsKeyCombinationInUseAsync(string key, ModifierKeys modifierKeys, Guid? excludeShortcutId = null);
|
||||
Task<bool> IsKeyCombinationInUseAsync(WinoApplicationMode mode, string key, ModifierKeys modifierKeys, Guid? excludeShortcutId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates default keyboard shortcuts for common mail operations.
|
||||
|
||||
@@ -12,6 +12,11 @@ public class KeyboardShortcutDialogResult
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The application mode selected by the user.
|
||||
/// </summary>
|
||||
public WinoApplicationMode Mode { get; set; } = WinoApplicationMode.Mail;
|
||||
|
||||
/// <summary>
|
||||
/// The key combination entered by the user.
|
||||
/// </summary>
|
||||
@@ -23,21 +28,22 @@ public class KeyboardShortcutDialogResult
|
||||
public ModifierKeys ModifierKeys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mail operation selected by the user.
|
||||
/// The shortcut action selected by the user.
|
||||
/// </summary>
|
||||
public MailOperation MailOperation { get; set; }
|
||||
public KeyboardShortcutAction Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful result.
|
||||
/// </summary>
|
||||
public static KeyboardShortcutDialogResult Success(string key, ModifierKeys modifierKeys, MailOperation mailOperation)
|
||||
public static KeyboardShortcutDialogResult Success(WinoApplicationMode mode, string key, ModifierKeys modifierKeys, KeyboardShortcutAction action)
|
||||
{
|
||||
return new KeyboardShortcutDialogResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
Mode = mode,
|
||||
Key = key,
|
||||
ModifierKeys = modifierKeys,
|
||||
MailOperation = mailOperation
|
||||
Action = action
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Models;
|
||||
|
||||
public class KeyboardShortcutTriggerDetails
|
||||
{
|
||||
public Guid ShortcutId { get; init; }
|
||||
public WinoApplicationMode Mode { get; init; }
|
||||
public KeyboardShortcutAction Action { get; init; }
|
||||
public string Key { get; init; } = string.Empty;
|
||||
public ModifierKeys ModifierKeys { get; init; }
|
||||
public bool Handled { get; set; }
|
||||
public object Sender { get; init; }
|
||||
public object Origin { get; init; }
|
||||
}
|
||||
@@ -362,19 +362,29 @@
|
||||
"KeyboardShortcuts_FailedToReset": "Failed to reset keyboard shortcuts.",
|
||||
"KeyboardShortcuts_FailedToUpdate": "Failed to update keyboard shortcuts",
|
||||
"KeyboardShortcuts_MailoperationAction": "Action",
|
||||
"KeyboardShortcuts_Action": "Action",
|
||||
"KeyboardShortcuts_FailedToLoad": "Failed to load keyboard shortcuts.",
|
||||
"KeyboardShortcuts_EnterKeyForShortcut": "Please enter a key for the shortcut.",
|
||||
"KeyboardShortcuts_SelectOperationForShortcut": "Please an action to perform for the shortcut.",
|
||||
"KeyboardShortcuts_EnterKey": "Please enter a key for the shortcut.",
|
||||
"KeyboardShortcuts_SelectOperation": "Please select an action for the shortcut.",
|
||||
"KeyboardShortcuts_ShortcutInUse": "This shortcut is already in use by another s hortcut.",
|
||||
"KeyboardShortcuts_FailedToSave": "Failed to save the shortcut.",
|
||||
"KeyboardShortcuts_FailedToDelete": "Failed to delete the shortcut.",
|
||||
"KeyboardShortcuts_PageDescription": "Set up keyboard shortcuts for quick mail operations. Press keys while focused on the key input field to capture shortcuts.",
|
||||
"KeyboardShortcuts_Add": "Add shortcut",
|
||||
"KeyboardShortcuts_EditTitle": "Edit Keyboard Shortcut",
|
||||
"KeyboardShortcuts_ResetToDefaults": "Reset to Defaults",
|
||||
"KeyboardShortcuts_PressKeysHere": "Press keys here...",
|
||||
"KeyboardShortcuts_KeyCombination": "Key Combination",
|
||||
"KeyboardShortcuts_FocusArea": "Focus the field above and press the desired key combination",
|
||||
"KeyboardShortcuts_Modifiers": "Modifier Keys",
|
||||
"KeyboardShortcuts_Mode": "App Mode",
|
||||
"KeyboardShortcuts_ModeMail": "Mail",
|
||||
"KeyboardShortcuts_ModeCalendar": "Calendar",
|
||||
"KeyboardShortcuts_ActionToggleReadUnread": "Toggle read/unread",
|
||||
"KeyboardShortcuts_ActionToggleFlag": "Toggle flag",
|
||||
"KeyboardShortcuts_ActionToggleArchive": "Toggle archive/unarchive",
|
||||
"ImageRenderingDisabled": "Image rendering is disabled for this message.",
|
||||
"ImapAdvancedSetupDialog_AuthenticationMethod": "Authentication method",
|
||||
"ImapAdvancedSetupDialog_ConnectionSecurity": "Connection security",
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
|
||||
namespace Wino.Core.ViewModels;
|
||||
@@ -40,6 +41,8 @@ public class CoreBaseViewModel : ObservableRecipient, INavigationAware
|
||||
|
||||
public virtual void OnPageLoaded() { }
|
||||
|
||||
public virtual Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args) => Task.CompletedTask;
|
||||
|
||||
public Task ExecuteUIThread(Action action)
|
||||
{
|
||||
if (action == null) return Task.CompletedTask;
|
||||
|
||||
@@ -13,13 +13,16 @@ public partial class BreadcrumbNavigationItemViewModel : ObservableObject
|
||||
|
||||
public int StepNumber { get; set; }
|
||||
|
||||
public int BackStackDepth { get; set; }
|
||||
|
||||
public BreadcrumbNavigationRequested Request { get; set; }
|
||||
|
||||
public BreadcrumbNavigationItemViewModel(BreadcrumbNavigationRequested request, bool isActive, int stepNumber = 0)
|
||||
public BreadcrumbNavigationItemViewModel(BreadcrumbNavigationRequested request, bool isActive, int stepNumber = 0, int backStackDepth = 0)
|
||||
{
|
||||
Request = request;
|
||||
Title = request.PageTitle;
|
||||
IsActive = isActive;
|
||||
StepNumber = stepNumber;
|
||||
BackStackDepth = backStackDepth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.ViewModels.Data;
|
||||
|
||||
public class KeyboardShortcutActionViewModel
|
||||
{
|
||||
public WinoApplicationMode Mode { get; }
|
||||
public KeyboardShortcutAction Action { get; }
|
||||
|
||||
public string DisplayName => Action switch
|
||||
{
|
||||
KeyboardShortcutAction.NewMail => Translator.MenuNewMail,
|
||||
KeyboardShortcutAction.ToggleReadUnread => Translator.KeyboardShortcuts_ActionToggleReadUnread,
|
||||
KeyboardShortcutAction.ToggleFlag => Translator.KeyboardShortcuts_ActionToggleFlag,
|
||||
KeyboardShortcutAction.ToggleArchive => Translator.KeyboardShortcuts_ActionToggleArchive,
|
||||
KeyboardShortcutAction.Delete => Translator.Buttons_Delete,
|
||||
KeyboardShortcutAction.Move => Translator.MailOperation_Move,
|
||||
KeyboardShortcutAction.Reply => Translator.MailOperation_Reply,
|
||||
KeyboardShortcutAction.ReplyAll => Translator.MailOperation_ReplyAll,
|
||||
KeyboardShortcutAction.Send => Translator.Buttons_Send,
|
||||
KeyboardShortcutAction.NewEvent => Translator.CalendarEventCompose_NewEventButton,
|
||||
_ => Action.ToString()
|
||||
};
|
||||
|
||||
public KeyboardShortcutActionViewModel(WinoApplicationMode mode, KeyboardShortcutAction action)
|
||||
{
|
||||
Mode = mode;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public override string ToString() => DisplayName;
|
||||
}
|
||||
@@ -15,9 +15,10 @@ public partial class KeyboardShortcutViewModel : ObservableObject
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
public Guid Id { get; }
|
||||
public WinoApplicationMode Mode { get; }
|
||||
public string Key { get; }
|
||||
public ModifierKeys ModifierKeys { get; }
|
||||
public MailOperation MailOperation { get; }
|
||||
public KeyboardShortcutAction Action { get; }
|
||||
public DateTime CreatedAt { get; }
|
||||
|
||||
public string DisplayName
|
||||
@@ -38,25 +39,30 @@ public partial class KeyboardShortcutViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
public string MailOperationDisplayName
|
||||
public string ModeDisplayName => Mode switch
|
||||
{
|
||||
WinoApplicationMode.Mail => Translator.KeyboardShortcuts_ModeMail,
|
||||
WinoApplicationMode.Calendar => Translator.KeyboardShortcuts_ModeCalendar,
|
||||
_ => Mode.ToString()
|
||||
};
|
||||
|
||||
public string ActionDisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
return MailOperation switch
|
||||
return Action switch
|
||||
{
|
||||
MailOperation.Archive => Translator.MailOperation_Archive,
|
||||
MailOperation.UnArchive => Translator.MailOperation_Unarchive,
|
||||
MailOperation.SoftDelete => Translator.MailOperation_Delete,
|
||||
MailOperation.Move => Translator.MailOperation_Move,
|
||||
MailOperation.MoveToJunk => Translator.MailOperation_MoveJunk,
|
||||
MailOperation.SetFlag => Translator.MailOperation_SetFlag,
|
||||
MailOperation.ClearFlag => Translator.MailOperation_ClearFlag,
|
||||
MailOperation.MarkAsRead => Translator.MailOperation_MarkAsRead,
|
||||
MailOperation.MarkAsUnread => Translator.MailOperation_MarkAsUnread,
|
||||
MailOperation.Reply => Translator.MailOperation_Reply,
|
||||
MailOperation.ReplyAll => Translator.MailOperation_ReplyAll,
|
||||
MailOperation.Forward => Translator.MailOperation_Forward,
|
||||
_ => MailOperation.ToString()
|
||||
KeyboardShortcutAction.NewMail => Translator.MenuNewMail,
|
||||
KeyboardShortcutAction.ToggleReadUnread => Translator.KeyboardShortcuts_ActionToggleReadUnread,
|
||||
KeyboardShortcutAction.ToggleFlag => Translator.KeyboardShortcuts_ActionToggleFlag,
|
||||
KeyboardShortcutAction.ToggleArchive => Translator.KeyboardShortcuts_ActionToggleArchive,
|
||||
KeyboardShortcutAction.Delete => Translator.Buttons_Delete,
|
||||
KeyboardShortcutAction.Move => Translator.MailOperation_Move,
|
||||
KeyboardShortcutAction.Reply => Translator.MailOperation_Reply,
|
||||
KeyboardShortcutAction.ReplyAll => Translator.MailOperation_ReplyAll,
|
||||
KeyboardShortcutAction.Send => Translator.Buttons_Send,
|
||||
KeyboardShortcutAction.NewEvent => Translator.CalendarEventCompose_NewEventButton,
|
||||
_ => Action.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,9 +70,10 @@ public partial class KeyboardShortcutViewModel : ObservableObject
|
||||
public KeyboardShortcutViewModel(KeyboardShortcut shortcut)
|
||||
{
|
||||
Id = shortcut.Id;
|
||||
Mode = shortcut.Mode;
|
||||
Key = shortcut.Key;
|
||||
ModifierKeys = shortcut.ModifierKeys;
|
||||
MailOperation = shortcut.MailOperation;
|
||||
Action = shortcut.Action;
|
||||
CreatedAt = shortcut.CreatedAt;
|
||||
IsEnabled = shortcut.IsEnabled;
|
||||
}
|
||||
@@ -76,9 +83,10 @@ public partial class KeyboardShortcutViewModel : ObservableObject
|
||||
return new KeyboardShortcut
|
||||
{
|
||||
Id = Id,
|
||||
Mode = Mode,
|
||||
Key = Key,
|
||||
ModifierKeys = ModifierKeys,
|
||||
MailOperation = MailOperation,
|
||||
Action = Action,
|
||||
CreatedAt = CreatedAt,
|
||||
IsEnabled = IsEnabled
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ public partial class KeyboardShortcutsPageViewModel : CoreBaseViewModel
|
||||
try
|
||||
{
|
||||
// Check if key combination is already in use
|
||||
var isInUse = await _keyboardShortcutService.IsKeyCombinationInUseAsync(result.Key, result.ModifierKeys, null);
|
||||
var isInUse = await _keyboardShortcutService.IsKeyCombinationInUseAsync(result.Mode, result.Key, result.ModifierKeys, null);
|
||||
if (isInUse)
|
||||
{
|
||||
await _dialogService.ShowMessageAsync(Translator.KeyboardShortcuts_ShortcutInUse, Translator.GeneralTitle_Error, WinoCustomMessageDialogIcon.Error);
|
||||
@@ -80,9 +80,10 @@ public partial class KeyboardShortcutsPageViewModel : CoreBaseViewModel
|
||||
// Create new shortcut
|
||||
var shortcut = new KeyboardShortcut
|
||||
{
|
||||
Mode = result.Mode,
|
||||
Key = result.Key,
|
||||
ModifierKeys = result.ModifierKeys,
|
||||
MailOperation = result.MailOperation,
|
||||
Action = result.Action,
|
||||
IsEnabled = true
|
||||
};
|
||||
|
||||
@@ -116,7 +117,7 @@ public partial class KeyboardShortcutsPageViewModel : CoreBaseViewModel
|
||||
try
|
||||
{
|
||||
// Check if key combination is already in use (excluding current shortcut)
|
||||
var isInUse = await _keyboardShortcutService.IsKeyCombinationInUseAsync(result.Key, result.ModifierKeys, shortcut.Id);
|
||||
var isInUse = await _keyboardShortcutService.IsKeyCombinationInUseAsync(result.Mode, result.Key, result.ModifierKeys, shortcut.Id);
|
||||
if (isInUse)
|
||||
{
|
||||
await _dialogService.ShowMessageAsync(Translator.KeyboardShortcuts_ShortcutInUse, Translator.GeneralTitle_Error, WinoCustomMessageDialogIcon.Error);
|
||||
@@ -125,9 +126,10 @@ public partial class KeyboardShortcutsPageViewModel : CoreBaseViewModel
|
||||
|
||||
// Update existing shortcut
|
||||
var updatedShortcut = shortcut.ToEntity();
|
||||
updatedShortcut.Mode = result.Mode;
|
||||
updatedShortcut.Key = result.Key;
|
||||
updatedShortcut.ModifierKeys = result.ModifierKeys;
|
||||
updatedShortcut.MailOperation = result.MailOperation;
|
||||
updatedShortcut.Action = result.Action;
|
||||
|
||||
await _keyboardShortcutService.SaveKeyboardShortcutAsync(updatedShortcut);
|
||||
await LoadShortcutsAsync();
|
||||
|
||||
@@ -18,6 +18,7 @@ using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Extensions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Extensions;
|
||||
using Wino.Core.Services;
|
||||
@@ -38,6 +39,18 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
||||
|
||||
public Func<Task<string>> GetHTMLBodyFunction;
|
||||
|
||||
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
||||
{
|
||||
if (args.Handled || args.Mode != WinoApplicationMode.Mail)
|
||||
return;
|
||||
|
||||
if (args.Action == KeyboardShortcutAction.Send)
|
||||
{
|
||||
await SendAsync();
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// When we send the message or discard it, we need to block the mime update
|
||||
// Update is triggered when we leave the page.
|
||||
private bool isUpdatingMimeBlocked = false;
|
||||
|
||||
@@ -16,6 +16,7 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.MenuItems;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
@@ -935,6 +936,18 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||
}
|
||||
|
||||
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
||||
{
|
||||
if (args.Handled || args.Mode != WinoApplicationMode.Mail)
|
||||
return;
|
||||
|
||||
if (args.Action == KeyboardShortcutAction.NewMail)
|
||||
{
|
||||
await HandleCreateNewMailAsync();
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: Handle by messaging.
|
||||
|
||||
@@ -18,6 +18,7 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Menus;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
@@ -72,6 +73,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IMailDialogService _mailDialogService;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IMimeFileService _mimeFileService;
|
||||
private readonly INotificationBuilder _notificationBuilder;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IContextMenuItemService _contextMenuItemService;
|
||||
@@ -165,6 +167,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
IAccountService accountService,
|
||||
IMailDialogService mailDialogService,
|
||||
IMailService mailService,
|
||||
IMimeFileService mimeFileService,
|
||||
IStatePersistanceService statePersistenceService,
|
||||
INotificationBuilder notificationBuilder,
|
||||
IFolderService folderService,
|
||||
@@ -179,6 +182,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
_accountService = accountService;
|
||||
_mailDialogService = mailDialogService;
|
||||
_mailService = mailService;
|
||||
_mimeFileService = mimeFileService;
|
||||
_folderService = folderService;
|
||||
_contextMenuItemService = contextMenuItemService;
|
||||
_winoRequestDelegator = winoRequestDelegator;
|
||||
@@ -610,6 +614,87 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
public Task ExecuteMailOperationAsync(MailOperationPreperationRequest package) => _winoRequestDelegator.ExecuteAsync(package);
|
||||
|
||||
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
||||
{
|
||||
if (args.Handled || args.Mode != WinoApplicationMode.Mail)
|
||||
return;
|
||||
|
||||
var targetItems = GetShortcutTargetItems().ToList();
|
||||
|
||||
switch (args.Action)
|
||||
{
|
||||
case KeyboardShortcutAction.ToggleReadUnread:
|
||||
if (!targetItems.Any()) return;
|
||||
await ExecuteMailOperationAsync(new MailOperationPreperationRequest(MailOperation.MarkAsRead, targetItems.Select(x => x.MailCopy), true));
|
||||
args.Handled = true;
|
||||
break;
|
||||
case KeyboardShortcutAction.ToggleFlag:
|
||||
if (!targetItems.Any()) return;
|
||||
await ExecuteMailOperationAsync(new MailOperationPreperationRequest(MailOperation.SetFlag, targetItems.Select(x => x.MailCopy), true));
|
||||
args.Handled = true;
|
||||
break;
|
||||
case KeyboardShortcutAction.ToggleArchive:
|
||||
if (!targetItems.Any()) return;
|
||||
await ExecuteMailOperationAsync(new MailOperationPreperationRequest(MailOperation.Archive, targetItems.Select(x => x.MailCopy), true));
|
||||
args.Handled = true;
|
||||
break;
|
||||
case KeyboardShortcutAction.Delete:
|
||||
if (!targetItems.Any()) return;
|
||||
await ExecuteMailOperationAsync(new MailOperationPreperationRequest(MailOperation.SoftDelete, targetItems.Select(x => x.MailCopy)));
|
||||
args.Handled = true;
|
||||
break;
|
||||
case KeyboardShortcutAction.Move:
|
||||
if (!targetItems.Any()) return;
|
||||
await ExecuteMailOperationAsync(new MailOperationPreperationRequest(MailOperation.Move, targetItems.Select(x => x.MailCopy)));
|
||||
args.Handled = true;
|
||||
break;
|
||||
case KeyboardShortcutAction.Reply:
|
||||
await CreateReplyDraftAsync(DraftCreationReason.Reply);
|
||||
args.Handled = true;
|
||||
break;
|
||||
case KeyboardShortcutAction.ReplyAll:
|
||||
await CreateReplyDraftAsync(DraftCreationReason.ReplyAll);
|
||||
args.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<MailItemViewModel> GetShortcutTargetItems()
|
||||
{
|
||||
if (MailCollection.SelectedItemsCount > 0)
|
||||
return MailCollection.SelectedItems.OfType<MailItemViewModel>();
|
||||
|
||||
if (_activeMailItem != null)
|
||||
return [_activeMailItem];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private async Task CreateReplyDraftAsync(DraftCreationReason reason)
|
||||
{
|
||||
var targetMail = GetShortcutTargetItems().FirstOrDefault();
|
||||
if (targetMail?.MailCopy == null || targetMail.MailCopy.FileId == Guid.Empty)
|
||||
return;
|
||||
|
||||
var mimeInformation = await _mimeFileService.GetMimeMessageInformationAsync(targetMail.MailCopy.FileId, targetMail.MailCopy.AssignedAccount.Id);
|
||||
if (mimeInformation?.MimeMessage == null)
|
||||
return;
|
||||
|
||||
var draftOptions = new DraftCreationOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReferencedMessage = new ReferencedMessage
|
||||
{
|
||||
MimeMessage = mimeInformation.MimeMessage,
|
||||
MailCopy = targetMail.MailCopy
|
||||
}
|
||||
};
|
||||
|
||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(targetMail.MailCopy.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
||||
var draftPreparationRequest = new DraftPreparationRequest(targetMail.MailCopy.AssignedAccount, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, targetMail.MailCopy);
|
||||
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||
}
|
||||
|
||||
public IEnumerable<MailOperationMenuItem> GetAvailableMailActions(IEnumerable<MailItemViewModel> contextMailItems)
|
||||
=> _contextMenuItemService.GetMailItemContextMenuActions(contextMailItems.Select(a => a.MailCopy));
|
||||
|
||||
|
||||
@@ -37,11 +37,14 @@ public partial class BasePage : Page, IRecipient<LanguageChanged>
|
||||
/// Unregister message recipients for this page. Override to unregister specific message types.
|
||||
/// </summary>
|
||||
protected virtual void UnregisterRecipients() { }
|
||||
|
||||
public virtual CoreBaseViewModel? AssociatedViewModel => null;
|
||||
}
|
||||
|
||||
public abstract class BasePage<T> : BasePage where T : CoreBaseViewModel
|
||||
{
|
||||
public T ViewModel { get; } = WinoApplication.Current.Services.GetService<T>() ?? throw new ArgumentException($"Can't resolve '{typeof(T)}' as view model.");
|
||||
public override CoreBaseViewModel AssociatedViewModel => ViewModel;
|
||||
|
||||
protected BasePage()
|
||||
{
|
||||
|
||||
@@ -21,22 +21,40 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Mail Operation -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,20">
|
||||
<TextBlock
|
||||
Margin="0,0,0,8"
|
||||
Style="{ThemeResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.KeyboardShortcuts_Mode}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<RadioButton
|
||||
Content="{x:Bind domain:Translator.KeyboardShortcuts_ModeMail}"
|
||||
GroupName="ShortcutMode"
|
||||
IsChecked="{x:Bind IsMailModeSelected, Mode=TwoWay}" />
|
||||
<RadioButton
|
||||
Content="{x:Bind domain:Translator.KeyboardShortcuts_ModeCalendar}"
|
||||
GroupName="ShortcutMode"
|
||||
IsChecked="{x:Bind IsCalendarModeSelected, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Action -->
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,20">
|
||||
<TextBlock
|
||||
Margin="0,0,0,4"
|
||||
Style="{ThemeResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.KeyboardShortcuts_MailoperationAction}" />
|
||||
Text="{x:Bind domain:Translator.KeyboardShortcuts_Action}" />
|
||||
<ComboBox
|
||||
x:Name="MailOperationComboBox"
|
||||
x:Name="ActionComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{x:Bind AvailableMailOperations}"
|
||||
SelectedItem="{x:Bind SelectedMailOperation, Mode=TwoWay}">
|
||||
ItemsSource="{x:Bind AvailableActions}"
|
||||
SelectedItem="{x:Bind SelectedAction, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
|
||||
<DataTemplate x:DataType="data:MailOperationViewModel">
|
||||
<DataTemplate x:DataType="data:KeyboardShortcutActionViewModel">
|
||||
<TextBlock Text="{x:Bind DisplayName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
@@ -44,7 +62,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<!-- Key Input -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,20">
|
||||
<StackPanel Grid.Row="2" Margin="0,0,0,20">
|
||||
<TextBlock
|
||||
Margin="0,0,0,4"
|
||||
Style="{ThemeResource BodyStrongTextBlockStyle}"
|
||||
@@ -61,31 +79,10 @@
|
||||
Text="{x:Bind domain:Translator.KeyboardShortcuts_FocusArea}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Modifiers -->
|
||||
<StackPanel Grid.Row="2" Margin="0,0,0,20">
|
||||
<TextBlock
|
||||
Margin="0,0,0,8"
|
||||
Style="{ThemeResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind domain:Translator.KeyboardShortcuts_Modifiers}" />
|
||||
<Border
|
||||
Padding="16,12"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<CheckBox Content="Ctrl" IsChecked="{x:Bind IsControlPressed, Mode=TwoWay}" />
|
||||
<CheckBox Content="Alt" IsChecked="{x:Bind IsAltPressed, Mode=TwoWay}" />
|
||||
<CheckBox Content="Shift" IsChecked="{x:Bind IsShiftPressed, Mode=TwoWay}" />
|
||||
<CheckBox Content="Win" IsChecked="{x:Bind IsWindowsPressed, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error Message -->
|
||||
<Border
|
||||
x:Name="ErrorBorder"
|
||||
Grid.Row="3"
|
||||
Grid.Row="4"
|
||||
Padding="12,8"
|
||||
Background="{ThemeResource SystemFillColorCriticalBackgroundBrush}"
|
||||
CornerRadius="4"
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.ViewModels.Data;
|
||||
|
||||
@@ -13,35 +14,51 @@ public sealed partial class KeyboardShortcutDialog : ContentDialog
|
||||
{
|
||||
public KeyboardShortcutDialogResult Result { get; private set; } = KeyboardShortcutDialogResult.Canceled();
|
||||
|
||||
public List<MailOperationViewModel> AvailableMailOperations { get; }
|
||||
public List<KeyboardShortcutActionViewModel> AvailableActions { get; private set; } = [];
|
||||
|
||||
public MailOperationViewModel SelectedMailOperation { get; set; }
|
||||
public bool IsControlPressed { get; set; }
|
||||
public bool IsAltPressed { get; set; }
|
||||
public bool IsShiftPressed { get; set; }
|
||||
public bool IsWindowsPressed { get; set; }
|
||||
public KeyboardShortcutActionViewModel SelectedAction { get; set; } = null!;
|
||||
public WinoApplicationMode SelectedMode { get; set; } = WinoApplicationMode.Mail;
|
||||
public bool IsMailModeSelected
|
||||
{
|
||||
get => SelectedMode == WinoApplicationMode.Mail;
|
||||
set
|
||||
{
|
||||
if (!value || SelectedMode == WinoApplicationMode.Mail) return;
|
||||
SelectedMode = WinoApplicationMode.Mail;
|
||||
RefreshAvailableActions();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCalendarModeSelected
|
||||
{
|
||||
get => SelectedMode == WinoApplicationMode.Calendar;
|
||||
set
|
||||
{
|
||||
if (!value || SelectedMode == WinoApplicationMode.Calendar) return;
|
||||
SelectedMode = WinoApplicationMode.Calendar;
|
||||
RefreshAvailableActions();
|
||||
}
|
||||
}
|
||||
|
||||
private ModifierKeys _modifierKeys;
|
||||
private string _key = string.Empty;
|
||||
|
||||
public KeyboardShortcutDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
AvailableMailOperations = GetAvailableMailOperations();
|
||||
SelectedMailOperation = AvailableMailOperations.FirstOrDefault()!;
|
||||
RefreshAvailableActions();
|
||||
}
|
||||
|
||||
public KeyboardShortcutDialog(KeyboardShortcut existingShortcut) : this()
|
||||
{
|
||||
if (existingShortcut != null)
|
||||
{
|
||||
KeyInputTextBox.Text = existingShortcut.Key;
|
||||
SelectedMailOperation = AvailableMailOperations.FirstOrDefault(x => x.Operation == existingShortcut.MailOperation)!;
|
||||
|
||||
var modifiers = existingShortcut.ModifierKeys;
|
||||
IsControlPressed = modifiers.HasFlag(ModifierKeys.Control);
|
||||
IsAltPressed = modifiers.HasFlag(ModifierKeys.Alt);
|
||||
IsShiftPressed = modifiers.HasFlag(ModifierKeys.Shift);
|
||||
IsWindowsPressed = modifiers.HasFlag(ModifierKeys.Windows);
|
||||
|
||||
Title = "Edit Keyboard Shortcut";
|
||||
SelectedMode = existingShortcut.Mode;
|
||||
_modifierKeys = existingShortcut.ModifierKeys;
|
||||
_key = existingShortcut.Key;
|
||||
RefreshAvailableActions(existingShortcut.Action);
|
||||
KeyInputTextBox.Text = BuildDisplayString(_key, _modifierKeys);
|
||||
Title = Translator.KeyboardShortcuts_EditTitle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,61 +68,47 @@ public sealed partial class KeyboardShortcutDialog : ContentDialog
|
||||
ErrorBorder.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
|
||||
|
||||
// Validate input
|
||||
if (string.IsNullOrWhiteSpace(KeyInputTextBox.Text))
|
||||
if (string.IsNullOrWhiteSpace(_key))
|
||||
{
|
||||
ShowError("Please enter a key for the shortcut.");
|
||||
ShowError(Translator.KeyboardShortcuts_EnterKey);
|
||||
args.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedMailOperation == null || SelectedMailOperation.Operation == MailOperation.None)
|
||||
if (SelectedAction == null || SelectedAction.Action == KeyboardShortcutAction.None)
|
||||
{
|
||||
ShowError("Please select a mail operation for the shortcut.");
|
||||
ShowError(Translator.KeyboardShortcuts_SelectOperation);
|
||||
args.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get modifier keys
|
||||
var modifierKeys = GetSelectedModifierKeys();
|
||||
|
||||
// Create successful result
|
||||
Result = KeyboardShortcutDialogResult.Success(KeyInputTextBox.Text, modifierKeys, SelectedMailOperation.Operation);
|
||||
Result = KeyboardShortcutDialogResult.Success(SelectedMode, _key, _modifierKeys, SelectedAction.Action);
|
||||
}
|
||||
|
||||
private void KeyInputTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
// Clear error when user starts typing
|
||||
ErrorBorder.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
|
||||
|
||||
var key = e.Key.ToString();
|
||||
_modifierKeys = GetCurrentModifierKeys();
|
||||
var key = NormalizeKey(e.Key);
|
||||
|
||||
// Update modifier states based on current key press
|
||||
IsControlPressed = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
|
||||
IsAltPressed = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Menu).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
|
||||
IsShiftPressed = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
|
||||
IsWindowsPressed = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.LeftWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down) ||
|
||||
Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.RightWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down);
|
||||
|
||||
// Set the key (ignore modifier keys themselves)
|
||||
if (key != "Control" && key != "Menu" && key != "Shift" && key != "LeftWindows" && key != "RightWindows")
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
KeyInputTextBox.Text = key;
|
||||
_key = key;
|
||||
}
|
||||
|
||||
// Prevent the key from being processed further
|
||||
// e.Handled = true;
|
||||
KeyInputTextBox.Text = string.IsNullOrEmpty(_key)
|
||||
? BuildDisplayString(string.Empty, _modifierKeys)
|
||||
: BuildDisplayString(_key, _modifierKeys);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private ModifierKeys GetSelectedModifierKeys()
|
||||
private void RefreshAvailableActions(KeyboardShortcutAction selectedAction = KeyboardShortcutAction.None)
|
||||
{
|
||||
var modifiers = ModifierKeys.None;
|
||||
|
||||
if (IsControlPressed) modifiers |= ModifierKeys.Control;
|
||||
if (IsAltPressed) modifiers |= ModifierKeys.Alt;
|
||||
if (IsShiftPressed) modifiers |= ModifierKeys.Shift;
|
||||
if (IsWindowsPressed) modifiers |= ModifierKeys.Windows;
|
||||
|
||||
return modifiers;
|
||||
AvailableActions = GetAvailableActions(SelectedMode);
|
||||
SelectedAction = AvailableActions.FirstOrDefault(x => x.Action == selectedAction) ?? AvailableActions.FirstOrDefault()!;
|
||||
Bindings.Update();
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
@@ -114,32 +117,91 @@ public sealed partial class KeyboardShortcutDialog : ContentDialog
|
||||
ErrorBorder.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
|
||||
}
|
||||
|
||||
private static List<MailOperationViewModel> GetAvailableMailOperations()
|
||||
private static List<KeyboardShortcutActionViewModel> GetAvailableActions(WinoApplicationMode mode)
|
||||
{
|
||||
var operations = new List<MailOperationViewModel>();
|
||||
|
||||
// Add commonly used mail operations that make sense for keyboard shortcuts
|
||||
var validOperations = new[]
|
||||
KeyboardShortcutAction[] actions = mode switch
|
||||
{
|
||||
MailOperation.Archive,
|
||||
MailOperation.UnArchive,
|
||||
MailOperation.SoftDelete,
|
||||
MailOperation.Move,
|
||||
MailOperation.MoveToJunk,
|
||||
MailOperation.SetFlag,
|
||||
MailOperation.ClearFlag,
|
||||
MailOperation.MarkAsRead,
|
||||
MailOperation.MarkAsUnread,
|
||||
MailOperation.Reply,
|
||||
MailOperation.ReplyAll,
|
||||
MailOperation.Forward
|
||||
WinoApplicationMode.Mail =>
|
||||
[
|
||||
KeyboardShortcutAction.NewMail,
|
||||
KeyboardShortcutAction.ToggleReadUnread,
|
||||
KeyboardShortcutAction.ToggleFlag,
|
||||
KeyboardShortcutAction.ToggleArchive,
|
||||
KeyboardShortcutAction.Delete,
|
||||
KeyboardShortcutAction.Move,
|
||||
KeyboardShortcutAction.Reply,
|
||||
KeyboardShortcutAction.ReplyAll,
|
||||
KeyboardShortcutAction.Send
|
||||
],
|
||||
WinoApplicationMode.Calendar =>
|
||||
[
|
||||
KeyboardShortcutAction.NewEvent,
|
||||
KeyboardShortcutAction.Delete
|
||||
],
|
||||
_ => []
|
||||
};
|
||||
|
||||
foreach (var operation in validOperations)
|
||||
return actions
|
||||
.Select(action => new KeyboardShortcutActionViewModel(mode, action))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static ModifierKeys GetCurrentModifierKeys()
|
||||
{
|
||||
var modifiers = ModifierKeys.None;
|
||||
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Control;
|
||||
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Menu).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Alt;
|
||||
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Shift;
|
||||
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.LeftWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down) ||
|
||||
Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.RightWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
{
|
||||
operations.Add(new MailOperationViewModel(operation));
|
||||
modifiers |= ModifierKeys.Windows;
|
||||
}
|
||||
|
||||
return operations.OrderBy(x => x.DisplayName).ToList();
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private static string NormalizeKey(Windows.System.VirtualKey key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
Windows.System.VirtualKey.Control or
|
||||
Windows.System.VirtualKey.LeftControl or
|
||||
Windows.System.VirtualKey.RightControl or
|
||||
Windows.System.VirtualKey.Menu or
|
||||
Windows.System.VirtualKey.LeftMenu or
|
||||
Windows.System.VirtualKey.RightMenu or
|
||||
Windows.System.VirtualKey.Shift or
|
||||
Windows.System.VirtualKey.LeftShift or
|
||||
Windows.System.VirtualKey.RightShift or
|
||||
Windows.System.VirtualKey.LeftWindows or
|
||||
Windows.System.VirtualKey.RightWindows => string.Empty,
|
||||
_ => key.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildDisplayString(string key, ModifierKeys modifierKeys)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (modifierKeys.HasFlag(ModifierKeys.Control))
|
||||
parts.Add("Ctrl");
|
||||
if (modifierKeys.HasFlag(ModifierKeys.Alt))
|
||||
parts.Add("Alt");
|
||||
if (modifierKeys.HasFlag(ModifierKeys.Shift))
|
||||
parts.Add("Shift");
|
||||
if (modifierKeys.HasFlag(ModifierKeys.Windows))
|
||||
parts.Add("Win");
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
parts.Add(key);
|
||||
|
||||
return string.Join("+", parts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
|
||||
namespace Wino.Helpers;
|
||||
|
||||
public static class BreadcrumbNavigationHelper
|
||||
{
|
||||
public static bool Navigate(
|
||||
Frame frame,
|
||||
ObservableCollection<BreadcrumbNavigationItemViewModel> pageHistory,
|
||||
BreadcrumbNavigationRequested message,
|
||||
Func<WinoPage, Type> getPageType)
|
||||
{
|
||||
var pageType = getPageType(message.PageType);
|
||||
|
||||
if (pageType == null)
|
||||
return false;
|
||||
|
||||
frame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo
|
||||
{
|
||||
Effect = SlideNavigationTransitionEffect.FromRight
|
||||
});
|
||||
|
||||
SetActiveItem(pageHistory, null);
|
||||
pageHistory.Add(new BreadcrumbNavigationItemViewModel(
|
||||
message,
|
||||
isActive: true,
|
||||
stepNumber: pageHistory.Count + 1,
|
||||
backStackDepth: frame.BackStack.Count + 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool GoBack(
|
||||
Frame frame,
|
||||
ObservableCollection<BreadcrumbNavigationItemViewModel> pageHistory,
|
||||
NavigationTransitionEffect slideEffect)
|
||||
{
|
||||
if (!frame.CanGoBack || pageHistory.Count == 0)
|
||||
return false;
|
||||
|
||||
pageHistory.RemoveAt(pageHistory.Count - 1);
|
||||
frame.GoBack(new SlideNavigationTransitionInfo
|
||||
{
|
||||
Effect = slideEffect == NavigationTransitionEffect.FromLeft
|
||||
? SlideNavigationTransitionEffect.FromLeft
|
||||
: SlideNavigationTransitionEffect.FromRight
|
||||
});
|
||||
|
||||
SetActiveItem(pageHistory, pageHistory.Count > 0 ? pageHistory[^1] : null);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool NavigateTo(
|
||||
Frame frame,
|
||||
ObservableCollection<BreadcrumbNavigationItemViewModel> pageHistory,
|
||||
int targetIndex)
|
||||
{
|
||||
if (targetIndex < 0 || targetIndex >= pageHistory.Count)
|
||||
return false;
|
||||
|
||||
var activeIndex = GetActiveIndex(pageHistory);
|
||||
if (activeIndex <= 0 || targetIndex >= activeIndex)
|
||||
return false;
|
||||
|
||||
var targetItem = pageHistory[targetIndex];
|
||||
|
||||
while (frame.BackStack.Count > targetItem.BackStackDepth)
|
||||
{
|
||||
frame.BackStack.RemoveAt(frame.BackStack.Count - 1);
|
||||
}
|
||||
|
||||
if (!frame.CanGoBack)
|
||||
return false;
|
||||
|
||||
frame.GoBack(new SlideNavigationTransitionInfo
|
||||
{
|
||||
Effect = SlideNavigationTransitionEffect.FromLeft
|
||||
});
|
||||
|
||||
while (pageHistory.Count > targetIndex + 1)
|
||||
{
|
||||
pageHistory.RemoveAt(pageHistory.Count - 1);
|
||||
}
|
||||
|
||||
SetActiveItem(pageHistory, targetItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int GetActiveIndex(ObservableCollection<BreadcrumbNavigationItemViewModel> pageHistory)
|
||||
{
|
||||
for (var i = 0; i < pageHistory.Count; i++)
|
||||
{
|
||||
if (pageHistory[i].IsActive)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void SetActiveItem(
|
||||
ObservableCollection<BreadcrumbNavigationItemViewModel> pageHistory,
|
||||
BreadcrumbNavigationItemViewModel? activeItem)
|
||||
{
|
||||
foreach (var item in pageHistory)
|
||||
{
|
||||
item.IsActive = ReferenceEquals(item, activeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
@@ -14,6 +15,7 @@ using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
@@ -43,6 +45,7 @@ public sealed partial class MailAppShell : MailAppShellAbstract,
|
||||
public MailAppShell() : base()
|
||||
{
|
||||
InitializeComponent();
|
||||
PreviewKeyDown += OnPreviewKeyDown;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
@@ -315,6 +318,100 @@ public sealed partial class MailAppShell : MailAppShellAbstract,
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.KeyStatus.RepeatCount > 1 || ShouldIgnoreShortcut())
|
||||
return;
|
||||
|
||||
var key = NormalizeKey(e.Key);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return;
|
||||
|
||||
var shortcutService = WinoApplication.Current.Services.GetRequiredService<IKeyboardShortcutService>();
|
||||
var shortcut = await shortcutService.GetShortcutForKeyAsync(WinoApplicationMode.Mail, key, GetCurrentModifierKeys());
|
||||
|
||||
if (shortcut == null)
|
||||
return;
|
||||
|
||||
var details = new KeyboardShortcutTriggerDetails
|
||||
{
|
||||
ShortcutId = shortcut.Id,
|
||||
Mode = shortcut.Mode,
|
||||
Action = shortcut.Action,
|
||||
Key = shortcut.Key,
|
||||
ModifierKeys = shortcut.ModifierKeys,
|
||||
Sender = sender,
|
||||
Origin = FocusManager.GetFocusedElement(XamlRoot)
|
||||
};
|
||||
|
||||
await ViewModel.KeyboardShortcutHook(details);
|
||||
|
||||
if (InnerShellFrame.Content is BasePage activePage && activePage.AssociatedViewModel != null)
|
||||
{
|
||||
await activePage.AssociatedViewModel.KeyboardShortcutHook(details);
|
||||
}
|
||||
|
||||
if (details.Handled)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldIgnoreShortcut()
|
||||
{
|
||||
var focusedElement = FocusManager.GetFocusedElement(XamlRoot);
|
||||
|
||||
if (focusedElement is TextBox or AutoSuggestBox or PasswordBox or RichEditBox or ComboBox)
|
||||
return true;
|
||||
|
||||
if (focusedElement is FrameworkElement frameworkElement)
|
||||
{
|
||||
var typeName = frameworkElement.GetType().Name;
|
||||
if (typeName.Contains("WebView", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ModifierKeys GetCurrentModifierKeys()
|
||||
{
|
||||
var modifiers = ModifierKeys.None;
|
||||
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Control;
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Menu).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Alt;
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Shift;
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.LeftWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down) ||
|
||||
Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.RightWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
{
|
||||
modifiers |= ModifierKeys.Windows;
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private static string NormalizeKey(Windows.System.VirtualKey key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
Windows.System.VirtualKey.Control or
|
||||
Windows.System.VirtualKey.LeftControl or
|
||||
Windows.System.VirtualKey.RightControl or
|
||||
Windows.System.VirtualKey.Menu or
|
||||
Windows.System.VirtualKey.LeftMenu or
|
||||
Windows.System.VirtualKey.RightMenu or
|
||||
Windows.System.VirtualKey.Shift or
|
||||
Windows.System.VirtualKey.LeftShift or
|
||||
Windows.System.VirtualKey.RightShift or
|
||||
Windows.System.VirtualKey.LeftWindows or
|
||||
Windows.System.VirtualKey.RightWindows => string.Empty,
|
||||
_ => key.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
protected override void RegisterRecipients()
|
||||
{
|
||||
base.RegisterRecipients();
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Mail.Views.Abstract;
|
||||
using Wino.Messaging.Client.Calendar;
|
||||
|
||||
@@ -17,14 +23,13 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
|
||||
public CalendarAppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
PreviewKeyDown += OnPreviewKeyDown;
|
||||
|
||||
// Window.Current.SetTitleBar(DragArea);
|
||||
ManageCalendarDisplayType(ViewModel.StatePersistenceService.CalendarDisplayType);
|
||||
}
|
||||
|
||||
private void ManageCalendarDisplayType(Core.Domain.Enums.CalendarDisplayType displayType)
|
||||
{
|
||||
// Go to different states based on the display type.
|
||||
if (displayType == Core.Domain.Enums.CalendarDisplayType.Month)
|
||||
{
|
||||
VisualStateManager.GoToState(this, STATE_VerticalCalendar, false);
|
||||
@@ -44,12 +49,6 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
|
||||
ManageCalendarDisplayType(message.NewDisplayType);
|
||||
}
|
||||
|
||||
//private void ShellFrameContentNavigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
|
||||
// => RealAppBar.ShellFrameContent = (e.Content as BasePage).ShellContent;
|
||||
|
||||
//private void AppBarBackButtonClicked(Core.UWP.Controls.WinoAppTitleBar sender, RoutedEventArgs args)
|
||||
// => ViewModel.NavigationService.GoBack();
|
||||
|
||||
protected override void RegisterRecipients()
|
||||
{
|
||||
base.RegisterRecipients();
|
||||
@@ -63,4 +62,98 @@ public sealed partial class CalendarAppShell : CalendarAppShellAbstract,
|
||||
|
||||
WeakReferenceMessenger.Default.Unregister<CalendarDisplayTypeChangedMessage>(this);
|
||||
}
|
||||
|
||||
private async void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.KeyStatus.RepeatCount > 1 || ShouldIgnoreShortcut())
|
||||
return;
|
||||
|
||||
var key = NormalizeKey(e.Key);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return;
|
||||
|
||||
var shortcutService = WinoApplication.Current.Services.GetRequiredService<IKeyboardShortcutService>();
|
||||
var shortcut = await shortcutService.GetShortcutForKeyAsync(WinoApplicationMode.Calendar, key, GetCurrentModifierKeys());
|
||||
|
||||
if (shortcut == null)
|
||||
return;
|
||||
|
||||
var details = new KeyboardShortcutTriggerDetails
|
||||
{
|
||||
ShortcutId = shortcut.Id,
|
||||
Mode = shortcut.Mode,
|
||||
Action = shortcut.Action,
|
||||
Key = shortcut.Key,
|
||||
ModifierKeys = shortcut.ModifierKeys,
|
||||
Sender = sender,
|
||||
Origin = FocusManager.GetFocusedElement(XamlRoot)
|
||||
};
|
||||
|
||||
await ViewModel.KeyboardShortcutHook(details);
|
||||
|
||||
if (InnerShellFrame.Content is BasePage activePage && activePage.AssociatedViewModel != null)
|
||||
{
|
||||
await activePage.AssociatedViewModel.KeyboardShortcutHook(details);
|
||||
}
|
||||
|
||||
if (details.Handled)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldIgnoreShortcut()
|
||||
{
|
||||
var focusedElement = FocusManager.GetFocusedElement(XamlRoot);
|
||||
|
||||
if (focusedElement is TextBox or AutoSuggestBox or PasswordBox or RichEditBox or ComboBox)
|
||||
return true;
|
||||
|
||||
if (focusedElement is FrameworkElement frameworkElement)
|
||||
{
|
||||
var typeName = frameworkElement.GetType().Name;
|
||||
if (typeName.Contains("WebView", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ModifierKeys GetCurrentModifierKeys()
|
||||
{
|
||||
var modifiers = ModifierKeys.None;
|
||||
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Control;
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Menu).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Alt;
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
modifiers |= ModifierKeys.Shift;
|
||||
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.LeftWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down) ||
|
||||
Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.RightWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
|
||||
{
|
||||
modifiers |= ModifierKeys.Windows;
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private static string NormalizeKey(Windows.System.VirtualKey key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
Windows.System.VirtualKey.Control or
|
||||
Windows.System.VirtualKey.LeftControl or
|
||||
Windows.System.VirtualKey.RightControl or
|
||||
Windows.System.VirtualKey.Menu or
|
||||
Windows.System.VirtualKey.LeftMenu or
|
||||
Windows.System.VirtualKey.RightMenu or
|
||||
Windows.System.VirtualKey.Shift or
|
||||
Windows.System.VirtualKey.LeftShift or
|
||||
Windows.System.VirtualKey.RightShift or
|
||||
Windows.System.VirtualKey.LeftWindows or
|
||||
Windows.System.VirtualKey.RightWindows => string.Empty,
|
||||
_ => key.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,8 +470,8 @@
|
||||
<Border
|
||||
x:Name="AttachmentsPane"
|
||||
Margin="0,8,0,0"
|
||||
AllowDrop="True"
|
||||
Padding="16"
|
||||
AllowDrop="True"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
DragLeave="AttachmentsPane_DragLeave"
|
||||
@@ -487,7 +487,10 @@
|
||||
Style="{StaticResource TransparentActionButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<coreControls:WinoFontIcon FontSize="14" Icon="AttachmentNew" />
|
||||
<TextBlock FontSize="18" FontWeight="SemiBold" Text="+" />
|
||||
<TextBlock
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="+" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
@@ -560,7 +563,20 @@
|
||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.CalendarEventCompose_Notes}" />
|
||||
</StackPanel>
|
||||
<!-- Notes Editor -->
|
||||
<mailControls:EditorTabbedCommandBarControl CommandTarget="{x:Bind NotesEditor}" />
|
||||
<mailControls:EditorTabbedCommandBarControl CommandTarget="{x:Bind NotesEditor}">
|
||||
<mailControls:EditorTabbedCommandBarControl.PaneCustomContent>
|
||||
<toolkit:TabbedCommandBarItem
|
||||
CommandAlignment="Right"
|
||||
IsDynamicOverflowEnabled="True"
|
||||
OverflowButtonAlignment="Left">
|
||||
<AppBarButton Click="ToggleNotesEditorThemeClicked" ToolTipService.ToolTip="{x:Bind GetEditorThemeToolTip(NotesEditor.IsEditorDarkMode), Mode=OneWay}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="{x:Bind GetEditorThemeIcon(NotesEditor.IsEditorDarkMode), Mode=OneWay}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
</toolkit:TabbedCommandBarItem>
|
||||
</mailControls:EditorTabbedCommandBarControl.PaneCustomContent>
|
||||
</mailControls:EditorTabbedCommandBarControl>
|
||||
<mailControls:WebViewEditorControl
|
||||
x:Name="NotesEditor"
|
||||
MinHeight="500"
|
||||
|
||||
@@ -15,6 +15,7 @@ using Windows.Storage;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
using Wino.Calendar.ViewModels.Data;
|
||||
using Wino.Mail.WinUI.Controls;
|
||||
using Wino.Mail.WinUI.Views.Abstract;
|
||||
|
||||
namespace Wino.Calendar.Views;
|
||||
@@ -29,6 +30,15 @@ public sealed partial class CalendarEventComposePage : CalendarEventComposePageA
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public WinoIconGlyph GetEditorThemeIcon(bool isDarkMode) => isDarkMode ? WinoIconGlyph.LightEditor : WinoIconGlyph.DarkEditor;
|
||||
|
||||
public string GetEditorThemeToolTip(bool isDarkMode) => isDarkMode ? Translator.Composer_LightTheme : Translator.Composer_DarkTheme;
|
||||
|
||||
private void ToggleNotesEditorThemeClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NotesEditor.ToggleEditorTheme();
|
||||
}
|
||||
|
||||
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
@@ -145,14 +145,13 @@
|
||||
Visibility="{x:Bind ViewModel.IsDraftBusy, Mode=OneWay}">
|
||||
<ProgressRing IsActive="True" />
|
||||
</AppBarButton>
|
||||
<AppBarToggleButton
|
||||
x:Name="EditorThemeToggleButton"
|
||||
IsChecked="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=TwoWay}"
|
||||
ToolTipService.ToolTip="Toggle editor dark mode">
|
||||
<AppBarToggleButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
||||
</AppBarToggleButton.Icon>
|
||||
</AppBarToggleButton>
|
||||
<AppBarButton
|
||||
Click="ToggleEditorThemeClicked"
|
||||
ToolTipService.ToolTip="{x:Bind GetEditorThemeToolTip(WebViewEditor.IsEditorDarkMode), Mode=OneWay}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="{x:Bind GetEditorThemeIcon(WebViewEditor.IsEditorDarkMode), Mode=OneWay}" />
|
||||
</AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
<AppBarButton Command="{x:Bind ViewModel.DiscardCommand}" Label="{x:Bind domain:Translator.Buttons_Discard}">
|
||||
<AppBarButton.Icon>
|
||||
<coreControls:WinoFontIcon Icon="Delete" />
|
||||
|
||||
@@ -22,6 +22,7 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Models.Reader;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.ViewModels.Messages;
|
||||
using Wino.Mail.WinUI.Controls;
|
||||
using Wino.Mail.WinUI.Extensions;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
@@ -43,6 +44,15 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public WinoIconGlyph GetEditorThemeIcon(bool isDarkMode) => isDarkMode ? WinoIconGlyph.LightEditor : WinoIconGlyph.DarkEditor;
|
||||
|
||||
public string GetEditorThemeToolTip(bool isDarkMode) => isDarkMode ? Translator.Composer_LightTheme : Translator.Composer_DarkTheme;
|
||||
|
||||
private void ToggleEditorThemeClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WebViewEditor.ToggleEditorTheme();
|
||||
}
|
||||
|
||||
private async void GlobalFocusManagerGotFocus(object? sender, FocusManagerGotFocusEventArgs e)
|
||||
{
|
||||
// In order to delegate cursor to the inner editor for WebView2.
|
||||
|
||||
@@ -52,7 +52,6 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
|
||||
private IStatePersistanceService StatePersistenceService { get; } = WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
|
||||
private IKeyPressService KeyPressService { get; } = WinoApplication.Current.Services.GetService<IKeyPressService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
|
||||
private IKeyboardShortcutService KeyboardShortcutService { get; } = WinoApplication.Current.Services.GetService<IKeyboardShortcutService>() ?? throw new Exception($"Can't resolve {nameof(IKeyboardShortcutService)}");
|
||||
public MailListPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -671,19 +670,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check keyboard shortcuts from service.
|
||||
ModifierKeys modifiers = args.Modifiers.ToDomainModifierKeys();
|
||||
|
||||
var operation = await KeyboardShortcutService.GetMailOperationForKeyAsync(args.Key.ToString(), modifiers);
|
||||
|
||||
if (operation != null)
|
||||
{
|
||||
ViewModel.ExecuteMailOperationCommand.Execute(operation);
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Handled = false;
|
||||
}
|
||||
args.Handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using MoreLinq;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Helpers;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.WinUI.Views.Abstract;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
@@ -42,7 +42,7 @@ public sealed partial class ManageAccountsPage : ManageAccountsPageAbstract,
|
||||
AccountPagesFrame.Navigated += AccountPagesFrameNavigated;
|
||||
|
||||
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuManageAccounts, WinoPage.AccountManagementPage);
|
||||
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true));
|
||||
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true, backStackDepth: AccountPagesFrame.BackStack.Count + 1));
|
||||
|
||||
var accountManagementPageType = ViewModel.NavigationService.GetPageType(WinoPage.AccountManagementPage);
|
||||
|
||||
@@ -69,15 +69,7 @@ public sealed partial class ManageAccountsPage : ManageAccountsPageAbstract,
|
||||
|
||||
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
|
||||
{
|
||||
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
|
||||
|
||||
if (pageType == null) return;
|
||||
|
||||
AccountPagesFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = Microsoft.UI.Xaml.Media.Animation.SlideNavigationTransitionEffect.FromRight });
|
||||
|
||||
PageHistory.ForEach(a => a.IsActive = false);
|
||||
|
||||
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, true));
|
||||
BreadcrumbNavigationHelper.Navigate(AccountPagesFrame, PageHistory, message, ViewModel.NavigationService.GetPageType);
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
@@ -89,40 +81,20 @@ public sealed partial class ManageAccountsPage : ManageAccountsPageAbstract,
|
||||
|
||||
private void GoBackFrame(Core.Domain.Enums.NavigationTransitionEffect slideEffect)
|
||||
{
|
||||
if (AccountPagesFrame.CanGoBack)
|
||||
{
|
||||
PageHistory.RemoveAt(PageHistory.Count - 1);
|
||||
if (!BreadcrumbNavigationHelper.GoBack(AccountPagesFrame, PageHistory, slideEffect))
|
||||
return;
|
||||
|
||||
var winuiEffect = slideEffect switch
|
||||
{
|
||||
Core.Domain.Enums.NavigationTransitionEffect.FromLeft => Microsoft.UI.Xaml.Media.Animation.SlideNavigationTransitionEffect.FromLeft,
|
||||
_ => Microsoft.UI.Xaml.Media.Animation.SlideNavigationTransitionEffect.FromRight,
|
||||
};
|
||||
|
||||
AccountPagesFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = winuiEffect });
|
||||
|
||||
// Set the new last item as active
|
||||
if (PageHistory.Count > 0)
|
||||
{
|
||||
PageHistory.ForEach(a => a.IsActive = false);
|
||||
PageHistory[PageHistory.Count - 1].IsActive = true;
|
||||
}
|
||||
|
||||
// Update back button visibility after navigation
|
||||
ViewModel.StatePersistenceService.IsManageAccountsNavigating = AccountPagesFrame.CanGoBack;
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
ViewModel.StatePersistenceService.IsManageAccountsNavigating = AccountPagesFrame.CanGoBack;
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
private void BreadItemClicked(Microsoft.UI.Xaml.Controls.BreadcrumbBar sender, Microsoft.UI.Xaml.Controls.BreadcrumbBarItemClickedEventArgs args)
|
||||
{
|
||||
var clickedPageHistory = PageHistory[args.Index];
|
||||
if (!BreadcrumbNavigationHelper.NavigateTo(AccountPagesFrame, PageHistory, args.Index))
|
||||
return;
|
||||
|
||||
// Trigger GoBack repeatedly until we reach the clicked breadcrumb item
|
||||
while (PageHistory.FirstOrDefault(a => a.IsActive) != clickedPageHistory)
|
||||
{
|
||||
ViewModel.NavigationService.GoBack();
|
||||
}
|
||||
ViewModel.StatePersistenceService.IsManageAccountsNavigating = AccountPagesFrame.CanGoBack;
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
public void Receive(BackBreadcrumNavigationRequested message)
|
||||
|
||||
@@ -221,6 +221,17 @@
|
||||
<FontIcon Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard
|
||||
Click="SettingOptionClicked"
|
||||
Description="{x:Bind domain:Translator.Settings_KeyboardShortcuts_Description}"
|
||||
Header="{x:Bind domain:Translator.Settings_KeyboardShortcuts_Title}"
|
||||
IsClickEnabled="True"
|
||||
Tag="{x:Bind enums:WinoPage.KeyboardShortcutsPage}">
|
||||
<controls:SettingsCard.HeaderIcon>
|
||||
<FontIcon Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</abstract:SettingOptionsPageAbstract>
|
||||
|
||||
@@ -52,19 +52,28 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock
|
||||
Margin="0,0,0,4"
|
||||
Style="{ThemeResource BodyStrongTextBlockStyle}"
|
||||
Text="{x:Bind MailOperationDisplayName}" />
|
||||
Text="{x:Bind ActionDisplayName}" />
|
||||
<TextBlock
|
||||
Opacity="0.8"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind DisplayName}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind ModeDisplayName}" />
|
||||
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Margin="4,0"
|
||||
@@ -73,7 +82,7 @@
|
||||
Content=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Style="{ThemeResource SubtleButtonStyle}"
|
||||
ToolTipService.ToolTip="Edit" />
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Buttons_Edit}" />
|
||||
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
@@ -83,7 +92,7 @@
|
||||
Content=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Style="{ThemeResource SubtleButtonStyle}"
|
||||
ToolTipService.ToolTip="Delete" />
|
||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Buttons_Delete}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using MoreLinq;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Helpers;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
using Wino.Views.Abstract;
|
||||
@@ -35,7 +35,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
|
||||
SettingsFrame.Navigate(typeof(SettingOptionsPage), null, new SuppressNavigationTransitionInfo());
|
||||
|
||||
var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuSettings, WinoPage.SettingOptionsPage);
|
||||
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true));
|
||||
PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true, backStackDepth: SettingsFrame.BackStack.Count + 1));
|
||||
|
||||
if (e.Parameter is WinoPage parameterPage)
|
||||
{
|
||||
@@ -99,15 +99,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
|
||||
|
||||
void IRecipient<BreadcrumbNavigationRequested>.Receive(BreadcrumbNavigationRequested message)
|
||||
{
|
||||
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
|
||||
|
||||
if (pageType == null) return;
|
||||
|
||||
SettingsFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
|
||||
|
||||
PageHistory.ForEach(a => a.IsActive = false);
|
||||
|
||||
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, true));
|
||||
BreadcrumbNavigationHelper.Navigate(SettingsFrame, PageHistory, message, ViewModel.NavigationService.GetPageType);
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
@@ -119,40 +111,20 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
|
||||
|
||||
private void GoBackFrame(Core.Domain.Enums.NavigationTransitionEffect slideEffect)
|
||||
{
|
||||
if (SettingsFrame.CanGoBack)
|
||||
{
|
||||
PageHistory.RemoveAt(PageHistory.Count - 1);
|
||||
if (!BreadcrumbNavigationHelper.GoBack(SettingsFrame, PageHistory, slideEffect))
|
||||
return;
|
||||
|
||||
var winuiEffect = slideEffect switch
|
||||
{
|
||||
Core.Domain.Enums.NavigationTransitionEffect.FromLeft => Microsoft.UI.Xaml.Media.Animation.SlideNavigationTransitionEffect.FromLeft,
|
||||
_ => Microsoft.UI.Xaml.Media.Animation.SlideNavigationTransitionEffect.FromRight,
|
||||
};
|
||||
|
||||
SettingsFrame.GoBack(new SlideNavigationTransitionInfo() { Effect = winuiEffect });
|
||||
|
||||
// Set the new last item as active
|
||||
if (PageHistory.Count > 0)
|
||||
{
|
||||
PageHistory.ForEach(a => a.IsActive = false);
|
||||
PageHistory[PageHistory.Count - 1].IsActive = true;
|
||||
}
|
||||
|
||||
// Update back button visibility after navigation
|
||||
ViewModel.StatePersistenceService.IsSettingsNavigating = SettingsFrame.CanGoBack;
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
ViewModel.StatePersistenceService.IsSettingsNavigating = SettingsFrame.CanGoBack;
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
private void BreadItemClicked(Microsoft.UI.Xaml.Controls.BreadcrumbBar sender, Microsoft.UI.Xaml.Controls.BreadcrumbBarItemClickedEventArgs args)
|
||||
{
|
||||
var clickedPageHistory = PageHistory[args.Index];
|
||||
if (!BreadcrumbNavigationHelper.NavigateTo(SettingsFrame, PageHistory, args.Index))
|
||||
return;
|
||||
|
||||
// Trigger GoBack repeatedly until we reach the clicked breadcrumb item
|
||||
while (PageHistory.FirstOrDefault(a => a.IsActive) != clickedPageHistory)
|
||||
{
|
||||
ViewModel.NavigationService.GoBack();
|
||||
}
|
||||
ViewModel.StatePersistenceService.IsSettingsNavigating = SettingsFrame.CanGoBack;
|
||||
UpdateWindowTitle();
|
||||
}
|
||||
|
||||
public void Receive(BackBreadcrumNavigationRequested message)
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using MoreLinq;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Helpers;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Mail.WinUI.Views.Abstract;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
@@ -45,16 +45,7 @@ public sealed partial class WelcomeHostPage : WelcomeHostPageAbstract,
|
||||
|
||||
public void Receive(BreadcrumbNavigationRequested message)
|
||||
{
|
||||
var pageType = ViewModel.NavigationService.GetPageType(message.PageType);
|
||||
if (pageType == null) return;
|
||||
|
||||
WizardFrame.Navigate(pageType, message.Parameter, new SlideNavigationTransitionInfo
|
||||
{
|
||||
Effect = SlideNavigationTransitionEffect.FromRight
|
||||
});
|
||||
|
||||
PageHistory.ForEach(a => a.IsActive = false);
|
||||
PageHistory.Add(new BreadcrumbNavigationItemViewModel(message, isActive: true, stepNumber: PageHistory.Count + 1));
|
||||
BreadcrumbNavigationHelper.Navigate(WizardFrame, PageHistory, message, ViewModel.NavigationService.GetPageType);
|
||||
}
|
||||
|
||||
public void Receive(BackBreadcrumNavigationRequested message)
|
||||
@@ -64,33 +55,11 @@ public sealed partial class WelcomeHostPage : WelcomeHostPageAbstract,
|
||||
|
||||
private void GoBackFrame()
|
||||
{
|
||||
if (!WizardFrame.CanGoBack) return;
|
||||
|
||||
PageHistory.RemoveAt(PageHistory.Count - 1);
|
||||
WizardFrame.GoBack(new SlideNavigationTransitionInfo
|
||||
{
|
||||
Effect = SlideNavigationTransitionEffect.FromLeft
|
||||
});
|
||||
|
||||
if (PageHistory.Count > 0)
|
||||
{
|
||||
PageHistory.ForEach(a => a.IsActive = false);
|
||||
PageHistory[PageHistory.Count - 1].IsActive = true;
|
||||
}
|
||||
BreadcrumbNavigationHelper.GoBack(WizardFrame, PageHistory, NavigationTransitionEffect.FromLeft);
|
||||
}
|
||||
|
||||
private void BreadItemClicked(Microsoft.UI.Xaml.Controls.BreadcrumbBar sender, Microsoft.UI.Xaml.Controls.BreadcrumbBarItemClickedEventArgs args)
|
||||
{
|
||||
var clickedItem = PageHistory[args.Index];
|
||||
var currentActive = PageHistory.FirstOrDefault(a => a.IsActive);
|
||||
|
||||
// Only allow navigating backwards (clicking items before current)
|
||||
if (currentActive == null || args.Index >= PageHistory.IndexOf(currentActive))
|
||||
return;
|
||||
|
||||
while (PageHistory.FirstOrDefault(a => a.IsActive) != clickedItem && WizardFrame.CanGoBack)
|
||||
{
|
||||
GoBackFrame();
|
||||
}
|
||||
BreadcrumbNavigationHelper.NavigateTo(WizardFrame, PageHistory, args.Index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using SQLite;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Services;
|
||||
@@ -74,6 +75,8 @@ public class DatabaseService : IDatabaseService
|
||||
|
||||
private async Task EnsureSchemaUpgradesAsync()
|
||||
{
|
||||
await EnsureKeyboardShortcutSchemaAsync().ConfigureAwait(false);
|
||||
|
||||
var folderColumns = await Connection.GetTableInfoAsync(nameof(MailItemFolder)).ConfigureAwait(false);
|
||||
|
||||
if (!folderColumns.Any(c => c.Name == nameof(MailItemFolder.HighestKnownUid)))
|
||||
@@ -139,6 +142,44 @@ public class DatabaseService : IDatabaseService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureKeyboardShortcutSchemaAsync()
|
||||
{
|
||||
var keyboardShortcutColumns = await Connection.GetTableInfoAsync(nameof(KeyboardShortcut)).ConfigureAwait(false);
|
||||
|
||||
if (!keyboardShortcutColumns.Any(c => c.Name == nameof(KeyboardShortcut.Mode)))
|
||||
{
|
||||
await Connection
|
||||
.ExecuteAsync($"ALTER TABLE {nameof(KeyboardShortcut)} ADD COLUMN {nameof(KeyboardShortcut.Mode)} INTEGER NOT NULL DEFAULT 0")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!keyboardShortcutColumns.Any(c => c.Name == nameof(KeyboardShortcut.Action)))
|
||||
{
|
||||
await Connection
|
||||
.ExecuteAsync($"ALTER TABLE {nameof(KeyboardShortcut)} ADD COLUMN {nameof(KeyboardShortcut.Action)} INTEGER NOT NULL DEFAULT 0")
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Connection.ExecuteAsync($@"
|
||||
UPDATE {nameof(KeyboardShortcut)}
|
||||
SET {nameof(KeyboardShortcut.Action)} =
|
||||
CASE
|
||||
WHEN MailOperation = {(int)MailOperation.Archive} THEN {(int)KeyboardShortcutAction.ToggleArchive}
|
||||
WHEN MailOperation = {(int)MailOperation.UnArchive} THEN {(int)KeyboardShortcutAction.ToggleArchive}
|
||||
WHEN MailOperation = {(int)MailOperation.SoftDelete} THEN {(int)KeyboardShortcutAction.Delete}
|
||||
WHEN MailOperation = {(int)MailOperation.HardDelete} THEN {(int)KeyboardShortcutAction.Delete}
|
||||
WHEN MailOperation = {(int)MailOperation.Move} THEN {(int)KeyboardShortcutAction.Move}
|
||||
WHEN MailOperation = {(int)MailOperation.SetFlag} THEN {(int)KeyboardShortcutAction.ToggleFlag}
|
||||
WHEN MailOperation = {(int)MailOperation.ClearFlag} THEN {(int)KeyboardShortcutAction.ToggleFlag}
|
||||
WHEN MailOperation = {(int)MailOperation.MarkAsRead} THEN {(int)KeyboardShortcutAction.ToggleReadUnread}
|
||||
WHEN MailOperation = {(int)MailOperation.MarkAsUnread} THEN {(int)KeyboardShortcutAction.ToggleReadUnread}
|
||||
WHEN MailOperation = {(int)MailOperation.Reply} THEN {(int)KeyboardShortcutAction.Reply}
|
||||
WHEN MailOperation = {(int)MailOperation.ReplyAll} THEN {(int)KeyboardShortcutAction.ReplyAll}
|
||||
WHEN MailOperation = {(int)MailOperation.Forward} THEN {(int)KeyboardShortcutAction.Reply}
|
||||
ELSE {(int)KeyboardShortcutAction.None}
|
||||
END").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureIndexesAsync()
|
||||
{
|
||||
// Mail indexes
|
||||
|
||||
@@ -10,7 +10,7 @@ using Wino.Services.Extensions;
|
||||
namespace Wino.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing keyboard shortcuts for mail operations.
|
||||
/// Service for managing keyboard shortcuts for mail and calendar actions.
|
||||
/// </summary>
|
||||
public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutService
|
||||
{
|
||||
@@ -24,7 +24,7 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
public async Task<IEnumerable<KeyboardShortcut>> GetKeyboardShortcutsAsync()
|
||||
{
|
||||
return await Connection.QueryAsync<KeyboardShortcut>(
|
||||
"SELECT * FROM KeyboardShortcut ORDER BY MailOperation");
|
||||
"SELECT * FROM KeyboardShortcut ORDER BY Mode, Action");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +33,7 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
public async Task<IEnumerable<KeyboardShortcut>> GetEnabledKeyboardShortcutsAsync()
|
||||
{
|
||||
return await Connection.QueryAsync<KeyboardShortcut>(
|
||||
"SELECT * FROM KeyboardShortcut WHERE IsEnabled = ? ORDER BY MailOperation",
|
||||
"SELECT * FROM KeyboardShortcut WHERE IsEnabled = ? ORDER BY Mode, Action",
|
||||
true);
|
||||
}
|
||||
|
||||
@@ -65,32 +65,31 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mail operation for the given key combination.
|
||||
/// Gets the shortcut for the given key combination.
|
||||
/// </summary>
|
||||
public async Task<MailOperation?> GetMailOperationForKeyAsync(string key, ModifierKeys modifierKeys)
|
||||
public async Task<KeyboardShortcut> GetShortcutForKeyAsync(WinoApplicationMode mode, string key, ModifierKeys modifierKeys)
|
||||
{
|
||||
const string query = "SELECT * FROM KeyboardShortcut WHERE Key = ? AND ModifierKeys = ? AND IsEnabled = ? LIMIT 1";
|
||||
var shortcut = await Connection.FindWithQueryAsync<KeyboardShortcut>(query, key, (int)modifierKeys, 1);
|
||||
return shortcut?.MailOperation;
|
||||
const string query = "SELECT * FROM KeyboardShortcut WHERE Mode = ? AND Key = ? AND ModifierKeys = ? AND IsEnabled = ? LIMIT 1";
|
||||
return await Connection.FindWithQueryAsync<KeyboardShortcut>(query, (int)mode, key, (int)modifierKeys, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a key combination is already assigned to another shortcut.
|
||||
/// </summary>
|
||||
public async Task<bool> IsKeyCombinationInUseAsync(string key, ModifierKeys modifierKeys, Guid? excludeShortcutId = null)
|
||||
public async Task<bool> IsKeyCombinationInUseAsync(WinoApplicationMode mode, string key, ModifierKeys modifierKeys, Guid? excludeShortcutId = null)
|
||||
{
|
||||
string query;
|
||||
KeyboardShortcut shortcut;
|
||||
|
||||
if (excludeShortcutId.HasValue)
|
||||
{
|
||||
query = "SELECT * FROM KeyboardShortcut WHERE Key = ? AND ModifierKeys = ? AND Id != ? LIMIT 1";
|
||||
shortcut = await Connection.FindWithQueryAsync<KeyboardShortcut>(query, key, (int)modifierKeys, excludeShortcutId.Value);
|
||||
query = "SELECT * FROM KeyboardShortcut WHERE Mode = ? AND Key = ? AND ModifierKeys = ? AND Id != ? LIMIT 1";
|
||||
shortcut = await Connection.FindWithQueryAsync<KeyboardShortcut>(query, (int)mode, key, (int)modifierKeys, excludeShortcutId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = "SELECT * FROM KeyboardShortcut WHERE Key = ? AND ModifierKeys = ? LIMIT 1";
|
||||
shortcut = await Connection.FindWithQueryAsync<KeyboardShortcut>(query, key, (int)modifierKeys);
|
||||
query = "SELECT * FROM KeyboardShortcut WHERE Mode = ? AND Key = ? AND ModifierKeys = ? LIMIT 1";
|
||||
shortcut = await Connection.FindWithQueryAsync<KeyboardShortcut>(query, (int)mode, key, (int)modifierKeys);
|
||||
}
|
||||
|
||||
return shortcut != null;
|
||||
@@ -106,7 +105,7 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
foreach (var shortcut in defaultShortcuts)
|
||||
{
|
||||
// Only create if it doesn't exist already
|
||||
var exists = await IsKeyCombinationInUseAsync(shortcut.Key, shortcut.ModifierKeys);
|
||||
var exists = await IsKeyCombinationInUseAsync(shortcut.Mode, shortcut.Key, shortcut.ModifierKeys);
|
||||
if (!exists)
|
||||
{
|
||||
await SaveKeyboardShortcutAsync(shortcut);
|
||||
@@ -138,15 +137,17 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "Delete",
|
||||
ModifierKeys = ModifierKeys.None,
|
||||
MailOperation = MailOperation.SoftDelete,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.Delete,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "Delete",
|
||||
ModifierKeys = ModifierKeys.Shift,
|
||||
MailOperation = MailOperation.HardDelete,
|
||||
Key = "N",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.NewMail,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
@@ -154,7 +155,8 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "A",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
MailOperation = MailOperation.Archive,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.ToggleArchive,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
@@ -162,15 +164,8 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "R",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
MailOperation = MailOperation.MarkAsRead,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "U",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
MailOperation = MailOperation.MarkAsUnread,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.ToggleReadUnread,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
@@ -178,23 +173,8 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "F",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
MailOperation = MailOperation.SetFlag,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "F",
|
||||
ModifierKeys = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
MailOperation = MailOperation.ClearFlag,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "J",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
MailOperation = MailOperation.MoveToJunk,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.ToggleFlag,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
@@ -202,7 +182,35 @@ public class KeyboardShortcutService : BaseDatabaseService, IKeyboardShortcutSer
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "M",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
MailOperation = MailOperation.Move,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.Move,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "R",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.Reply,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "R",
|
||||
ModifierKeys = ModifierKeys.Control | ModifierKeys.Shift,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.ReplyAll,
|
||||
IsEnabled = true
|
||||
},
|
||||
new KeyboardShortcut
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Key = "Enter",
|
||||
ModifierKeys = ModifierKeys.Control,
|
||||
Mode = WinoApplicationMode.Mail,
|
||||
Action = KeyboardShortcutAction.Send,
|
||||
IsEnabled = true
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user