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 ;
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 ;
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 ;
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 ; }
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 ( ) ;
2025-09-29 11:16:14 +02:00
public ShellWindow ( )
{
2025-10-21 01:27:29 +02:00
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 ( ) ;
2025-10-21 15:40:19 +02:00
ShowWinoCommand = new RelayCommand ( RestoreFromTray ) ;
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 ;
}
}
2025-09-29 11:16:14 +02:00
public void HandleAppActivation ( LaunchActivatedEventArgs args )
{
2025-12-27 19:16:24 +01:00
// Parse launch arguments to determine the application mode
var launchArguments = args ? . Arguments ? . ToLower ( ) ? ? string . Empty ;
2025-09-29 11:16:14 +02:00
2025-12-27 19:16:24 +01:00
Core . Domain . Enums . WinoApplicationMode targetMode ;
if ( launchArguments . Contains ( "wino-calendar" ) )
{
targetMode = Core . Domain . Enums . WinoApplicationMode . Calendar ;
}
else if ( launchArguments . Contains ( "wino-mail" ) )
{
targetMode = Core . Domain . Enums . WinoApplicationMode . Mail ;
}
else if ( ! string . IsNullOrEmpty ( launchArguments ) )
{
// TODO: Handle other protocol activations (e.g., .eml files)
// For now, default to Mail mode for unknown protocols
targetMode = Core . Domain . Enums . WinoApplicationMode . Mail ;
}
else
{
// Default to Mail mode when no arguments provided
targetMode = Core . Domain . Enums . WinoApplicationMode . Mail ;
}
// Use NavigationService to change application mode with proper navigation
if ( targetMode = = Core . Domain . Enums . WinoApplicationMode . Mail )
{
AppModeSegmentedControl . SelectedIndex = 0 ;
}
else
{
AppModeSegmentedControl . SelectedIndex = 1 ;
}
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 )
{
2025-12-29 14:10:09 +01:00
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 )
{
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 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
}
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 ( ) ;
2025-10-21 01:27:29 +02:00
UnregisterRecipients ( ) ;
2025-10-21 15:40:19 +02:00
2025-10-04 15:46:05 +02:00
// Close the window
2025-10-21 15:40:19 +02:00
Close ( ) ;
2025-10-04 15:46:05 +02:00
// Exit the application
Application . Current . Exit ( ) ;
}
2025-10-21 01:27:29 +02:00
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 ) ;
2025-10-21 01:27:29 +02:00
}
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-10-21 01:27:29 +02:00
}
2025-12-26 20:46:48 +01:00
private void SegmentedChanged ( object sender , SelectionChangedEventArgs e )
{
if ( sender is Segmented segmentedControl )
{
if ( segmentedControl . SelectedIndex = = 0 )
{
NavigationService . ChangeApplicationMode ( Core . Domain . Enums . WinoApplicationMode . Mail ) ;
}
2025-12-28 06:58:06 +01:00
else if ( segmentedControl . SelectedIndex = = 1 )
2025-12-26 20:46:48 +01:00
{
NavigationService . ChangeApplicationMode ( Core . Domain . Enums . WinoApplicationMode . Calendar ) ;
}
}
}
2025-09-29 11:16:14 +02:00
}