using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Windows.UI; using Wino.Core.Domain; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Synchronization; using Wino.Extensions; using Wino.Mail.WinUI.Activation; using Wino.Mail.WinUI.Extensions; using Wino.Mail.WinUI.Interfaces; using Wino.Mail.WinUI.Models; using Wino.Mail.WinUI.Views; using Wino.Messaging.Client.Shell; using Wino.Messaging.UI; using WinUIEx; namespace Wino.Mail.WinUI; public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, IRecipient, IRecipient, IRecipient, IRecipient, IRecipient, IRecipient, IRecipient { private bool _allowClose; public IStatePersistanceService StatePersistanceService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("StatePersistanceService not registered in DI container."); public IPreferencesService PreferencesService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("PreferencesService not registered in DI container."); public INavigationService NavigationService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("NavigationService not registered in DI container."); private IMailDialogService MailDialogService { get; } = WinoApplication.Current.Services.GetRequiredService(); private IWinoAccountProfileService WinoAccountProfileService { get; } = WinoApplication.Current.Services.GetRequiredService(); public ObservableCollection SyncActionItems { get; } = new(); private bool _calendarReminderServerStartAttempted; private ITitleBarSearchHost? _activeTitleBarSearchHost; private bool _isBackButtonVisibilityReady; private bool _isSynchronizingTitleBarSearch; public ShellWindow() { RegisterRecipients(); InitializeComponent(); StatePersistanceService.StatePropertyChanged += StatePersistenceServiceChanged; MinWidth = 420; MinHeight = 420; ConfigureTitleBar(); ApplyTitleBarSearchHost(); // 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; // Register global mouse button listener for back button RegisterMouseBackButtonListener(); this.SetIcon("Assets/Wino_Icon.ico"); Title = StatePersistanceService.AppModeTitle; } private void ConfigureTitleBar() { AppWindow.TitleBar.ExtendsContentIntoTitleBar = true; // Apply initial theme colors var themeService = WinoApplication.Current.Services.GetService(); if (themeService != null) { var underlyingThemeService = WinoApplication.Current.Services.GetService(); if (underlyingThemeService != null) { UpdateTitleBarColors(underlyingThemeService.IsUnderlyingThemeDark()); } } } 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; } } public void HandleAppActivation(string? launchArguments, string? tileId = null, string? appId = null) { var targetMode = AppModeActivationResolver.Resolve(launchArguments, tileId, appId, PreferencesService.DefaultApplicationMode); NavigationService.ChangeApplicationMode(targetMode); } public Microsoft.UI.Xaml.Controls.TitleBar GetTitleBar() => ShellTitleBar; public Frame GetMainFrame() => MainShellFrame; 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(); } private void MainFrameNavigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e) { if (!_calendarReminderServerStartAttempted) { _calendarReminderServerStartAttempted = true; _ = StartCalendarReminderServerAsync(); } _isBackButtonVisibilityReady = true; ApplyTitleBarSearchHost(); RefreshBackButtonVisibility(); } private async Task StartCalendarReminderServerAsync() { try { var reminderServer = WinoApplication.Current.Services.GetService(); if (reminderServer != null) { await reminderServer.StartAsync(); } } catch (Exception ex) { _calendarReminderServerStartAttempted = false; Serilog.Log.Error(ex, "Failed to start calendar reminder server."); } } private void PaneButtonClicked(Microsoft.UI.Xaml.Controls.TitleBar sender, object args) { PreferencesService.IsNavigationPaneOpened = !PreferencesService.IsNavigationPaneOpened; } public void Receive(TitleBarShellContentUpdated message) { ApplyTitleBarSearchHost(); RefreshBackButtonVisibility(); } public void Receive(ApplicationThemeChanged message) { UpdateTitleBarColors(message.IsUnderlyingThemeDark); } public void Receive(InfoBarMessageRequested message) { ShowInfoBarMessage(message); } 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(); }); } public void Receive(WinoAccountProfileUpdatedMessage message) { DispatcherQueue.TryEnqueue(() => UpdateWinoAccountState(message.Account)); } public void Receive(WinoAccountProfileDeletedMessage message) { DispatcherQueue.TryEnqueue(() => UpdateWinoAccountState(null)); } 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) }; } 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 } }); } private void ApplyTitleBarSearchHost() { _activeTitleBarSearchHost = ResolveActiveTitleBarSearchHost(); SynchronizeTitleBarSearchBox(); } private void StatePersistenceServiceChanged(object? sender, string propertyName) { if (!DispatcherQueue.HasThreadAccess) { var enqueued = DispatcherQueue.TryEnqueue(() => StatePersistenceServiceChanged(sender, propertyName)); if (!enqueued) throw new InvalidOperationException("Could not marshal shell state changes onto the UI thread."); return; } if (propertyName == nameof(IStatePersistanceService.ApplicationMode) || propertyName == nameof(IStatePersistanceService.IsReadingMail) || propertyName == nameof(IStatePersistanceService.IsReaderNarrowed) || propertyName == nameof(IStatePersistanceService.IsEventDetailsVisible)) { RefreshBackButtonVisibility(); } } private void RefreshBackButtonVisibility() { if (!_isBackButtonVisibilityReady) { ShellTitleBar.IsBackButtonVisible = false; return; } ShellTitleBar.IsBackButtonVisible = NavigationService.CanGoBack(); } private ITitleBarSearchHost? ResolveActiveTitleBarSearchHost() { if (MainShellFrame.Content is WinoAppShell shellPage) { return shellPage.GetShellFrame().Content as ITitleBarSearchHost; } return MainShellFrame.Content as ITitleBarSearchHost; } private void SynchronizeTitleBarSearchBox() { _isSynchronizingTitleBarSearch = true; try { TitleBarSearchBox.IsEnabled = _activeTitleBarSearchHost != null; TitleBarSearchBox.PlaceholderText = _activeTitleBarSearchHost?.SearchPlaceholderText ?? Translator.SearchBarPlaceholder; TitleBarSearchBox.ItemsSource = _activeTitleBarSearchHost?.SearchSuggestions; TitleBarSearchBox.Text = _activeTitleBarSearchHost?.SearchText ?? string.Empty; } finally { _isSynchronizingTitleBarSearch = false; } } private async void TitleBarSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { if (_isSynchronizingTitleBarSearch || _activeTitleBarSearchHost == null) return; _activeTitleBarSearchHost.SearchText = sender.Text; await _activeTitleBarSearchHost.OnTitleBarSearchTextChangedAsync(); } private void TitleBarSearchSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { if (_activeTitleBarSearchHost == null || args.SelectedItem is not TitleBarSearchSuggestion suggestion) return; _activeTitleBarSearchHost.OnTitleBarSearchSuggestionChosen(suggestion); SynchronizeTitleBarSearchBox(); } private async void TitleBarSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { if (_activeTitleBarSearchHost == null) return; await _activeTitleBarSearchHost.OnTitleBarSearchSubmittedAsync(args.QueryText, args.ChosenSuggestion as TitleBarSearchSuggestion); SynchronizeTitleBarSearchBox(); } private void OnAppWindowClosing(object sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs e) { if (_allowClose || (Application.Current as App)?.IsExiting == true) return; e.Cancel = true; var windowManager = WinoApplication.Current.Services.GetService(); windowManager?.HideWindow(this); } public void PrepareForClose() { if (MainShellFrame.Content is WinoAppShell shellPage) { shellPage.PrepareForWindowClose(); } _allowClose = true; } private void OnWindowClosed(object sender, WindowEventArgs e) { Closed -= OnWindowClosed; AppWindow.Closing -= OnAppWindowClosing; StatePersistanceService.StatePropertyChanged -= StatePersistenceServiceChanged; if (MainShellFrame.Content is WinoAppShell shellPage) { shellPage.PrepareForWindowClose(); } UnregisterRecipients(); } private void RegisterRecipients() { WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); } private void UnregisterRecipients() { WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); } private void ShowInfoBarMessage(InfoBarMessageRequested message) { DispatcherQueue.TryEnqueue(() => { if (string.IsNullOrEmpty(message.ActionButtonTitle) || message.Action == null) { ShellInfoBar.ActionButton = null; } else { ShellInfoBar.ActionButton = new Button() { Content = message.ActionButtonTitle, Command = new RelayCommand(message.Action) }; } ShellInfoBar.Message = message.Message; ShellInfoBar.Title = message.Title; ShellInfoBar.Severity = message.Severity.AsMUXCInfoBarSeverity(); ShellInfoBar.IsOpen = true; }); } private void UpdateWinoAccountState(WinoAccount? account) { var isSignedIn = account != null; WinoAccountSignedOutView.Visibility = isSignedIn ? Visibility.Collapsed : Visibility.Visible; WinoAccountSignedInView.Visibility = isSignedIn ? Visibility.Visible : Visibility.Collapsed; WinoAccountButtonPicture.Visibility = isSignedIn ? Visibility.Visible : Visibility.Collapsed; WinoAccountSignedOutIcon.Visibility = isSignedIn ? Visibility.Collapsed : Visibility.Visible; var initials = GetInitials(account?.Email); WinoAccountButtonPicture.Initials = initials; WinoAccountFlyoutPicture.Initials = initials; WinoAccountButtonPicture.DisplayName = account?.Email ?? Translator.WinoAccount_Titlebar_SignedOutTitle; WinoAccountFlyoutPicture.DisplayName = account?.Email ?? Translator.WinoAccount_Titlebar_SignedOutTitle; WinoAccountFlyoutEmailText.Text = account?.Email ?? string.Empty; WinoAccountFlyoutStatusText.Text = account == null ? string.Empty : string.Format(Translator.WinoAccount_Titlebar_SignedInStatus, account.AccountStatus); } private static string GetInitials(string? email) { if (string.IsNullOrWhiteSpace(email)) { return "W"; } var localPart = email.Split('@')[0]; var segments = localPart .Split(['.', '_', '-', ' '], StringSplitOptions.RemoveEmptyEntries) .Where(segment => !string.IsNullOrWhiteSpace(segment)) .Take(2) .ToArray(); if (segments.Length == 0) { return email[..1].ToUpperInvariant(); } return string.Concat(segments.Select(segment => char.ToUpperInvariant(segment[0]))); } private async void RegisterWinoAccountClicked(object sender, RoutedEventArgs e) { WinoAccountFlyout.Hide(); var account = await MailDialogService.ShowWinoAccountRegistrationDialogAsync(); if (account != null) { ShowInfoBarMessage(new InfoBarMessageRequested( InfoBarMessageType.Success, Translator.GeneralTitle_Info, string.Format(Translator.WinoAccount_RegisterSuccessMessage, account.Email))); } } private async void LoginWinoAccountClicked(object sender, RoutedEventArgs e) { WinoAccountFlyout.Hide(); var account = await MailDialogService.ShowWinoAccountLoginDialogAsync(); if (account != null) { ShowInfoBarMessage(new InfoBarMessageRequested( InfoBarMessageType.Success, Translator.GeneralTitle_Info, string.Format(Translator.WinoAccount_LoginSuccessMessage, account.Email))); } } private async void SignOutWinoAccountClicked(object sender, RoutedEventArgs e) { var activeAccount = await WinoAccountProfileService.GetActiveAccountAsync(); if (activeAccount == null) { ShowInfoBarMessage(new InfoBarMessageRequested( InfoBarMessageType.Warning, Translator.GeneralTitle_Info, Translator.WinoAccount_SignOut_NoAccountMessage)); return; } await WinoAccountProfileService.SignOutAsync(); ShowInfoBarMessage(new InfoBarMessageRequested( InfoBarMessageType.Success, Translator.GeneralTitle_Info, string.Format(Translator.WinoAccount_SignOut_SuccessMessage, activeAccount.Email))); } }