using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using Microsoft.Maui.Controls; namespace CommanderApp; public partial class MainPage : ContentPage, INotifyPropertyChanged { private readonly IFileSystemService _fileService; public ObservableCollection LeftItems { get; } = new(); public ObservableCollection RightItems { get; } = new(); private string _leftPath = string.Empty; private string _rightPath = string.Empty; // Для управления выделением и фокусом private Button? _currentFocusedButton; private bool _isLeftPanelActive = true; private int _leftSelectedIndex = -1; private int _rightSelectedIndex = -1; public string LeftPath { get => _leftPath; set { if (_leftPath != value) { _leftPath = value; NotifyPropertyChanged(); } } } public string RightPath { get => _rightPath; set { if (_rightPath != value) { _rightPath = value; NotifyPropertyChanged(); } } } private string _currentLeftPath = string.Empty; private string _currentRightPath = string.Empty; private FileSystemItem? _selectedLeftItem; private FileSystemItem? _selectedRightItem; // Для двойного нажатия private FileSystemItem? _lastClickedItem; private bool? _lastIsLeftPanel; public MainPage(IFileSystemService fileService) { InitializeComponent(); _fileService = fileService; BindingContext = this; // Инициализация шаблонов InitializeCollectionViews(); var root = _fileService.GetRootPath(); LoadDirectory(root, true); LoadDirectory(root, false); // Устанавливаем фокус на страницу this.Focus(); } private void InitializeCollectionViews() { LeftPanel.ItemTemplate = PanelCollectionView.CreateItemTemplate(isLeftPanel: true, page: this); RightPanel.ItemTemplate = PanelCollectionView.CreateItemTemplate(isLeftPanel: true, page: this); } protected override void OnHandlerChanged() { base.OnHandlerChanged(); this.Focus(); } public void HandleItemClick(FileSystemItem item, bool isLeftPanel) { if (_lastIsLeftPanel == isLeftPanel && _lastClickedItem == item) { // Двойной клик if (isLeftPanel) OnLeftItemDoubleTapped(item); else OnRightItemDoubleTapped(item); } else { // Одинарный клик - выделение if (isLeftPanel) { _isLeftPanelActive = true; _leftSelectedIndex = LeftItems.IndexOf(item); _selectedLeftItem = item; _selectedRightItem = null; } else { _isLeftPanelActive = false; _rightSelectedIndex = RightItems.IndexOf(item); _selectedRightItem = item; _selectedLeftItem = null; } UpdateVisualSelection(); } _lastIsLeftPanel = isLeftPanel; _lastClickedItem = item; } private void LoadDirectory(string path, bool isLeft) { var items = _fileService.GetDirectoryContents(path).ToList(); if (isLeft) { LeftItems.Clear(); foreach (var item in items) LeftItems.Add(item); _currentLeftPath = path; LeftPath = path; // Сбрасываем выделение при загрузке новой директории _leftSelectedIndex = items.Count > 0 ? 0 : -1; } else { RightItems.Clear(); foreach (var item in items) RightItems.Add(item); _currentRightPath = path; RightPath = path; _rightSelectedIndex = items.Count > 0 ? 0 : -1; } UpdateVisualSelection(); } // Обработчики выделения (вызываются при изменении Selection в CollectionView) private void OnLeftSelectionChanged(object sender, SelectionChangedEventArgs e) { // Обновляем индексы при программном изменении selection if (e.CurrentSelection.FirstOrDefault() is FileSystemItem selectedItem) { _leftSelectedIndex = LeftItems.IndexOf(selectedItem); _isLeftPanelActive = true; UpdateVisualSelection(); } } private void OnRightSelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.CurrentSelection.FirstOrDefault() is FileSystemItem selectedItem) { _rightSelectedIndex = RightItems.IndexOf(selectedItem); _isLeftPanelActive = false; UpdateVisualSelection(); } } private void UpdateVisualSelection() { //Сбрасываем предыдущий фокус if (_currentFocusedButton != null) { VisualStateManager.GoToState(_currentFocusedButton, "Normal"); _currentFocusedButton = null; } if (_isLeftPanelActive && _leftSelectedIndex >= 0 && _leftSelectedIndex < LeftItems.Count) { var item = LeftItems[_leftSelectedIndex]; SetFocusToItem(LeftPanel, item); _selectedLeftItem = item; _selectedRightItem = null; } else if (!_isLeftPanelActive && _rightSelectedIndex >= 0 && _rightSelectedIndex < RightItems.Count) { var item = RightItems[_rightSelectedIndex]; SetFocusToItem(RightPanel, item); _selectedRightItem = item; _selectedLeftItem = null; } } private void SetFocusToItem(CollectionView collectionView, FileSystemItem item) { // Используем Dispatcher чтобы дождаться рендеринга Dispatcher.Dispatch(() => { var container = FindButtonContainer(collectionView, item); if (container is Button button) { VisualStateManager.GoToState(button, "Focused"); _currentFocusedButton = button; _lastClickedItem = item; // Прокручиваем к выбранному элементу collectionView.ScrollTo(item, position: ScrollToPosition.MakeVisible, animate: false); } }); } private Button? FindButtonContainer(CollectionView collectionView, FileSystemItem item) { // Ищем кнопку в логических дочерних элементах foreach (var child in collectionView.LogicalChildren) { if (child is Button button && button.BindingContext == item) { return button; } } return null; } public void OnLeftItemDoubleTapped(FileSystemItem item) { if (item.IsDirectory) { LoadDirectory(item.FullName, true); } } public void OnRightItemDoubleTapped(FileSystemItem item) { if (item.IsDirectory) { LoadDirectory(item.FullName, false); } } // Остальные методы без изменений private async void OnCopyClicked(object sender, EventArgs e) { await ProcessFileOperation(async (src, destDir) => { if (Directory.Exists(src)) await CopyDirectory(src, Path.Combine(destDir, Path.GetFileName(src))); else File.Copy(src, Path.Combine(destDir, Path.GetFileName(src)), overwrite: true); }, "Copy"); } private async void OnMoveClicked(object sender, EventArgs e) { await ProcessFileOperation((src, destDir) => { var dest = Path.Combine(destDir, Path.GetFileName(src)); if (Directory.Exists(src)) Directory.Move(src, dest); else File.Move(src, dest, overwrite: true); return Task.CompletedTask; }, "Move"); } private async void OnDeleteClicked(object sender, EventArgs e) { var item = _selectedLeftItem ?? _selectedRightItem; if (item == null) return; var confirm = await DisplayAlert("Delete", $"Delete '{item.Name}'?", "Yes", "No"); if (confirm) { if (item.IsDirectory) Directory.Delete(item.FullName, recursive: true); else File.Delete(item.FullName); LoadDirectory(_currentLeftPath, true); LoadDirectory(_currentRightPath, false); } } private async void OnMkdirClicked(object sender, EventArgs e) { var result = await DisplayPromptAsync("New Folder", "Folder name:", "Create", "Cancel"); if (string.IsNullOrWhiteSpace(result)) return; string targetPath = (_selectedLeftItem != null || LeftItems.Count > 0) ? _currentLeftPath : _currentRightPath; string newPath = Path.Combine(targetPath, result.Trim()); if (!Directory.Exists(newPath)) { Directory.CreateDirectory(newPath); LoadDirectory(_currentLeftPath, true); LoadDirectory(_currentRightPath, false); } else { await DisplayAlert("Error", "Folder already exists.", "OK"); } } private void OnExitClicked(object sender, EventArgs e) { Application.Current?.Quit(); } private async Task CopyDirectory(string sourceDir, string targetDir) { Directory.CreateDirectory(targetDir); foreach (var file in Directory.GetFiles(sourceDir)) { await Task.Run(() => File.Copy(file, Path.Combine(targetDir, Path.GetFileName(file)), overwrite: true)); } foreach (var dir in Directory.GetDirectories(sourceDir)) { await CopyDirectory(dir, Path.Combine(targetDir, Path.GetFileName(dir))); } } private async Task ProcessFileOperation(Func operation, string actionName) { var srcItem = _selectedLeftItem ?? _selectedRightItem; if (srcItem == null) { await DisplayAlert("Error", "Select a file or folder first.", "OK"); return; } string destDir = (srcItem == _selectedLeftItem) ? _currentRightPath : _currentLeftPath; try { await operation(srcItem.FullName, destDir); LoadDirectory(_currentLeftPath, true); LoadDirectory(_currentRightPath, false); } catch (Exception ex) { await DisplayAlert("Error", $"{actionName} failed: {ex.Message}", "OK"); } } public new event PropertyChangedEventHandler? PropertyChanged; protected virtual void NotifyPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }