using System.ComponentModel; using System.Runtime.CompilerServices; using CommanderApp.Services; namespace CommanderApp; public partial class MainPage : ContentPage, INotifyPropertyChanged { private readonly IFileSystemService _fileService; private readonly IPanelManager _panelManager; private readonly IFileOperations _fileOperations; private readonly IKeyboardService _keyboardService; private readonly IPathHelper _pathHelper; // Для визуального выделения private readonly Color ActiveIndicatorColor = Color.FromArgb("#007ACC"); private readonly Color InactiveIndicatorColor = Colors.Transparent; private readonly Color FocusedButtonColor = Color.FromArgb("#E3F2FD"); private readonly Color NormalButtonColor = Colors.Transparent; // Для отслеживания двойного клика private DateTime _lastClickTime = DateTime.MinValue; private object _lastClickedButton = null; private bool _awaitingConfirmation = false; public string LeftPath { get => _panelManager.LeftPanelPath; set { if (_panelManager.LeftPanelPath != value) { _panelManager.UpdatePanelPaths(value, _panelManager.RightPanelPath); NotifyPropertyChanged(); } } } public string RightPath { get => _panelManager.RightPanelPath; set { if (_panelManager.RightPanelPath != value) { _panelManager.UpdatePanelPaths(_panelManager.LeftPanelPath, value); NotifyPropertyChanged(); } } } public MainPage(IFileSystemService fileService, IPanelManager panelManager, IFileOperations fileOperations, IKeyboardService keyboardService, IPathHelper pathHelper) { InitializeComponent(); _fileService = fileService; _panelManager = panelManager; _fileOperations = fileOperations; _keyboardService = keyboardService; _pathHelper = pathHelper; BindingContext = this; System.Diagnostics.Debug.WriteLine("=== MainPage constructor ==="); // Подписываемся на события _panelManager.StateChanged += OnPanelStateChanged; _keyboardService.KeyPressed += OnKeyPressed; // Загружаем начальные директории var root = _fileService.GetRootPath(); LoadDirectory(root, true); LoadDirectory(root, false); System.Diagnostics.Debug.WriteLine("=== MainPage constructor completed ==="); } protected override void OnAppearing() { base.OnAppearing(); System.Diagnostics.Debug.WriteLine("=== OnAppearing ==="); Dispatcher.Dispatch(async () => { await Task.Delay(300); SetInitialFocus(); }); } protected override void OnHandlerChanged() { base.OnHandlerChanged(); System.Diagnostics.Debug.WriteLine($"=== OnHandlerChanged ==="); _keyboardService.SetupKeyboardHandling(this); } private void OnKeyPressed(object sender, KeyPressedEventArgs e) { System.Diagnostics.Debug.WriteLine($"=== KEY PRESSED: {e.Key} on {e.Platform} ==="); MainThread.BeginInvokeOnMainThread(() => { // Сбрасываем флаг для всех клавиш навигации if (e.Key == "w" || e.Key == "s" || e.Key == "a" || e.Key == "d") { _awaitingConfirmation = true; } switch (e.Key) { case "w": // W - вверх _panelManager.MoveSelection(-1); break; case "s": // S - вниз _panelManager.MoveSelection(1); break; case "a": // A - левая панель _panelManager.SwitchToLeftPanel(); break; case "d": // D - правая панель _panelManager.SwitchToRightPanel(); break; case " ": // Space - вход/запуск case "enter": // Enter - вход/запуск OpenSelectedItem(); break; case "f5": // F5 - Копирование OnCopyClicked(this, EventArgs.Empty); break; case "f6": // F6 - Перемещение OnMoveClicked(this, EventArgs.Empty); break; case "f7": // F7 - Создать папку OnMkdirClicked(this, EventArgs.Empty); break; case "f8": // F8 - Удаление OnDeleteClicked(this, EventArgs.Empty); break; case "f10": // F10 - Выход OnExitClicked(this, EventArgs.Empty); break; case "h": // H - Home OnHomeClicked(this, EventArgs.Empty); break; } // Всегда скроллим после любого действия с клавиатуры ScrollToSelectedItem(); }); } private void OnPanelStateChanged(object sender, PanelStateChangedEventArgs e) { UpdateVisualState(); // Устанавливаем фокус на выбранный элемент if (e.IsLeftPanelActive && e.LeftSelectedIndex >= 0 && e.LeftSelectedIndex < LeftPanel.Children.Count) { var button = LeftPanel.Children[e.LeftSelectedIndex] as Button; button?.Focus(); } else if (!e.IsLeftPanelActive && e.RightSelectedIndex >= 0 && e.RightSelectedIndex < RightPanel.Children.Count) { var button = RightPanel.Children[e.RightSelectedIndex] as Button; button?.Focus(); } // Скроллим при изменении состояния ScrollToSelectedItem(); } private void SetInitialFocus() { try { System.Diagnostics.Debug.WriteLine("=== Setting initial focus ==="); if (LeftPanel.Children.Count > 0) { var firstButton = LeftPanel.Children[0] as Button; if (firstButton != null) { var focused = firstButton.Focus(); System.Diagnostics.Debug.WriteLine($"First button focus result: {focused}"); if (focused) { _panelManager.SetSelection(0, true); ScrollToSelectedItem(); } } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error setting initial focus: {ex.Message}"); } } // Простой и надежный метод скролла private async void ScrollToSelectedItem() { try { await Task.Delay(50); // Небольшая задержка для стабильности if (_panelManager.IsLeftPanelActive) { if (_panelManager.LeftSelectedIndex >= 0 && _panelManager.LeftSelectedIndex < LeftPanel.Children.Count) { // Вычисляем позицию для скролла var itemHeight = 40; // Высота одного элемента var scrollViewHeight = LeftScrollView.Height; // Высота видимой области var visibleItems = (int)(scrollViewHeight / itemHeight); // Сколько элементов видно // Вычисляем целевой скролл, чтобы элемент был в середине видимой области var targetScrollY = Math.Max(0, (_panelManager.LeftSelectedIndex - visibleItems / 2) * itemHeight); await LeftScrollView.ScrollToAsync(0, targetScrollY, true); System.Diagnostics.Debug.WriteLine($"Left scroll: index={_panelManager.LeftSelectedIndex}, targetY={targetScrollY}"); } } else { if (_panelManager.RightSelectedIndex >= 0 && _panelManager.RightSelectedIndex < RightPanel.Children.Count) { // Вычисляем позицию для скролла var itemHeight = 40; // Высота одного элемента var scrollViewHeight = RightScrollView.Height; // Высота видимой области var visibleItems = (int)(scrollViewHeight / itemHeight); // Сколько элементов видно // Вычисляем целевой скролл, чтобы элемент был в середине видимой области var targetScrollY = Math.Max(0, (_panelManager.RightSelectedIndex - visibleItems / 2) * itemHeight); await RightScrollView.ScrollToAsync(0, targetScrollY, true); System.Diagnostics.Debug.WriteLine($"Right scroll: index={_panelManager.RightSelectedIndex}, targetY={targetScrollY}"); } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Scroll error: {ex.Message}"); } } // Остальные методы без изменений... private void OpenSelectedItem() { var item = _panelManager.SelectedItem; if (item != null) { System.Diagnostics.Debug.WriteLine($"Opening: {item.Name} (IsDirectory: {item.IsDirectory})"); if (item.IsDirectory) { LoadDirectory(item.FullName, _panelManager.IsLeftPanelActive); } else { _ = _fileOperations.OpenFileAsync(item.FullName); } } else { System.Diagnostics.Debug.WriteLine("No item selected"); } } private void UpdateVisualState() { // Визуальное выделение активной панели if (_panelManager.IsLeftPanelActive) { LeftPanelIndicator.BackgroundColor = ActiveIndicatorColor; RightPanelIndicator.BackgroundColor = InactiveIndicatorColor; } else { LeftPanelIndicator.BackgroundColor = InactiveIndicatorColor; RightPanelIndicator.BackgroundColor = ActiveIndicatorColor; } UpdateButtonSelection(); } private void UpdateButtonSelection() { // Сбрасываем выделение всех кнопок foreach (var child in LeftPanel.Children) { if (child is Button button) { button.BackgroundColor = NormalButtonColor; } } foreach (var child in RightPanel.Children) { if (child is Button button) { button.BackgroundColor = NormalButtonColor; } } // Выделяем активную кнопку if (_panelManager.IsLeftPanelActive && _panelManager.LeftSelectedIndex >= 0 && _panelManager.LeftSelectedIndex < LeftPanel.Children.Count) { if (LeftPanel.Children[_panelManager.LeftSelectedIndex] is Button leftButton) { leftButton.BackgroundColor = FocusedButtonColor; } } else if (!_panelManager.IsLeftPanelActive && _panelManager.RightSelectedIndex >= 0 && _panelManager.RightSelectedIndex < RightPanel.Children.Count) { if (RightPanel.Children[_panelManager.RightSelectedIndex] is Button rightButton) { rightButton.BackgroundColor = FocusedButtonColor; } } } private void LoadDirectory(string path, bool isLeft) { try { var items = _fileService.GetDirectoryContents(path).ToList(); var panel = isLeft ? LeftPanel : RightPanel; var collection = isLeft ? _panelManager.LeftItems : _panelManager.RightItems; panel.Children.Clear(); collection.Clear(); foreach (var item in items) { collection.Add(item); AddItemToPanel(item, panel, isLeft); } if (isLeft) { LeftPath = path; _panelManager.SetSelection(items.Count > 0 ? 0 : -1, true); } else { RightPath = path; _panelManager.SetSelection(items.Count > 0 ? 0 : -1, false); } System.Diagnostics.Debug.WriteLine($"Loaded {items.Count} items to {(isLeft ? "LEFT" : "RIGHT")} panel"); UpdateVisualState(); _awaitingConfirmation = false; // Устанавливаем фокус на первый элемент if (items.Count > 0) { Dispatcher.Dispatch(() => { var firstButton = panel.Children[0] as Button; firstButton?.Focus(); ScrollToSelectedItem(); }); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error loading directory: {ex.Message}"); DisplayAlert("Error", $"Cannot load directory: {ex.Message}", "OK"); } } private void AddItemToPanel(FileSystemItem item, VerticalStackLayout panel, bool isLeft) { var button = new Button { Text = item.DisplayText, Padding = new Thickness(15, 8), HeightRequest = 40, BackgroundColor = NormalButtonColor, HorizontalOptions = LayoutOptions.FillAndExpand, TextColor = Colors.Black, FontSize = 14, BorderColor = Colors.Transparent, CornerRadius = 0 }; // Обработчик клика - выделение при клике, открытие при двойном клике button.Clicked += (s, e) => HandleItemClick(button, item, isLeft); // Обработчик фокуса button.Focused += (s, e) => { var index = panel.Children.IndexOf(button); if (index >= 0) { _panelManager.SetSelection(index, isLeft); } }; panel.Children.Add(button); } private void HandleItemClick(Button button, FileSystemItem item, bool isLeft) { var currentTime = DateTime.Now; var isDoubleClick = (currentTime - _lastClickTime).TotalMilliseconds < 500 && _lastClickedButton == button; _lastClickTime = currentTime; _lastClickedButton = button; System.Diagnostics.Debug.WriteLine($"=== CLICK: {item.Name} in {(isLeft ? "LEFT" : "RIGHT")} panel, Double: {isDoubleClick}, AwaitingConfirm: {_awaitingConfirmation} ==="); // ВСЕГДА выделяем элемент при клике var panel = isLeft ? LeftPanel : RightPanel; var index = panel.Children.IndexOf(button); if (index >= 0) { _panelManager.SetSelection(index, isLeft); button.Focus(); ScrollToSelectedItem(); } // Открываем, если: // - двойной клик (мышь), ИЛИ // - одиночный клик, но мы только что навигировались клавишами (Enter/Space имитация) if (isDoubleClick || _awaitingConfirmation) { // Сбрасываем флаг ПЕРЕД открытием _awaitingConfirmation = false; if (item.IsDirectory) { LoadDirectory(item.FullName, isLeft); } else { _ = _fileOperations.OpenFileAsync(item.FullName); } } } // Команды тулбара (без изменений) private async void OnCopyClicked(object sender, EventArgs e) { System.Diagnostics.Debug.WriteLine("=== BUTTON: F5 Copy ==="); var sourceItem = _panelManager.SelectedItem; var targetPath = _panelManager.IsLeftPanelActive ? RightPath : LeftPath; if (sourceItem != null) { try { var targetFullPath = _pathHelper.CombinePaths(targetPath, sourceItem.Name); bool success; if (sourceItem.IsDirectory) { success = await _fileOperations.CopyDirectoryAsync(sourceItem.FullName, targetFullPath); if (success) await DisplayAlert("Success", $"Directory '{sourceItem.Name}' copied successfully", "OK"); } else { success = await _fileOperations.CopyAsync(sourceItem.FullName, targetFullPath); if (success) await DisplayAlert("Success", $"File '{sourceItem.Name}' copied successfully", "OK"); } if (success) { // Обновляем целевую панель LoadDirectory(targetPath, !_panelManager.IsLeftPanelActive); } else { await DisplayAlert("Error", "Copy operation failed", "OK"); } } catch (Exception ex) { await DisplayAlert("Error", $"Copy failed: {ex.Message}", "OK"); } } else { await DisplayAlert("Info", "Please select an item to copy", "OK"); } } private async void OnMoveClicked(object sender, EventArgs e) { System.Diagnostics.Debug.WriteLine("=== BUTTON: F6 Move ==="); var sourceItem = _panelManager.SelectedItem; var targetPath = _panelManager.IsLeftPanelActive ? RightPath : LeftPath; if (sourceItem != null) { try { var targetFullPath = _pathHelper.CombinePaths(targetPath, sourceItem.Name); bool success; if (sourceItem.IsDirectory) { success = await _fileOperations.CopyDirectoryAsync(sourceItem.FullName, targetFullPath); if (success) success = await _fileOperations.DeleteAsync(sourceItem.FullName); } else { success = await _fileOperations.MoveAsync(sourceItem.FullName, targetFullPath); } if (success) { await DisplayAlert("Success", $"Item '{sourceItem.Name}' moved successfully", "OK"); // Обновляем обе панели LoadDirectory(_panelManager.IsLeftPanelActive ? LeftPath : RightPath, _panelManager.IsLeftPanelActive); LoadDirectory(targetPath, !_panelManager.IsLeftPanelActive); } else { await DisplayAlert("Error", "Move operation failed", "OK"); } } catch (Exception ex) { await DisplayAlert("Error", $"Move failed: {ex.Message}", "OK"); } } else { await DisplayAlert("Info", "Please select an item to move", "OK"); } } private async void OnMkdirClicked(object sender, EventArgs e) { System.Diagnostics.Debug.WriteLine("=== BUTTON: F7 Mkdir ==="); var currentPath = _panelManager.IsLeftPanelActive ? LeftPath : RightPath; // Запрашиваем имя новой папки var folderName = await DisplayPromptAsync("Create Folder", "Enter folder name:", "Create", "Cancel", "New Folder"); if (!string.IsNullOrWhiteSpace(folderName)) { try { var newFolderPath = _pathHelper.CombinePaths(currentPath, folderName); var success = await _fileOperations.CreateDirectoryAsync(newFolderPath); if (success) { await DisplayAlert("Success", $"Folder '{folderName}' created successfully", "OK"); // Обновляем текущую панель LoadDirectory(currentPath, _panelManager.IsLeftPanelActive); } else { await DisplayAlert("Error", "Failed to create folder", "OK"); } } catch (Exception ex) { await DisplayAlert("Error", $"Cannot create folder: {ex.Message}", "OK"); } } } private async void OnDeleteClicked(object sender, EventArgs e) { System.Diagnostics.Debug.WriteLine("=== BUTTON: F8 Delete ==="); var item = _panelManager.SelectedItem; if (item != null) { // Подтверждение удаления var result = await DisplayAlert("Confirm Delete", $"Are you sure you want to delete '{item.Name}'?", "Delete", "Cancel"); if (result) { try { var success = await _fileOperations.DeleteAsync(item.FullName); if (success) { await DisplayAlert("Success", $"Item '{item.Name}' deleted successfully", "OK"); // Обновляем текущую панель LoadDirectory(_panelManager.IsLeftPanelActive ? LeftPath : RightPath, _panelManager.IsLeftPanelActive); } else { await DisplayAlert("Error", "Delete operation failed", "OK"); } } catch (Exception ex) { await DisplayAlert("Error", $"Delete failed: {ex.Message}", "OK"); } } } else { await DisplayAlert("Info", "Please select an item to delete", "OK"); } } private void OnHomeClicked(object sender, EventArgs e) { System.Diagnostics.Debug.WriteLine("=== BUTTON: Home ==="); try { // Получаем домашнюю директорию пользователя var homePath = _pathHelper.GetUserHomePath(); // Переходим в домашнюю директорию на активной панели LoadDirectory(homePath, _panelManager.IsLeftPanelActive); System.Diagnostics.Debug.WriteLine($"Navigated to home directory: {homePath}"); } catch (Exception ex) { DisplayAlert("Error", $"Cannot navigate to home directory: {ex.Message}", "OK"); } } private void OnExitClicked(object sender, EventArgs e) => Application.Current?.Quit(); // INotifyPropertyChanged public new event PropertyChangedEventHandler? PropertyChanged; protected virtual void NotifyPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }