Files
Wino-Mail/Wino.Mail.WinUI/ShellWindow.xaml.cs
T

347 lines
12 KiB
C#
Raw Normal View History

2025-09-29 19:09:48 +02:00
using System;
2026-02-07 14:03:41 +01:00
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
2025-10-21 15:40:19 +02:00
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
2025-09-29 19:09:48 +02:00
using CommunityToolkit.Mvvm.Messaging;
2025-12-26 20:46:48 +01:00
using CommunityToolkit.WinUI.Controls;
2025-09-29 19:09:48 +02:00
using Microsoft.Extensions.DependencyInjection;
2025-09-29 11:16:14 +02:00
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
2025-10-04 13:40:35 +02:00
using Windows.UI;
2026-02-07 14:03:41 +01:00
using Wino.Core.Domain;
2026-02-22 15:13:39 +01:00
using Wino.Core.Domain.Enums;
2025-09-29 19:09:48 +02:00
using Wino.Core.Domain.Interfaces;
2026-02-07 14:03:41 +01:00
using Wino.Core.Domain.Models.Synchronization;
2026-02-22 15:13:39 +01:00
using Wino.Mail.WinUI.Activation;
2025-11-15 14:52:01 +01:00
using Wino.Mail.WinUI.Interfaces;
2025-10-04 13:40:35 +02:00
using Wino.Messaging.Client.Shell;
2025-10-03 22:12:27 +02:00
using Wino.Messaging.UI;
2025-09-29 11:16:14 +02:00
using Wino.Views;
using WinUIEx;
namespace Wino.Mail.WinUI;
2026-02-07 14:03:41 +01:00
public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
IRecipient<ApplicationThemeChanged>,
IRecipient<TitleBarShellContentUpdated>,
IRecipient<SynchronizationActionsAdded>,
IRecipient<SynchronizationActionsCompleted>
2025-09-29 11:16:14 +02:00
{
2025-09-29 19:09:48 +02:00
public IStatePersistanceService StatePersistanceService { get; } = WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception("StatePersistanceService not registered in DI container.");
public IPreferencesService PreferencesService { get; } = WinoApplication.Current.Services.GetService<IPreferencesService>() ?? throw new Exception("PreferencesService not registered in DI container.");
2025-12-26 20:46:48 +01:00
public INavigationService NavigationService { get; } = WinoApplication.Current.Services.GetService<INavigationService>() ?? throw new Exception("NavigationService not registered in DI container.");
2025-10-21 15:40:19 +02:00
public ICommand ShowWinoCommand { get; set; }
2026-03-06 12:31:37 +01:00
public ICommand ShowWinoCalendarCommand { get; set; }
2025-10-21 15:40:19 +02:00
public ICommand ExitWinoCommand { get; set; }
2025-09-29 19:09:48 +02:00
2026-02-07 14:03:41 +01:00
public ObservableCollection<SynchronizationActionItem> SyncActionItems { get; } = new();
private bool _calendarReminderServerStartAttempted;
2026-02-22 15:13:39 +01:00
private bool _isApplyingActivationMode;
private WinoApplicationMode _currentMode = WinoApplicationMode.Mail;
2026-02-07 14:03:41 +01:00
2025-09-29 11:16:14 +02:00
public ShellWindow()
{
RegisterRecipients();
2025-10-03 22:12:27 +02:00
2025-09-29 11:16:14 +02:00
InitializeComponent();
2025-09-29 19:09:48 +02:00
MinWidth = 420;
MinHeight = 420;
2025-09-29 11:16:14 +02:00
ConfigureTitleBar();
2025-10-04 15:46:05 +02:00
// Handle window closing event to minimize to tray instead of closing
Closed += OnWindowClosed;
// Use the AppWindow.Closing event to handle the close request
AppWindow.Closing += OnAppWindowClosing;
2025-10-21 15:40:19 +02:00
2026-01-06 17:34:06 +01:00
// Register global mouse button listener for back button
RegisterMouseBackButtonListener();
2026-03-06 12:31:37 +01:00
ShowWinoCommand = new RelayCommand(() => RestoreAndSwitchMode(WinoApplicationMode.Mail));
ShowWinoCalendarCommand = new RelayCommand(() => RestoreAndSwitchMode(WinoApplicationMode.Calendar));
2025-10-21 15:40:19 +02:00
ExitWinoCommand = new RelayCommand(ForceClose);
2025-10-31 19:53:43 +01:00
this.SetIcon("Assets/Wino_Icon.ico");
Title = "Wino Mail";
2025-10-21 15:40:19 +02:00
SystemTrayIcon.ForceCreate();
2025-09-29 11:16:14 +02:00
}
private void ConfigureTitleBar()
{
AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;
2025-10-21 15:40:19 +02:00
2025-10-04 13:40:35 +02:00
// Apply initial theme colors
var themeService = WinoApplication.Current.Services.GetService<INewThemeService>();
if (themeService != null)
{
var underlyingThemeService = WinoApplication.Current.Services.GetService<IUnderlyingThemeService>();
if (underlyingThemeService != null)
{
UpdateTitleBarColors(underlyingThemeService.IsUnderlyingThemeDark());
}
}
2025-09-29 11:16:14 +02:00
}
2026-01-06 17:34:06 +01:00
private void RegisterMouseBackButtonListener()
{
// Subscribe to pointer pressed events on the root content
if (Content is UIElement rootElement)
{
rootElement.AddHandler(UIElement.PointerPressedEvent, new Microsoft.UI.Xaml.Input.PointerEventHandler(OnPointerPressed), true);
}
}
private void OnPointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
// Check if it's the back button (XButton1)
var pointerPoint = e.GetCurrentPoint(null);
var properties = pointerPoint.Properties;
// XButton1 is the back button on most mice
if (properties.IsXButton1Pressed)
{
// Call GoBack on NavigationService
NavigationService.GoBack();
e.Handled = true;
}
}
2026-02-22 15:13:39 +01:00
public void HandleAppActivation(string? launchArguments, string? tileId = null, string? appId = null)
2025-09-29 11:16:14 +02:00
{
var targetMode = AppModeActivationResolver.Resolve(launchArguments, tileId, appId, PreferencesService.DefaultApplicationMode);
2026-02-22 15:13:39 +01:00
_currentMode = targetMode;
2025-09-29 11:16:14 +02:00
2026-02-22 15:13:39 +01:00
_isApplyingActivationMode = true;
AppModeSegmentedControl.SelectedIndex = targetMode == WinoApplicationMode.Mail ? 0 : 1;
_isApplyingActivationMode = false;
2025-12-27 19:16:24 +01:00
2026-02-22 15:13:39 +01:00
NavigationService.ChangeApplicationMode(targetMode);
2025-09-29 11:16:14 +02:00
}
public Microsoft.UI.Xaml.Controls.TitleBar GetTitleBar() => ShellTitleBar;
public Frame GetMainFrame() => MainShellFrame;
2025-09-29 19:09:48 +02:00
public FrameworkElement GetRootContent() => Content as Grid ?? throw new Exception("RootContent is not a Grid or empty.");
private void BackButtonClicked(Microsoft.UI.Xaml.Controls.TitleBar sender, object args)
{
NavigationService.GoBack();
2025-09-29 19:09:48 +02:00
}
2025-11-14 18:51:48 +01:00
private void MainFrameNavigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{
if (!_calendarReminderServerStartAttempted)
{
_calendarReminderServerStartAttempted = true;
_ = StartCalendarReminderServerAsync();
}
2025-12-27 19:16:24 +01:00
// Mail shell has shell content only for mail list page
// Thus, we check if the current content is MailAppShell
if (sender is Frame mainFrame && mainFrame.Content is MailAppShell mailAppShellPage)
ShellTitleBar.Content = mailAppShellPage.TopShellContent;
else if (e.Content is BasePage basePage)
2025-11-14 18:51:48 +01:00
ShellTitleBar.Content = basePage.ShellContent;
}
2025-09-29 19:09:48 +02:00
private async Task StartCalendarReminderServerAsync()
{
try
{
var reminderServer = WinoApplication.Current.Services.GetService<ICalendarReminderServer>();
if (reminderServer != null)
{
await reminderServer.StartAsync();
}
}
catch (Exception ex)
{
_calendarReminderServerStartAttempted = false;
Serilog.Log.Error(ex, "Failed to start calendar reminder server.");
}
}
2025-09-29 19:09:48 +02:00
private void PaneButtonClicked(Microsoft.UI.Xaml.Controls.TitleBar sender, object args)
{
PreferencesService.IsNavigationPaneOpened = !PreferencesService.IsNavigationPaneOpened;
}
2025-10-03 22:12:27 +02:00
public void Receive(TitleBarShellContentUpdated message)
{
2025-12-26 20:46:48 +01:00
if (MainShellFrame.Content is MailAppShell shellPage)
2025-10-03 22:12:27 +02:00
{
ShellTitleBar.Content = shellPage.TopShellContent;
}
}
2025-10-04 13:40:35 +02:00
public void Receive(ApplicationThemeChanged message)
{
UpdateTitleBarColors(message.IsUnderlyingThemeDark);
}
2026-02-07 14:03:41 +01:00
public void Receive(SynchronizationActionsAdded message)
{
DispatcherQueue.TryEnqueue(() =>
{
foreach (var action in message.Actions)
SyncActionItems.Add(action);
UpdateSyncStatusVisibility();
});
}
public void Receive(SynchronizationActionsCompleted message)
{
DispatcherQueue.TryEnqueue(() =>
{
var toRemove = SyncActionItems.Where(a => a.AccountId == message.AccountId).ToList();
foreach (var item in toRemove)
SyncActionItems.Remove(item);
UpdateSyncStatusVisibility();
});
}
private void UpdateSyncStatusVisibility()
{
SyncStatusButton.Visibility = SyncActionItems.Any()
? Visibility.Visible
: Visibility.Collapsed;
var distinctAccounts = SyncActionItems.Select(a => a.AccountId).Distinct().Count();
SyncStatusText.Text = distinctAccounts switch
{
0 => string.Empty,
1 => string.Format(Translator.SyncAction_SynchronizingAccount, SyncActionItems.First().AccountName),
_ => string.Format(Translator.SyncAction_SynchronizingAccounts, distinctAccounts)
};
}
2025-10-04 13:40:35 +02:00
private void UpdateTitleBarColors(bool isDarkTheme)
{
DispatcherQueue.TryEnqueue(() =>
{
var titleBar = AppWindow.TitleBar;
if (titleBar == null) return;
// Set button colors based on theme
// Background is always transparent for all buttons
titleBar.ButtonBackgroundColor = Color.FromArgb(0, 0, 0, 0); // Transparent
titleBar.ButtonInactiveBackgroundColor = Color.FromArgb(0, 0, 0, 0); // Transparent
titleBar.ButtonHoverBackgroundColor = Color.FromArgb(0, 0, 0, 0); // Transparent
titleBar.ButtonPressedBackgroundColor = Color.FromArgb(0, 0, 0, 0); // Transparent
if (isDarkTheme)
{
// Dark theme: use light text/icons for better contrast
titleBar.ButtonForegroundColor = Color.FromArgb(255, 255, 255, 255); // White
titleBar.ButtonInactiveForegroundColor = Color.FromArgb(128, 255, 255, 255); // Semi-transparent white
titleBar.ButtonHoverForegroundColor = Color.FromArgb(255, 255, 255, 255); // White
titleBar.ButtonPressedForegroundColor = Color.FromArgb(200, 255, 255, 255); // Slightly dimmed white
}
else
{
// Light theme: use dark text/icons for better contrast
titleBar.ButtonForegroundColor = Color.FromArgb(255, 0, 0, 0); // Black
titleBar.ButtonInactiveForegroundColor = Color.FromArgb(128, 0, 0, 0); // Semi-transparent black
titleBar.ButtonHoverForegroundColor = Color.FromArgb(255, 0, 0, 0); // Black
titleBar.ButtonPressedForegroundColor = Color.FromArgb(200, 0, 0, 0); // Slightly dimmed black
}
});
}
2025-10-04 15:46:05 +02:00
private void OnAppWindowClosing(object sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs e)
{
e.Cancel = true;
MinimizeToTray();
}
private void OnWindowClosed(object sender, WindowEventArgs e)
{
2025-10-21 15:40:19 +02:00
SystemTrayIcon?.Dispose();
2025-10-04 15:46:05 +02:00
}
private void MinimizeToTray()
{
this.Hide();
2025-10-21 15:40:19 +02:00
SystemTrayIcon.ForceCreate();
2025-10-04 15:46:05 +02:00
}
private void RestoreFromTray()
{
2025-12-26 20:46:48 +01:00
2025-10-21 15:40:19 +02:00
this.Show();
BringToFront();
2025-10-04 15:46:05 +02:00
}
2026-03-06 12:31:37 +01:00
private void RestoreAndSwitchMode(WinoApplicationMode mode)
{
_currentMode = mode;
_isApplyingActivationMode = true;
AppModeSegmentedControl.SelectedIndex = mode == WinoApplicationMode.Mail ? 0 : 1;
_isApplyingActivationMode = false;
NavigationService.ChangeApplicationMode(mode);
RestoreFromTray();
}
2025-10-04 15:46:05 +02:00
public void ForceClose()
{
// Unsubscribe from the closing event to avoid infinite loop
AppWindow.Closing -= OnAppWindowClosing;
2025-10-21 15:40:19 +02:00
2025-10-04 15:46:05 +02:00
// Clean up system tray
2025-10-21 15:40:19 +02:00
SystemTrayIcon?.Dispose();
UnregisterRecipients();
2025-10-21 15:40:19 +02:00
2026-03-05 10:12:03 +01:00
var windowManager = WinoApplication.Current.Services.GetService<IWinoWindowManager>();
windowManager?.CloseAllWindows();
2025-10-21 15:40:19 +02:00
2025-10-04 15:46:05 +02:00
// Exit the application
Application.Current.Exit();
}
private void RegisterRecipients()
{
WeakReferenceMessenger.Default.Register<TitleBarShellContentUpdated>(this);
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
2026-02-07 14:03:41 +01:00
WeakReferenceMessenger.Default.Register<SynchronizationActionsAdded>(this);
WeakReferenceMessenger.Default.Register<SynchronizationActionsCompleted>(this);
}
private void UnregisterRecipients()
{
WeakReferenceMessenger.Default.Unregister<TitleBarShellContentUpdated>(this);
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
2026-02-07 14:03:41 +01:00
WeakReferenceMessenger.Default.Unregister<SynchronizationActionsAdded>(this);
WeakReferenceMessenger.Default.Unregister<SynchronizationActionsCompleted>(this);
}
2025-12-26 20:46:48 +01:00
private void SegmentedChanged(object sender, SelectionChangedEventArgs e)
{
2026-02-22 15:13:39 +01:00
if (_isApplyingActivationMode || sender is not Segmented segmentedControl)
return;
var selectedMode = segmentedControl.SelectedIndex == 1
? WinoApplicationMode.Calendar
: WinoApplicationMode.Mail;
if (selectedMode == _currentMode)
return;
_currentMode = selectedMode;
NavigationService.ChangeApplicationMode(selectedMode);
2025-12-26 20:46:48 +01:00
}
2025-09-29 11:16:14 +02:00
}