// MainPage.xaml.cs 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; set; } = new(); public ObservableCollection RightItems { get; set; } = new(); // --- Изменено: приватные поля + INotifyPropertyChanged --- private string _leftPath = string.Empty; private string _rightPath = string.Empty; private Grid? _currentSelectedGrid; // только один выделенный элемент в интерфейсе 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; public MainPage(IFileSystemService fileService) { InitializeComponent(); _fileService = fileService; BindingContext = this; var root = _fileService.GetRootPath(); LoadDirectory(root, true); LoadDirectory(root, false); } 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; // Теперь это вызовет обновление UI } else { RightItems.Clear(); foreach (var item in items) RightItems.Add(item); _currentRightPath = path; RightPath = path; // И это тоже } } private void OnLeftSelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.CurrentSelection.FirstOrDefault() is FileSystemItem item) { // Сохраняем выбор _selectedLeftItem = item; // Если это ".." или папка — переходим if (item.Name == ".." || item.IsDirectory) { LoadDirectory(item.FullName, true); // После перехода — сбрасываем выбор, чтобы не осталось выделения в новой папке _selectedLeftItem = null; LeftPanel.SelectedItem = null; } // Если это файл — ничего не делаем, оставляем его выделенным } } private void OnRightSelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.CurrentSelection.FirstOrDefault() is FileSystemItem item) { _selectedRightItem = item; if (item.Name == ".." || item.IsDirectory) { LoadDirectory(item.FullName, false); _selectedRightItem = null; RightPanel.SelectedItem = null; } } } 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"); } } private void OnLeftItemTapped(object sender, TappedEventArgs e) { var grid = (Grid)sender; var item = (FileSystemItem)grid.BindingContext; // Сбрасываем выделение у предыдущего элемента (где бы он ни был) if (_currentSelectedGrid != null) { _currentSelectedGrid.BackgroundColor = Colors.Transparent; } // Выделяем новый элемент grid.BackgroundColor = Color.FromArgb("#d0e0ff"); _currentSelectedGrid = grid; // Обновляем активный выбор _selectedLeftItem = item; _selectedRightItem = null; // сбрасываем выбор в правой панели } private void OnLeftItemDoubleTapped(object sender, TappedEventArgs e) { var grid = (Grid)sender; var item = (FileSystemItem)grid.BindingContext; if (item.IsDirectory) { LoadDirectory(item.FullName, isLeft: true); // Сбрасываем выделение, так как панель обновляется if (_currentSelectedGrid != null) { _currentSelectedGrid.BackgroundColor = Colors.Transparent; _currentSelectedGrid = null; } _selectedLeftItem = null; _selectedRightItem = null; } } private void OnRightItemTapped(object sender, TappedEventArgs e) { var grid = (Grid)sender; var item = (FileSystemItem)grid.BindingContext; if (_currentSelectedGrid != null) { _currentSelectedGrid.BackgroundColor = Colors.Transparent; } grid.BackgroundColor = Color.FromArgb("#d0e0ff"); _currentSelectedGrid = grid; _selectedRightItem = item; _selectedLeftItem = null; // сбрасываем выбор в левой панели } private void OnRightItemDoubleTapped(object sender, TappedEventArgs e) { var grid = (Grid)sender; var item = (FileSystemItem)grid.BindingContext; if (item.IsDirectory) { LoadDirectory(item.FullName, isLeft: false); if (_currentSelectedGrid != null) { _currentSelectedGrid.BackgroundColor = Colors.Transparent; _currentSelectedGrid = null; } _selectedLeftItem = null; _selectedRightItem = null; } } // --- Реализация INotifyPropertyChanged --- public new event PropertyChangedEventHandler? PropertyChanged; protected virtual void NotifyPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }