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.

458 lines
15 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 int _lastLeftSelectedIndex = 0;
private int _lastRightSelectedIndex = 0;
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();
}
// Переключаем фокус с указанием направления
private void MoveFocus(bool moveForward = true)
{
var allFocusable = GetFocusableElements();
var current = allFocusable.FirstOrDefault(x => x.IsFocused);
var next = moveForward ?
GetNextFocusable(current, allFocusable) :
GetPreviousFocusable(current, allFocusable);
next?.Focus();
}
// Следующий элемент (для Down/Tab)
private View GetNextFocusable(View current, List<View> allElements)
{
if (current == null) return allElements.FirstOrDefault();
var currentIndex = allElements.IndexOf(current);
if (currentIndex == -1) return allElements.FirstOrDefault();
var nextIndex = (currentIndex + 1) % allElements.Count;
return allElements[nextIndex];
}
// Предыдущий элемент (для Up/Shift+Tab)
private View GetPreviousFocusable(View current, List<View> allElements)
{
if (current == null) return allElements.LastOrDefault();
var currentIndex = allElements.IndexOf(current);
if (currentIndex == -1) return allElements.LastOrDefault();
var prevIndex = (currentIndex - 1 + allElements.Count) % allElements.Count;
return allElements[prevIndex];
}
private List<View> GetFocusableElements()
{
return this.GetVisualTreeDescendants()
.OfType<View>()
.Where(x => x.IsEnabled && x.IsVisible)
.OrderBy(GetVisualTreeOrder) // Сортируем по порядку в визуальном дереве
.ToList();
}
private int GetVisualTreeOrder(View view)
{
// Простой способ - используем порядок в визуальном дереве
var parent = view.Parent as Layout;
if (parent != null)
{
var index = parent.Children.IndexOf(view);
return index >= 0 ? index : 0;
}
return 0;
}
// Обработчики выделения (вызываются при изменении Selection в CollectionView)
private void OnLeftSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.FirstOrDefault() is FileSystemItem selectedItem)
{
_leftSelectedIndex = LeftItems.IndexOf(selectedItem);
_isLeftPanelActive = true;
System.Diagnostics.Debug.WriteLine($"L !!! {_leftSelectedIndex} {_lastLeftSelectedIndex}");
if (_lastLeftSelectedIndex < LeftItems.Count && _lastLeftSelectedIndex >= 0)
{
// Автоматически переключаем фокус при достижении границ
if (_leftSelectedIndex > _lastLeftSelectedIndex)
{
// Достигли конца списка - переходим к следующему элементу
MoveFocus(moveForward: true);
}
else if (_leftSelectedIndex < _lastLeftSelectedIndex)
{
// Достигли начала списка - переходим к предыдущему элементу
MoveFocus(moveForward: false);
}
}
UpdateVisualSelection();
}
}
private void OnRightSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.FirstOrDefault() is FileSystemItem selectedItem)
{
_rightSelectedIndex = RightItems.IndexOf(selectedItem);
_isLeftPanelActive = false;
System.Diagnostics.Debug.WriteLine($"R !!! {_rightSelectedIndex} {_lastRightSelectedIndex}");
if (_rightSelectedIndex < RightItems.Count && _rightSelectedIndex >= 0)
{
// Автоматически переключаем фокус при достижении границ
if (_rightSelectedIndex > _lastRightSelectedIndex)
{
// Достигли конца списка - переходим к следующему элементу
MoveFocus(moveForward: true);
}
else if (_rightSelectedIndex < _lastRightSelectedIndex)
{
// Достигли начала списка - переходим к предыдущему элементу
MoveFocus(moveForward: 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;
// Простой способ - используем текущие индексы
if (_isLeftPanelActive)
{
_lastLeftSelectedIndex = _leftSelectedIndex;
}
else
{
_lastRightSelectedIndex = _rightSelectedIndex;
}
// Прокручиваем к выбранному элементу
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));
}
}