You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
11 KiB
C#

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<FileSystemItem> LeftItems { get; } = new();
public ObservableCollection<FileSystemItem> 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<string, string, Task> 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));
}
}