From 31af132b311c4e5e62e8d382c520680ea9ffccb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B8=D0=BB=D0=B8=D0=BF=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B8=CC=86=20=D0=91=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D1=81=D0=BE=D0=B2=D0=B8=D1=87?= Date: Thu, 20 Nov 2025 21:44:26 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.xaml.cs | 35 +- FileOperations.cs | 129 +++++++ FileSystemService.cs | 4 - IFileOperations.cs | 18 + IFileSystemService.cs | 13 +- IKeyboardService.cs | 13 + IPanelManager.cs | 35 ++ IPathHelper.cs | 10 + KeyboardService.cs | 129 +++++++ MainPage.xaml | 85 +++-- MainPage.xaml.cs | 761 ++++++++++++++++++++++----------------- MauiProgram.cs | 2 +- MockFileOperations.cs | 46 +++ MockFileSystemItem.cs | 34 ++ MockFileSystemService.cs | 81 +++++ MockKeyboardService.cs | 17 + MockPanelManager.cs | 50 +++ MockPathHelper.cs | 13 + PanelCollectionView.cs | 58 --- PanelManager.cs | 119 ++++++ PathHelper.cs | 28 ++ README.md | 174 ++++++++- 22 files changed, 1411 insertions(+), 443 deletions(-) create mode 100644 FileOperations.cs create mode 100644 IFileOperations.cs create mode 100644 IKeyboardService.cs create mode 100644 IPanelManager.cs create mode 100644 IPathHelper.cs create mode 100644 KeyboardService.cs create mode 100644 MockFileOperations.cs create mode 100644 MockFileSystemItem.cs create mode 100644 MockFileSystemService.cs create mode 100644 MockKeyboardService.cs create mode 100644 MockPanelManager.cs create mode 100644 MockPathHelper.cs delete mode 100644 PanelCollectionView.cs create mode 100644 PanelManager.cs create mode 100644 PathHelper.cs diff --git a/App.xaml.cs b/App.xaml.cs index ebd66b4..46d1a1a 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,15 +1,28 @@ -namespace CommanderApp +using CommanderApp.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace CommanderApp; + +public partial class App : Application { - public partial class App : Application + public App() + { + InitializeComponent(); + + var services = new ServiceCollection(); + ConfigureServices(services); + var serviceProvider = services.BuildServiceProvider(); + + MainPage = serviceProvider.GetService(); + } + + private void ConfigureServices(ServiceCollection services) { - public App() - { - InitializeComponent(); - } - - protected override Window CreateWindow(IActivationState? activationState) - { - return new Window(new AppShell()); - } + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } \ No newline at end of file diff --git a/FileOperations.cs b/FileOperations.cs new file mode 100644 index 0000000..5f0c295 --- /dev/null +++ b/FileOperations.cs @@ -0,0 +1,129 @@ +namespace CommanderApp.Services; + +public class FileOperations : IFileOperations +{ + public async Task CopyAsync(string sourcePath, string targetPath, bool overwrite = true) + { + try + { + await Task.Run(() => File.Copy(sourcePath, targetPath, overwrite)); + return true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Copy failed: {ex.Message}"); + return false; + } + } + + public async Task MoveAsync(string sourcePath, string targetPath, bool overwrite = true) + { + try + { + await Task.Run(() => + { + if (File.Exists(targetPath) && overwrite) + File.Delete(targetPath); + File.Move(sourcePath, targetPath); + }); + return true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Move failed: {ex.Message}"); + return false; + } + } + + public async Task DeleteAsync(string path) + { + try + { + await Task.Run(() => + { + if (Directory.Exists(path)) + Directory.Delete(path, true); + else if (File.Exists(path)) + File.Delete(path); + }); + return true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Delete failed: {ex.Message}"); + return false; + } + } + + public async Task CreateDirectoryAsync(string path) + { + try + { + await Task.Run(() => Directory.CreateDirectory(path)); + return true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"CreateDirectory failed: {ex.Message}"); + return false; + } + } + + public async Task OpenFileAsync(string filePath) + { + try + { + await Task.Run(() => + { +#if WINDOWS + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + { + FileName = filePath, + UseShellExecute = true + }); +#else + var process = new System.Diagnostics.Process(); + process.StartInfo.FileName = "open"; + process.StartInfo.Arguments = $"\"{filePath}\""; + process.StartInfo.UseShellExecute = false; + process.Start(); +#endif + }); + return true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"OpenFile failed: {ex.Message}"); + return false; + } + } + + public async Task CopyDirectoryAsync(string sourceDir, string targetDir) + { + try + { + await Task.Run(() => + { + Directory.CreateDirectory(targetDir); + + foreach (var file in Directory.GetFiles(sourceDir)) + { + var destFile = Path.Combine(targetDir, Path.GetFileName(file)); + File.Copy(file, destFile, true); + } + + foreach (var directory in Directory.GetDirectories(sourceDir)) + { + var destDir = Path.Combine(targetDir, Path.GetFileName(directory)); + CopyDirectoryAsync(directory, destDir).Wait(); + } + }); + return true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"CopyDirectory failed: {ex.Message}"); + return false; + } + } +} \ No newline at end of file diff --git a/FileSystemService.cs b/FileSystemService.cs index 80838bd..cb618d6 100644 --- a/FileSystemService.cs +++ b/FileSystemService.cs @@ -1,6 +1,5 @@ using System.IO; using System.Linq; -using Microsoft.Maui.Storage; namespace CommanderApp; @@ -12,14 +11,12 @@ public class FileSystemService : IFileSystemService return FileSystem.AppDataDirectory; #else var path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - // Убедимся, что путь корректен return Path.GetFullPath(path); #endif } public IEnumerable GetDirectoryContents(string path) { - // Нормализуем путь path = Path.GetFullPath(path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); if (!Directory.Exists(path)) @@ -65,7 +62,6 @@ public class FileSystemService : IFileSystemService catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error reading directory {path}: {ex.Message}"); - // Возвращаем хотя бы ".." если возможно if (items.Count == 0 && Directory.GetParent(path) != null) { items.Add(new FileSystemItem diff --git a/IFileOperations.cs b/IFileOperations.cs new file mode 100644 index 0000000..1a62cd0 --- /dev/null +++ b/IFileOperations.cs @@ -0,0 +1,18 @@ +namespace CommanderApp.Services; + +public interface IFileOperations +{ + Task CopyAsync(string sourcePath, string targetPath, bool overwrite = true); + Task MoveAsync(string sourcePath, string targetPath, bool overwrite = true); + Task DeleteAsync(string path); + Task CreateDirectoryAsync(string path); + Task OpenFileAsync(string filePath); + Task CopyDirectoryAsync(string sourceDir, string targetDir); +} + +public class FileOperationResult +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; + public Exception Exception { get; set; } +} \ No newline at end of file diff --git a/IFileSystemService.cs b/IFileSystemService.cs index 1947800..cfc820c 100644 --- a/IFileSystemService.cs +++ b/IFileSystemService.cs @@ -1,8 +1,7 @@ -namespace CommanderApp +namespace CommanderApp; + +public interface IFileSystemService { - public interface IFileSystemService - { - IEnumerable GetDirectoryContents(string path); - string GetRootPath(); - } -} + string GetRootPath(); + IEnumerable GetDirectoryContents(string path); +} \ No newline at end of file diff --git a/IKeyboardService.cs b/IKeyboardService.cs new file mode 100644 index 0000000..0af10ea --- /dev/null +++ b/IKeyboardService.cs @@ -0,0 +1,13 @@ +namespace CommanderApp.Services; + +public interface IKeyboardService +{ + void SetupKeyboardHandling(ContentPage page); + event EventHandler KeyPressed; +} + +public class KeyPressedEventArgs : EventArgs +{ + public string Key { get; set; } = string.Empty; + public string Platform { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/IPanelManager.cs b/IPanelManager.cs new file mode 100644 index 0000000..1c3d52a --- /dev/null +++ b/IPanelManager.cs @@ -0,0 +1,35 @@ +using System.Collections.ObjectModel; + +namespace CommanderApp.Services; + +public interface IPanelManager +{ + event EventHandler StateChanged; + + bool IsLeftPanelActive { get; } + FileSystemItem SelectedItem { get; } + string ActivePanelPath { get; } + string LeftPanelPath { get; } + string RightPanelPath { get; } + int LeftSelectedIndex { get; } + int RightSelectedIndex { get; } + + void SwitchToLeftPanel(); + void SwitchToRightPanel(); + void MoveSelection(int direction); + void SetSelection(int index, bool isLeftPanel); + void UpdatePanelPaths(string leftPath, string rightPath); + void ClearSelection(); + + // Для привязки данных + ObservableCollection LeftItems { get; } + ObservableCollection RightItems { get; } +} + +public class PanelStateChangedEventArgs : EventArgs +{ + public bool IsLeftPanelActive { get; set; } + public int LeftSelectedIndex { get; set; } + public int RightSelectedIndex { get; set; } + public FileSystemItem SelectedItem { get; set; } +} \ No newline at end of file diff --git a/IPathHelper.cs b/IPathHelper.cs new file mode 100644 index 0000000..772a3f2 --- /dev/null +++ b/IPathHelper.cs @@ -0,0 +1,10 @@ +namespace CommanderApp.Services; + +public interface IPathHelper +{ + string GetUserHomePath(); + string GetRootPath(); + string CombinePaths(params string[] paths); + string GetFileName(string path); + string GetDirectoryName(string path); +} \ No newline at end of file diff --git a/KeyboardService.cs b/KeyboardService.cs new file mode 100644 index 0000000..864af70 --- /dev/null +++ b/KeyboardService.cs @@ -0,0 +1,129 @@ +#if WINDOWS +using Microsoft.UI.Xaml.Input; +using Windows.System; +#endif + +namespace CommanderApp.Services; + +public class KeyboardService : IKeyboardService +{ + public event EventHandler KeyPressed; + + public void SetupKeyboardHandling(ContentPage page) + { +#if WINDOWS + SetupWindowsKeyboardHandling(page); +#elif MACCATALYST + SetupMacKeyboardHandling(page); +#endif + } + +#if WINDOWS + private void SetupWindowsKeyboardHandling(ContentPage page) + { + try + { + if (page.Handler?.PlatformView is Microsoft.UI.Xaml.FrameworkElement frameworkElement) + { + frameworkElement.KeyDown += OnWindowsKeyDown; + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Windows keyboard setup error: {ex.Message}"); + } + } + + private void OnWindowsKeyDown(object sender, KeyRoutedEventArgs e) + { + var key = e.Key switch + { + VirtualKey.W => "w", + VirtualKey.S => "s", + VirtualKey.A => "a", + VirtualKey.D => "d", + VirtualKey.Space => " ", + VirtualKey.Enter => "enter", + VirtualKey.F5 => "f5", + VirtualKey.F6 => "f6", + VirtualKey.F7 => "f7", + VirtualKey.F8 => "f8", + VirtualKey.F10 => "f10", + VirtualKey.H => "h", + _ => null + }; + + if (key != null) + { + KeyPressed?.Invoke(this, new KeyPressedEventArgs { Key = key, Platform = "Windows" }); + e.Handled = true; + } + } +#endif + +#if MACCATALYST + private void SetupMacKeyboardHandling(ContentPage page) + { + try + { + if (page.Handler?.PlatformView is UIKit.UIView uiView) + { + var keyHandler = new MacKeyHandler(OnMacKeyPressed); + keyHandler.Frame = uiView.Bounds; + keyHandler.AutoresizingMask = UIKit.UIViewAutoresizing.All; + uiView.AddSubview(keyHandler); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Mac keyboard setup error: {ex.Message}"); + } + } + + private void OnMacKeyPressed(string key) + { + KeyPressed?.Invoke(this, new KeyPressedEventArgs { Key = key, Platform = "Mac" }); + } + + public class MacKeyHandler : UIKit.UIView + { + private readonly Action _keyHandler; + + public MacKeyHandler(Action keyHandler) + { + _keyHandler = keyHandler; + this.BecomeFirstResponder(); + } + + public override bool CanBecomeFirstResponder => true; + + public override UIKit.UIKeyCommand[] KeyCommands => new[] + { + UIKit.UIKeyCommand.Create((Foundation.NSString)"w", (UIKit.UIKeyModifierFlags)0, new ObjCRuntime.Selector("handleW:")), + UIKit.UIKeyCommand.Create((Foundation.NSString)"s", (UIKit.UIKeyModifierFlags)0, new ObjCRuntime.Selector("handleS:")), + UIKit.UIKeyCommand.Create((Foundation.NSString)"a", (UIKit.UIKeyModifierFlags)0, new ObjCRuntime.Selector("handleA:")), + UIKit.UIKeyCommand.Create((Foundation.NSString)"d", (UIKit.UIKeyModifierFlags)0, new ObjCRuntime.Selector("handleD:")), + UIKit.UIKeyCommand.Create((Foundation.NSString)" ", (UIKit.UIKeyModifierFlags)0, new ObjCRuntime.Selector("handleSpace:")), + UIKit.UIKeyCommand.Create((Foundation.NSString)"h", (UIKit.UIKeyModifierFlags)0, new ObjCRuntime.Selector("handleH:")) + }; + + [Foundation.Export("handleW:")] + void HandleW(UIKit.UIKeyCommand cmd) => _keyHandler?.Invoke("w"); + + [Foundation.Export("handleS:")] + void HandleS(UIKit.UIKeyCommand cmd) => _keyHandler?.Invoke("s"); + + [Foundation.Export("handleA:")] + void HandleA(UIKit.UIKeyCommand cmd) => _keyHandler?.Invoke("a"); + + [Foundation.Export("handleD:")] + void HandleD(UIKit.UIKeyCommand cmd) => _keyHandler?.Invoke("d"); + + [Foundation.Export("handleSpace:")] + void HandleSpace(UIKit.UIKeyCommand cmd) => _keyHandler?.Invoke(" "); + + [Foundation.Export("handleH:")] + void HandleH(UIKit.UIKeyCommand cmd) => _keyHandler?.Invoke("h"); + } +#endif +} \ No newline at end of file diff --git a/MainPage.xaml b/MainPage.xaml index 8a0fca6..dc0db9c 100644 --- a/MainPage.xaml +++ b/MainPage.xaml @@ -1,51 +1,61 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + x:Class="CommanderApp.MainPage" + Title="MAUI Commander">