using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using UtopiaCanteenSystem.ViewModels; namespace UtopiaCanteenSystem.Views; /// /// Minimal code-behind for focus management and Enter-key submission. /// public partial class ScannerDashboardView : UserControl { private bool _isUnloaded; private bool _suppressScannerFocus; private DispatcherTimer? _focusSuppressionTimer; public ScannerDashboardView() { InitializeComponent(); Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { _isUnloaded = false; FocusRfidInput(selectAll: true, DispatcherPriority.Input); } private void OnUnloaded(object sender, RoutedEventArgs e) { _isUnloaded = true; // Clean up timer _focusSuppressionTimer?.Stop(); _focusSuppressionTimer = null; } private void FocusRfidInput(bool selectAll, DispatcherPriority priority) { if (_isUnloaded) return; Dispatcher.BeginInvoke(() => { if (_isUnloaded || !IsVisible || !IsEnabled) return; // Don't auto-focus scanner input if focus suppression is active if (_suppressScannerFocus) return; // Don't auto-focus scanner input if the Order History modal is open if (DataContext is ScannerDashboardViewModel vm && vm.IsOrderHistoryModalOpen) return; RfidInputTextBox.Focus(); Keyboard.Focus(RfidInputTextBox); if (selectAll) RfidInputTextBox.SelectAll(); }, priority); } private void RfidInputTextBox_OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { // Don't auto-focus scanner input if focus suppression is active if (_suppressScannerFocus) return; // Don't auto-focus scanner input if the Order History modal is open if (DataContext is ScannerDashboardViewModel vm && vm.IsOrderHistoryModalOpen) return; // If focus moved into the menu panel (e.g. Site number field), don't steal it back. if (IsDescendantOf(MenuPanel, e.NewFocus as DependencyObject)) return; // SIMPLE FIX: Just add a small delay before refocusing // This gives buttons time to process their click events DispatcherTimer focusTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(150) // 150ms is enough for click to process }; focusTimer.Tick += (s, args) => { focusTimer.Stop(); // Check conditions again after delay if (_suppressScannerFocus) return; if (DataContext is ScannerDashboardViewModel viewModel && viewModel.IsOrderHistoryModalOpen) return; // Only refocus if we're not already focused somewhere else // If modal opened, it will have focus now if (Keyboard.FocusedElement != RfidInputTextBox) { FocusRfidInput(selectAll: false, DispatcherPriority.ApplicationIdle); } }; focusTimer.Start(); } private static bool IsDescendantOf(DependencyObject? ancestor, DependencyObject? element) { if (ancestor == null || element == null) return false; while (element != null) { if (element == ancestor) return true; element = VisualTreeHelper.GetParent(element); } return false; } private void RfidInputTextBox_OnKeyDown(object sender, KeyEventArgs e) { if (e.Key != Key.Enter) return; if (DataContext is ScannerDashboardViewModel vm && vm.ScanCommand.CanExecute(null)) { vm.ScanCommand.Execute(null); e.Handled = true; } } private void SiteNumberTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) { e.Handled = !Regex.IsMatch(e.Text, @"^\d+$"); } private void MenuToggleButton_Unchecked(object sender, RoutedEventArgs e) { // When user closes the menu, focus scanner input so they can scan without clicking it. FocusRfidInput(selectAll: false, DispatcherPriority.ApplicationIdle); } private void OrderHistoryModalOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // Only close if the user actually clicked the dark overlay background, // not when the event is bubbling from inside the dialog content. if (!ReferenceEquals(e.OriginalSource, sender)) return; CloseOrderHistoryModal(); e.Handled = true; } private void OrderHistoryModalContent_Loaded(object sender, RoutedEventArgs e) { // No special behavior required here for now; this hook exists to satisfy XAML and // is a good place to move focus inside the dialog if needed later. } private void OrderHistoryModalClose_Click(object sender, RoutedEventArgs e) { CloseOrderHistoryModal(); } private void CloseOrderHistoryModal() { if (DataContext is ScannerDashboardViewModel vm) { vm.IsOrderHistoryModalOpen = false; // Suppress scanner focus temporarily after closing modal SuppressScannerFocusTemporarily(); } } private void SuppressScannerFocusTemporarily() { _suppressScannerFocus = true; // Clear any existing timer _focusSuppressionTimer?.Stop(); // Create a timer to clear suppression after UI settles _focusSuppressionTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) // Short delay to let UI settle }; _focusSuppressionTimer.Tick += (s, e) => { _suppressScannerFocus = false; _focusSuppressionTimer.Stop(); _focusSuppressionTimer = null; // Now restore scanner focus after suppression period FocusRfidInput(selectAll: false, DispatcherPriority.ApplicationIdle); }; _focusSuppressionTimer.Start(); } }