Utopia-Canteen-System/Views/ScannerDashboardView.xaml.cs

202 lines
6.3 KiB
C#

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;
/// <summary>
/// Minimal code-behind for focus management and Enter-key submission.
/// </summary>
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();
}
}