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.

659 lines
24 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.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));
}
}