diff --git a/Companion/App.axaml.cs b/Companion/App.axaml.cs index 7ab0b1d..449fc8a 100644 --- a/Companion/App.axaml.cs +++ b/Companion/App.axaml.cs @@ -27,9 +27,9 @@ namespace Companion; public class App : Application { - public static IServiceProvider ServiceProvider { get; private set; } + public static IServiceProvider ServiceProvider { get; private set; } = null!; - public static string OSType { get; private set; } + public static string OSType { get; private set; } = "Unknown"; #if DEBUG private bool _ShouldCheckForUpdates = false; @@ -243,7 +243,7 @@ public override void OnFrameworkInitializationCompleted() // check for updates if (_ShouldCheckForUpdates) - CheckForUpdatesAsync(); + _ = CheckForUpdatesAsync(); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { diff --git a/Companion/Converters/BooleanGreaterThanConverter.cs b/Companion/Converters/BooleanGreaterThanConverter.cs index 5ff53fa..b997387 100644 --- a/Companion/Converters/BooleanGreaterThanConverter.cs +++ b/Companion/Converters/BooleanGreaterThanConverter.cs @@ -9,7 +9,7 @@ namespace Companion.Converters; /// public class BooleanGreaterThanConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value == null || parameter == null) return false; @@ -27,8 +27,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn return false; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Companion/Converters/BooleanToTextConverter.cs b/Companion/Converters/BooleanToTextConverter.cs index 79906c1..ec06ff5 100644 --- a/Companion/Converters/BooleanToTextConverter.cs +++ b/Companion/Converters/BooleanToTextConverter.cs @@ -6,14 +6,20 @@ namespace Companion.Converters; public class BooleanToTextConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - var texts = parameter.ToString().Split(','); - return (bool)value ? texts[1] : texts[0]; + if (parameter == null) + return string.Empty; + + var texts = parameter.ToString()?.Split(',') ?? Array.Empty(); + if (texts.Length < 2 || value is not bool flag) + return string.Empty; + + return flag ? texts[1] : texts[0]; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Companion/Converters/BooleanToVisibilityConverter.cs b/Companion/Converters/BooleanToVisibilityConverter.cs index a5c5743..5dae73e 100644 --- a/Companion/Converters/BooleanToVisibilityConverter.cs +++ b/Companion/Converters/BooleanToVisibilityConverter.cs @@ -7,14 +7,16 @@ namespace Companion.Converters; public class BooleanToVisibilityConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - var isVisible = (bool)value; - return isVisible ? Visibility.Visible : Visibility.Collapse; + if (value is bool isVisible) + return isVisible ? Visibility.Visible : Visibility.Collapse; + + return Visibility.Collapse; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Companion/Converters/BooleanToWidthConverter.cs b/Companion/Converters/BooleanToWidthConverter.cs index ea2e40d..cef80aa 100644 --- a/Companion/Converters/BooleanToWidthConverter.cs +++ b/Companion/Converters/BooleanToWidthConverter.cs @@ -6,14 +6,24 @@ namespace Companion.Converters; public class BooleanToWidthConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - var widths = parameter.ToString().Split(','); - return (bool)value ? double.Parse(widths[0]) : double.Parse(widths[1]); + if (parameter == null || value is not bool flag) + return 0d; + + var widths = parameter.ToString()?.Split(',') ?? Array.Empty(); + if (widths.Length < 2) + return 0d; + + if (!double.TryParse(widths[0], out var trueWidth) || + !double.TryParse(widths[1], out var falseWidth)) + return 0d; + + return flag ? trueWidth : falseWidth; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Companion/Converters/CanConnectConverter.cs b/Companion/Converters/CanConnectConverter.cs index d71f37b..90c449b 100644 --- a/Companion/Converters/CanConnectConverter.cs +++ b/Companion/Converters/CanConnectConverter.cs @@ -9,7 +9,7 @@ namespace Companion.Converters; public class CanConnectConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { // Try to get the DataContext from the current view model if (Avalonia.Application.Current?.DataContext is PresetsTabViewModel viewModel) @@ -58,8 +58,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn return null; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Companion/Converters/EnumToBoolConverter.cs b/Companion/Converters/EnumToBoolConverter.cs index 1a95409..a98f0ab 100644 --- a/Companion/Converters/EnumToBoolConverter.cs +++ b/Companion/Converters/EnumToBoolConverter.cs @@ -6,22 +6,22 @@ namespace Companion.Converters; public class EnumToBoolConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (value != null && value.GetType().IsEnum) return (int)value == (int)parameter; + if (value != null && value.GetType().IsEnum && parameter != null) return (int)value == (int)parameter; return false; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { if (targetType.IsEnum) - if (value is bool boolValue) + if (value is bool boolValue && parameter != null) { if (boolValue) - return Enum.Parse(targetType, parameter.ToString()); + return Enum.Parse(targetType, parameter.ToString() ?? "None"); return Enum.Parse(targetType, "None"); // or some other default value } throw new ArgumentException("Unsupported type", nameof(value)); } -} \ No newline at end of file +} diff --git a/Companion/Converters/InvertedBooleanConverter.cs b/Companion/Converters/InvertedBooleanConverter.cs index a95122c..21bd495 100644 --- a/Companion/Converters/InvertedBooleanConverter.cs +++ b/Companion/Converters/InvertedBooleanConverter.cs @@ -7,15 +7,15 @@ namespace Companion.Converters; public class InvertedBooleanConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is bool booleanValue) return !booleanValue; return false; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is bool booleanValue) return !booleanValue; return false; } -} \ No newline at end of file +} diff --git a/Companion/Converters/NullToBoolConverter.cs b/Companion/Converters/NullToBoolConverter.cs index 45a6359..ffa5450 100644 --- a/Companion/Converters/NullToBoolConverter.cs +++ b/Companion/Converters/NullToBoolConverter.cs @@ -6,13 +6,13 @@ namespace Companion.Converters; public class NullToBoolConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return value != null; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Companion/Converters/PowerThresholdColorConverter.cs b/Companion/Converters/PowerThresholdColorConverter.cs index 44a09c4..2e04c55 100644 --- a/Companion/Converters/PowerThresholdColorConverter.cs +++ b/Companion/Converters/PowerThresholdColorConverter.cs @@ -25,7 +25,7 @@ public class PowerThresholdColorConverter : IValueConverter /// public ISolidColorBrush WarningColor { get; set; } = new SolidColorBrush(Colors.Red); - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is double doubleValue) { @@ -40,7 +40,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn return NormalColor; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/Companion/Converters/TagsToStringConverter.cs b/Companion/Converters/TagsToStringConverter.cs index 7f83bb7..0c18baa 100644 --- a/Companion/Converters/TagsToStringConverter.cs +++ b/Companion/Converters/TagsToStringConverter.cs @@ -7,7 +7,7 @@ namespace Companion.Converters; public class TagsToStringConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is ObservableCollection tags) { @@ -16,8 +16,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn return string.Empty; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Companion/Events/AppMessageEvent.cs b/Companion/Events/AppMessageEvent.cs index cb92c08..5dd10c9 100644 --- a/Companion/Events/AppMessageEvent.cs +++ b/Companion/Events/AppMessageEvent.cs @@ -18,7 +18,7 @@ public class AppMessage public string? Status { get => _status; - set => _status = value; + set => _status = value ?? string.Empty; } public DeviceConfig DeviceConfig { get; set; } = DeviceConfig.Instance; @@ -31,4 +31,4 @@ public override string ToString() return $"{nameof(Message)}: {Message}, {nameof(Status)}: {Status}, " + $"{nameof(DeviceConfig)}: {DeviceConfig}, {nameof(CanConnect)}: {CanConnect}"; } -} \ No newline at end of file +} diff --git a/Companion/Events/RadxaContentUpdateEvent.cs b/Companion/Events/RadxaContentUpdateEvent.cs index 1e12551..5f67081 100644 --- a/Companion/Events/RadxaContentUpdateEvent.cs +++ b/Companion/Events/RadxaContentUpdateEvent.cs @@ -8,11 +8,11 @@ public class RadxaContentUpdateChangeEvent : PubSubEvent /// Gets or sets the event aggregator for device events /// - public IEventAggregator EventAggregator { get; set; } + public IEventAggregator? EventAggregator { get; set; } /// /// Gets the singleton instance of DeviceConfig @@ -209,4 +209,4 @@ protected bool SetField(ref T field, T value, [CallerMemberName] string? prop return true; } #endregion -} \ No newline at end of file +} diff --git a/Companion/Models/OpenIPC.cs b/Companion/Models/OpenIPC.cs index 0b50cb8..4b14f2d 100644 --- a/Companion/Models/OpenIPC.cs +++ b/Companion/Models/OpenIPC.cs @@ -47,11 +47,11 @@ public enum FileType public const string RemoteTempFolder = "/tmp"; public const string RemoteWifiBroadcastBinFileLoc = "/usr/bin/wifibroadcast"; - public static string DroneKeyPath; - public static string GsKeyPath; + public static string DroneKeyPath = string.Empty; + public static string GsKeyPath = string.Empty; - public static string LocalFirmwareFolder; - public static string LocalBackUpFolder; + public static string LocalFirmwareFolder = string.Empty; + public static string LocalBackUpFolder = string.Empty; public static string DeviceUsername = "root"; @@ -61,12 +61,12 @@ static OpenIPC() } // Expose configPath and configDirectory as public static properties - public static string AppDataConfigDirectory { get; private set; } + public static string AppDataConfigDirectory { get; private set; } = string.Empty; - public static string LocalTempFolder { get; private set; } - public static string AppDataConfigPath { get; private set; } + public static string LocalTempFolder { get; private set; } = string.Empty; + public static string AppDataConfigPath { get; private set; } = string.Empty; - public static string DeviceSettingsConfigPath { get; private set; } + public static string DeviceSettingsConfigPath { get; private set; } = string.Empty; public static string GetBinariesPath() { diff --git a/Companion/Models/Presets/FileModification.cs b/Companion/Models/Presets/FileModification.cs index 9c3a875..413bc0f 100644 --- a/Companion/Models/Presets/FileModification.cs +++ b/Companion/Models/Presets/FileModification.cs @@ -5,11 +5,11 @@ namespace Companion.Models.Presets; public class FileModification { - public string FileName { get; set; } + public string FileName { get; set; } = string.Empty; public ObservableCollection> Changes { get; set; } public FileModification() { Changes = new ObservableCollection>(); } -} \ No newline at end of file +} diff --git a/Companion/Models/Presets/GitHubFile.cs b/Companion/Models/Presets/GitHubFile.cs index 1155b7b..3c5cec6 100644 --- a/Companion/Models/Presets/GitHubFile.cs +++ b/Companion/Models/Presets/GitHubFile.cs @@ -7,17 +7,17 @@ public class GitHubFile /// /// The name of the file (e.g., "preset-config.yaml"). /// - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// /// The relative path of the file in the repository (e.g., "presets/preset-config.yaml"). /// - public string Path { get; set; } + public string Path { get; set; } = string.Empty; /// /// The URL to directly download the file content. /// - public string DownloadUrl { get; set; } + public string DownloadUrl { get; set; } = string.Empty; -} \ No newline at end of file +} diff --git a/Companion/Models/Presets/Preset.cs b/Companion/Models/Presets/Preset.cs index 2718e99..42a51b3 100644 --- a/Companion/Models/Presets/Preset.cs +++ b/Companion/Models/Presets/Preset.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; @@ -58,9 +59,13 @@ public static Preset LoadFromFile(string configPath) var yamlContent = File.ReadAllText(configPath); var preset = deserializer.Deserialize(yamlContent); + if (preset == null) + { + throw new InvalidOperationException("Failed to deserialize preset configuration."); + } // Set the folder path to the directory containing the config file - preset.FolderPath = System.IO.Path.GetDirectoryName(configPath); + preset.FolderPath = System.IO.Path.GetDirectoryName(configPath) ?? string.Empty; // Initialize FileModifications from Files dictionary preset.InitializeFileModifications(); @@ -139,7 +144,7 @@ public void SaveToFile(string filePath) File.WriteAllText(filePath, yaml); // Update the folder path - FolderPath = System.IO.Path.GetDirectoryName(filePath); + FolderPath = System.IO.Path.GetDirectoryName(filePath) ?? string.Empty; } /// @@ -161,4 +166,4 @@ private void SyncFileModificationsToFiles() Files[modification.FileName] = changes; } } -} \ No newline at end of file +} diff --git a/Companion/Models/Presets/PresetIndex.cs b/Companion/Models/Presets/PresetIndex.cs index 12ef1e2..1eb95b8 100644 --- a/Companion/Models/Presets/PresetIndex.cs +++ b/Companion/Models/Presets/PresetIndex.cs @@ -14,7 +14,7 @@ public class PresetIndex /// Version of the preset index format /// [YamlMember(Alias = "version")] - public string Version { get; set; } + public string Version { get; set; } = string.Empty; /// /// Timestamp of when the index was last updated @@ -34,4 +34,3 @@ public class PresetIndex [YamlMember(Alias = "presets")] public List Presets { get; set; } = new(); } - diff --git a/Companion/Models/Presets/RepositorySettings.cs b/Companion/Models/Presets/RepositorySettings.cs index 4a39738..51786be 100644 --- a/Companion/Models/Presets/RepositorySettings.cs +++ b/Companion/Models/Presets/RepositorySettings.cs @@ -8,20 +8,20 @@ public class RepositorySettings /// /// The full URL of the repository /// - public string Url { get; set; } + public string Url { get; set; } = string.Empty; /// /// The branch to use for fetching presets /// - public string Branch { get; set; } + public string Branch { get; set; } = string.Empty; /// /// Optional description of the repository /// - public string Description { get; set; } + public string Description { get; set; } = string.Empty; /// /// Indicates whether the repository is active /// public bool IsActive { get; set; } = true; -} \ No newline at end of file +} diff --git a/Companion/Models/WfbConfig.cs b/Companion/Models/WfbConfig.cs index 59e8caf..f118dbe 100644 --- a/Companion/Models/WfbConfig.cs +++ b/Companion/Models/WfbConfig.cs @@ -2,10 +2,10 @@ namespace Companion.Models; public class WfbConfig { - public string Unit { get; set; } - public string Wlan { get; set; } - public string Region { get; set; } - public string Channel { get; set; } + public string Unit { get; set; } = string.Empty; + public string Wlan { get; set; } = string.Empty; + public string Region { get; set; } = string.Empty; + public string Channel { get; set; } = string.Empty; public int TxPower { get; set; } public int DriverTxPowerOverride { get; set; } public int Bandwidth { get; set; } @@ -16,9 +16,9 @@ public class WfbConfig public long LinkId { get; set; } public int UdpPort { get; set; } public int RcvBuf { get; set; } - public string FrameType { get; set; } + public string FrameType { get; set; } = string.Empty; public int FecK { get; set; } public int FecN { get; set; } public int PoolTimeout { get; set; } - public string GuardInterval { get; set; } -} \ No newline at end of file + public string GuardInterval { get; set; } = string.Empty; +} diff --git a/Companion/Services/GitHubService.cs b/Companion/Services/GitHubService.cs index 123927e..3bc77ba 100644 --- a/Companion/Services/GitHubService.cs +++ b/Companion/Services/GitHubService.cs @@ -21,10 +21,10 @@ public GitHubService(IMemoryCache cache, HttpClient httpClient, ILogger logger) _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); } - public async Task GetGitHubDataAsync(string url) + public async Task GetGitHubDataAsync(string url) { var cacheKey = $"GitHubData::{url}"; - if (_cache.TryGetValue(cacheKey, out string cachedData)) + if (_cache.TryGetValue(cacheKey, out string? cachedData) && !string.IsNullOrEmpty(cachedData)) { _logger.Information("GitHub API data retrieved from cache."); return cachedData; @@ -50,7 +50,7 @@ public async Task GetGitHubDataAsync(string url) { // Handle API errors gracefully (log, throw, etc.) _logger.Error($"Error calling GitHub API: {ex.Message}"); - return null; // Or throw the exception, depending on your needs + return null; } } } diff --git a/Companion/Services/GlobalSettingsService.cs b/Companion/Services/GlobalSettingsService.cs index dfeb567..dafa9ce 100644 --- a/Companion/Services/GlobalSettingsService.cs +++ b/Companion/Services/GlobalSettingsService.cs @@ -48,7 +48,8 @@ private async Task CheckWfbYamlSupport(CancellationToken cancellationToken) { var cmdResult = await GetIsWfbYamlSupported(cancellationToken); - IsWfbYamlEnabled = bool.TryParse(Utilities.RemoveLastChar(cmdResult?.Result), out var result) && result; + var rawResult = cmdResult?.Result ?? string.Empty; + IsWfbYamlEnabled = bool.TryParse(Utilities.RemoveLastChar(rawResult), out var result) && result; _logger.Debug($"WFB YAML support status: {IsWfbYamlEnabled}"); } @@ -67,4 +68,4 @@ private async Task CheckWfbYamlSupport(CancellationToken cancellationToken) command, cancellationToken); } - } \ No newline at end of file + } diff --git a/Companion/Services/IGitHubService.cs b/Companion/Services/IGitHubService.cs index e707085..bc573c0 100644 --- a/Companion/Services/IGitHubService.cs +++ b/Companion/Services/IGitHubService.cs @@ -4,5 +4,5 @@ namespace Companion.Services; public interface IGitHubService { - Task GetGitHubDataAsync(string url); -} \ No newline at end of file + Task GetGitHubDataAsync(string url); +} diff --git a/Companion/Services/INetworkCommands.cs b/Companion/Services/INetworkCommands.cs index 56737f8..0e6d25d 100644 --- a/Companion/Services/INetworkCommands.cs +++ b/Companion/Services/INetworkCommands.cs @@ -5,7 +5,7 @@ namespace Companion.Services; public interface INetworkCommands { - extern Task Ping(DeviceConfig deviceConfig); + Task Ping(DeviceConfig deviceConfig); Task Run(DeviceConfig deviceConfig, string command); -} \ No newline at end of file +} diff --git a/Companion/Services/ISshClientService.cs b/Companion/Services/ISshClientService.cs index b8f1ad5..a40b0a6 100644 --- a/Companion/Services/ISshClientService.cs +++ b/Companion/Services/ISshClientService.cs @@ -9,7 +9,7 @@ namespace Companion.Services; public interface ISshClientService { // Executes a command on the remote device and returns its response - Task ExecuteCommandWithResponseAsync(DeviceConfig deviceConfig, string command, + Task ExecuteCommandWithResponseAsync(DeviceConfig deviceConfig, string command, CancellationToken cancellationToken); @@ -56,5 +56,5 @@ Task ExecuteCommandWithProgressAsync( Action updateProgress, CancellationToken cancellationToken = default, TimeSpan? timeout = null, - Func isCommandComplete = null); -} \ No newline at end of file + Func? isCommandComplete = null); +} diff --git a/Companion/Services/MessageBoxService.cs b/Companion/Services/MessageBoxService.cs index 6b040b5..3b2e2c2 100644 --- a/Companion/Services/MessageBoxService.cs +++ b/Companion/Services/MessageBoxService.cs @@ -51,7 +51,12 @@ public async Task ShowMessageBoxWithFolderLink(string title, strin try { // Get the directory path from the file path - string folderPath = Path.GetDirectoryName(filePath); + var folderPath = Path.GetDirectoryName(filePath); + if (string.IsNullOrWhiteSpace(folderPath)) + { + await ShowMessageBox("Error", "Could not determine the containing folder."); + return result; + } // Open the folder in the default file explorer if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/Companion/Services/NetworkCommands.cs b/Companion/Services/NetworkCommands.cs index 7019ee0..cfc6fc5 100644 --- a/Companion/Services/NetworkCommands.cs +++ b/Companion/Services/NetworkCommands.cs @@ -21,8 +21,10 @@ public Task Run(DeviceConfig deviceConfig, string command) throw new NotImplementedException(); } - public Task Ping(string ipAddress) + public Task Ping(DeviceConfig deviceConfig) { + var ipAddress = deviceConfig.IpAddress; + // Send a ping request var reply = ping.Send(ipAddress); @@ -36,4 +38,4 @@ public Task Ping(string ipAddress) Log.Verbose($"Ping failed: {reply.Status}"); return Task.FromResult(false); } -} \ No newline at end of file +} diff --git a/Companion/Services/NetworkHelper.cs b/Companion/Services/NetworkHelper.cs index 06af7f3..a3916af 100644 --- a/Companion/Services/NetworkHelper.cs +++ b/Companion/Services/NetworkHelper.cs @@ -6,7 +6,7 @@ namespace Companion.Services; public class NetworkHelper { - public static string GetLocalIPAddress() + public static string? GetLocalIPAddress() { try { @@ -32,4 +32,4 @@ public static string GetLocalIPAddress() return null; } } -} \ No newline at end of file +} diff --git a/Companion/Services/PingService.cs b/Companion/Services/PingService.cs index d55b481..fa59b82 100644 --- a/Companion/Services/PingService.cs +++ b/Companion/Services/PingService.cs @@ -9,7 +9,7 @@ namespace Companion.Services; public class PingService { - private static PingService _instance; + private static PingService? _instance; private static readonly object _lock = new object(); private readonly SemaphoreSlim _pingSemaphore = new SemaphoreSlim(1, 1); @@ -35,7 +35,7 @@ public static PingService Instance(ILogger logger) } } } - return _instance; + return _instance!; } // Ping method with default timeout @@ -79,4 +79,4 @@ public void Dispose() { _pingSemaphore.Dispose(); } -} \ No newline at end of file +} diff --git a/Companion/Services/Presets/GitHubPresetService.cs b/Companion/Services/Presets/GitHubPresetService.cs index da22b50..8ef589a 100644 --- a/Companion/Services/Presets/GitHubPresetService.cs +++ b/Companion/Services/Presets/GitHubPresetService.cs @@ -220,7 +220,7 @@ private async Task DownloadAdditionalFilesAsync(string presetConfigYaml, string /// Local directory to save the file /// Optional custom URL for the file private async Task DownloadAdditionalFileAsync(string fileName, string baseRepoUrl, string localPresetDir, - string customUrl = null) + string? customUrl = null) { try { @@ -242,6 +242,7 @@ private async Task DownloadAdditionalFileAsync(string fileName, string baseRepoU } catch (HttpRequestException ex) { + _logger.Warning(ex, "Error downloading file {FileName}, trying binary download if applicable.", fileName); // Handle binary files differently if (IsBinaryFile(fileName)) { @@ -408,4 +409,4 @@ public async Task> SyncRepositoryPresetsAsync( return downloadedPresets; } -} \ No newline at end of file +} diff --git a/Companion/Services/Presets/PresetService.cs b/Companion/Services/Presets/PresetService.cs index 553cf06..cd3107d 100644 --- a/Companion/Services/Presets/PresetService.cs +++ b/Companion/Services/Presets/PresetService.cs @@ -15,6 +15,7 @@ namespace Companion.Services.Presets; /// /// Service for applying and managing presets /// +[Obsolete("Deprecated: PresetService is legacy and will be replaced by a newer preset workflow.")] public class PresetService : IPresetService { private readonly ILogger _logger; @@ -25,8 +26,8 @@ public class PresetService : IPresetService private readonly Dictionary _cachedFileContents = new(); // Add a field to track the active preset being applied - private Preset _activePreset; - private readonly string _presetsFolder; + private Preset? _activePreset; + private readonly string _presetsFolder = Path.Combine(Path.GetTempPath(), "OpenIPC Companion", "Presets"); // List of critical system files to ignore private readonly HashSet _criticalSystemFiles = new HashSet(StringComparer.OrdinalIgnoreCase) @@ -58,6 +59,7 @@ public PresetService( throw new ArgumentNullException(nameof(eventSubscriptionService)); _yamlConfigService = yamlConfigService ?? throw new ArgumentNullException(nameof(yamlConfigService)); _globalSettingsService = globalSettingsService; + Directory.CreateDirectory(_presetsFolder); } /// @@ -749,4 +751,4 @@ private void AddIfExists(Dictionary source, Dictionary /// with default values. /// - public static DeviceConfig? LoadSettings() + public static DeviceConfig LoadSettings() { - DeviceConfig deviceConfig; + DeviceConfig? deviceConfig; if (File.Exists(AppSettingFilename)) try @@ -80,4 +79,4 @@ public static void SaveSettings(DeviceConfig settings) var json = JsonConvert.SerializeObject(settings, Formatting.Indented); File.WriteAllText(AppSettingFilename, json); } -} \ No newline at end of file +} diff --git a/Companion/Services/SshClientService.cs b/Companion/Services/SshClientService.cs index d2ca07c..e7ee5d9 100644 --- a/Companion/Services/SshClientService.cs +++ b/Companion/Services/SshClientService.cs @@ -26,7 +26,7 @@ public SshClientService(IEventSubscriptionService eventSubscriptionService, ILog _eventSubscriptionService = eventSubscriptionService; } - public async Task ExecuteCommandWithResponseAsync(DeviceConfig deviceConfig, string command, + public async Task ExecuteCommandWithResponseAsync(DeviceConfig deviceConfig, string command, CancellationToken cancellationToken = default) { _logger.Debug($"Executing command: '{command}' on {deviceConfig.IpAddress}."); @@ -56,7 +56,7 @@ public async Task ExecuteCommandWithResponseAsync(DeviceConfig devic } } - public async Task ExecuteCommandAsync(DeviceConfig deviceConfig, string command) + public Task ExecuteCommandAsync(DeviceConfig deviceConfig, string command) { _logger.Debug($"Executing command: '{command}' on {deviceConfig.IpAddress}."); @@ -80,6 +80,8 @@ public async Task ExecuteCommandAsync(DeviceConfig deviceConfig, string command) client.Disconnect(); } } + + return Task.CompletedTask; } @@ -327,7 +329,7 @@ public async Task UploadBinaryAsync(DeviceConfig deviceConfig, string remoteDire else { var msgBox = MessageBoxManager.GetMessageBoxStandard("Error", $"File {fileName} not found."); - msgBox.ShowAsync(); + await msgBox.ShowAsync(); } } @@ -393,7 +395,7 @@ public async Task ExecuteCommandWithProgressAsync( Action updateProgress, CancellationToken cancellationToken = default, TimeSpan? timeout = null, - Func isCommandComplete = null) + Func? isCommandComplete = null) { timeout ??= TimeSpan.FromMinutes(2); // Default timeout isCommandComplete ??= output => output.Contains("Unconditional reboot") || output.Contains("Arguments written"); @@ -536,4 +538,4 @@ public void DownloadDirectoryRecursively(ScpClient client, string remoteDirector } } } -} \ No newline at end of file +} diff --git a/Companion/Services/UpdateChecker.cs b/Companion/Services/UpdateChecker.cs index 9d36140..54343e9 100644 --- a/Companion/Services/UpdateChecker.cs +++ b/Companion/Services/UpdateChecker.cs @@ -74,10 +74,10 @@ string NormalizeVersion(string version) public class UpdateInfo { - public string Version { get; set; } + public string Version { get; set; } = string.Empty; - [JsonProperty("release_notes")] public string ReleaseNotes { get; set; } + [JsonProperty("release_notes")] public string ReleaseNotes { get; set; } = string.Empty; - [JsonProperty("download_url")] public string DownloadUrl { get; set; } + [JsonProperty("download_url")] public string DownloadUrl { get; set; } = string.Empty; } } diff --git a/Companion/Services/WfbConfigParser.cs b/Companion/Services/WfbConfigParser.cs index db2f8fa..e6d40e5 100644 --- a/Companion/Services/WfbConfigParser.cs +++ b/Companion/Services/WfbConfigParser.cs @@ -8,10 +8,10 @@ namespace Companion.Services; public class WfbConfigParser { // Properties to store parsed values - public string Unit { get; private set; } - public string Wlan { get; private set; } - public string Region { get; private set; } - public string Channel { get; private set; } + public string Unit { get; private set; } = string.Empty; + public string Wlan { get; private set; } = string.Empty; + public string Region { get; private set; } = string.Empty; + public string Channel { get; private set; } = string.Empty; public int TxPower { get; private set; } public int DriverTxPowerOverride { get; private set; } public int Bandwidth { get; private set; } @@ -22,11 +22,11 @@ public class WfbConfigParser public long LinkId { get; private set; } public int UdpPort { get; private set; } public int RcvBuf { get; private set; } - public string FrameType { get; private set; } + public string FrameType { get; private set; } = string.Empty; public int FecK { get; private set; } public int FecN { get; private set; } public int PoolTimeout { get; private set; } - public string GuardInterval { get; private set; } + public string GuardInterval { get; private set; } = string.Empty; // Method to parse configuration from a string public void ParseConfigString(string configContent) @@ -64,16 +64,16 @@ public void ParseConfigString(string configContent) // Set properties based on parsed values configDict.TryGetValue("unit", out var unit); - Unit = unit; + Unit = unit ?? string.Empty; configDict.TryGetValue("wlan", out var wlan); - Wlan = wlan; + Wlan = wlan ?? string.Empty; configDict.TryGetValue("region", out var region); - Region = region; + Region = region ?? string.Empty; configDict.TryGetValue("channel", out var channel); - Channel = channel; + Channel = channel ?? string.Empty; if (configDict.TryGetValue("txpower", out var txPower)) TxPower = int.TryParse(txPower, out var value) ? value : 0; @@ -102,7 +102,7 @@ public void ParseConfigString(string configContent) if (configDict.TryGetValue("rcv_buf", out var rcvBuf)) RcvBuf = int.TryParse(rcvBuf, out var value) ? value : 0; configDict.TryGetValue("frame_type", out var frameType); - FrameType = frameType; + FrameType = frameType ?? string.Empty; if (configDict.TryGetValue("fec_k", out var fecK)) FecK = int.TryParse(fecK, out var value) ? value : 0; @@ -112,7 +112,7 @@ public void ParseConfigString(string configContent) PoolTimeout = int.TryParse(poolTimeout, out var value) ? value : 0; configDict.TryGetValue("guard_interval", out var guardInterval); - GuardInterval = guardInterval; + GuardInterval = guardInterval ?? string.Empty; // Log the parsed configuration Log.Debug("WFB Configuration Parsed Successfully:"); @@ -136,4 +136,4 @@ public void ParseConfigString(string configContent) Log.Debug($"PoolTimeout: {PoolTimeout}"); Log.Debug($"GuardInterval: {GuardInterval}"); } -} \ No newline at end of file +} diff --git a/Companion/Services/WfbGsConfigParser.cs b/Companion/Services/WfbGsConfigParser.cs index 0de7460..c6d83e4 100644 --- a/Companion/Services/WfbGsConfigParser.cs +++ b/Companion/Services/WfbGsConfigParser.cs @@ -8,10 +8,10 @@ namespace Companion.Services; public class WfbGsConfigParser : IWfbGsConfigParser { // Store the original configuration content - private string _originalConfigContent; + private string _originalConfigContent = string.Empty; // Properties to store parsed values - public string TxPower { get; set; } + public string TxPower { get; set; } = string.Empty; // Method to generate the updated configuration string while preserving comments public string GetUpdatedConfigString() @@ -94,4 +94,4 @@ public void ParseConfigString(string configContent) Log.Debug("WFB Configuration Parsed Successfully."); } -} \ No newline at end of file +} diff --git a/Companion/Services/WifiCardDetector.cs b/Companion/Services/WifiCardDetector.cs index 829c140..fba9fd0 100644 --- a/Companion/Services/WifiCardDetector.cs +++ b/Companion/Services/WifiCardDetector.cs @@ -8,7 +8,7 @@ namespace Companion.Services; public class WifiCardDetector { - public static string DetectWifiCard(string lsusbOutput) + public static string? DetectWifiCard(string lsusbOutput) { // Parse the lsusb output to extract device IDs. This is more robust than cutting by spaces. // Assumes each line in lsusbOutput contains "ID :" @@ -23,10 +23,11 @@ public static string DetectWifiCard(string lsusbOutput) return null; }) .Where(id => !string.IsNullOrEmpty(id)) //Remove nulls + .Select(id => id!) .Distinct() // Mimic the "sort | uniq" .ToList(); - string driver = null; // Initialize driver to null + string? driver = null; // Initialize driver to null foreach (string card in deviceIds) { diff --git a/Companion/Services/WifiConfigParser.cs b/Companion/Services/WifiConfigParser.cs index b156769..760b2e0 100644 --- a/Companion/Services/WifiConfigParser.cs +++ b/Companion/Services/WifiConfigParser.cs @@ -11,13 +11,13 @@ public class WifiConfigParser private readonly Dictionary _configDict = new(); // Store the original configuration content - private string _originalConfigContent; + private string _originalConfigContent = string.Empty; // Properties to store parsed values public int WifiChannel { get; set; } - public string WifiRegion { get; set; } - public string GsMavlinkPeer { get; set; } - public string GsVideoPeer { get; set; } + public string WifiRegion { get; set; } = string.Empty; + public string GsMavlinkPeer { get; set; } = string.Empty; + public string GsVideoPeer { get; set; } = string.Empty; // Method to parse configuration from a string public void ParseConfigString(string configContent) @@ -35,7 +35,7 @@ public void ParseConfigString(string configContent) var keyValueRegex = new Regex(@"^\s*(\w+)\s*=\s*['""]?(.*?)['""]?\s*(?:#.*)?$"); var sectionRegex = new Regex(@"^\s*\[.*\]\s*$"); - string currentSection = null; + string? currentSection = null; foreach (var line in lines) { @@ -97,7 +97,7 @@ public string GetUpdatedConfigString() var updatedConfig = new StringBuilder(); var keyValueRegex = new Regex(@"^\s*(\w+)\s*=\s*['""]?(.*?)['""]?\s*(#.*)?$"); var sectionRegex = new Regex(@"^\s*\[.*\]\s*$"); - string currentSection = null; + string? currentSection = null; foreach (var line in lines) { @@ -149,4 +149,4 @@ public string GetUpdatedConfigString() Log.Debug($"Updated Config:\n{result}"); return result; } -} \ No newline at end of file +} diff --git a/Companion/ViewModels/AdvancedTabViewModel.cs b/Companion/ViewModels/AdvancedTabViewModel.cs index db814d2..bc1db88 100644 --- a/Companion/ViewModels/AdvancedTabViewModel.cs +++ b/Companion/ViewModels/AdvancedTabViewModel.cs @@ -8,6 +8,10 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -340,51 +344,8 @@ private async Task GenerateSystemReportAsync() // Let the user know the report was generated UpdateUIMessage("System report generated. Select a location to save it..."); - // Now prompt the user for a save location - // Use Avalonia's SaveFileDialog to let the user pick a location - var saveFileDialog = new Avalonia.Controls.SaveFileDialog - { - Title = "Save System Report", - DefaultExtension = "txt", - InitialFileName = $"system_report_{DateTime.Now:yyyyMMdd_HHmmss}.txt", - Filters = new List - { - new Avalonia.Controls.FileDialogFilter - { - Name = "Text Files", - Extensions = new List { "txt" } - }, - new Avalonia.Controls.FileDialogFilter - { - Name = "All Files", - Extensions = new List { "*" } - } - } - }; - - // Get the main window to show the dialog - var mainWindow = - Avalonia.Application.Current.ApplicationLifetime is - Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop - ? desktop.MainWindow - : null; - - if (mainWindow == null) - { - UpdateUIMessage("Could not show save dialog. Using default location."); - // Save to a default location if we can't show the dialog - string defaultPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - $"system_report_{DateTime.Now:yyyyMMdd_HHmmss}.txt"); - - File.WriteAllText(defaultPath, reportContent); - UpdateUIMessage($"System report saved to: {defaultPath}"); - return; - } - - // Show the save dialog and get the selected path - string filePath = await saveFileDialog.ShowAsync(mainWindow); - + var suggestedFileName = $"system_report_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + var filePath = await PickSavePathAsync("Save System Report", suggestedFileName); if (string.IsNullOrEmpty(filePath)) { // User canceled the save operation @@ -440,50 +401,8 @@ private async Task ViewSystemLogsAsync() // Let the user know the logs were retrieved UpdateUIMessage("System logs retrieved. Select a location to save them..."); - // Prompt the user for a save location using SaveFileDialog - var saveFileDialog = new Avalonia.Controls.SaveFileDialog - { - Title = "Save System Logs", - DefaultExtension = "txt", - InitialFileName = $"system_logs_{DateTime.Now:yyyyMMdd_HHmmss}.txt", - Filters = new List - { - new Avalonia.Controls.FileDialogFilter - { - Name = "Text Files", - Extensions = new List { "txt" } - }, - new Avalonia.Controls.FileDialogFilter - { - Name = "All Files", - Extensions = new List { "*" } - } - } - }; - - // Get the main window to show the dialog - var mainWindow = - Avalonia.Application.Current.ApplicationLifetime is - Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop - ? desktop.MainWindow - : null; - - if (mainWindow == null) - { - UpdateUIMessage("Could not show save dialog. Using default location."); - // Save to a default location if we can't show the dialog - string defaultPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - $"system_logs_{DateTime.Now:yyyyMMdd_HHmmss}.txt"); - - File.WriteAllText(defaultPath, logContent); - UpdateUIMessage($"System logs saved to: {defaultPath}"); - return; - } - - // Show the save dialog and get the selected path - string filePath = await saveFileDialog.ShowAsync(mainWindow); - + var suggestedFileName = $"system_logs_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + var filePath = await PickSavePathAsync("Save System Logs", suggestedFileName); if (string.IsNullOrEmpty(filePath)) { // User canceled the save operation @@ -548,50 +467,8 @@ private async Task NetworkDiagnosticsAsync() // Let the user know the diagnostics were completed UpdateUIMessage("Network diagnostics completed. Select a location to save the results..."); - // Prompt the user for a save location using SaveFileDialog - var saveFileDialog = new Avalonia.Controls.SaveFileDialog - { - Title = "Save Network Diagnostics", - DefaultExtension = "txt", - InitialFileName = $"network_diagnostics_{DateTime.Now:yyyyMMdd_HHmmss}.txt", - Filters = new List - { - new Avalonia.Controls.FileDialogFilter - { - Name = "Text Files", - Extensions = new List { "txt" } - }, - new Avalonia.Controls.FileDialogFilter - { - Name = "All Files", - Extensions = new List { "*" } - } - } - }; - - // Get the main window to show the dialog - var mainWindow = - Avalonia.Application.Current.ApplicationLifetime is - Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop - ? desktop.MainWindow - : null; - - if (mainWindow == null) - { - UpdateUIMessage("Could not show save dialog. Using default location."); - // Save to a default location if we can't show the dialog - string defaultPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - $"network_diagnostics_{DateTime.Now:yyyyMMdd_HHmmss}.txt"); - - File.WriteAllText(defaultPath, diagnosticsContent); - UpdateUIMessage($"Network diagnostics saved to: {defaultPath}"); - return; - } - - // Show the save dialog and get the selected path - string filePath = await saveFileDialog.ShowAsync(mainWindow); - + var suggestedFileName = $"network_diagnostics_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; + var filePath = await PickSavePathAsync("Save Network Diagnostics", suggestedFileName); if (string.IsNullOrEmpty(filePath)) { // User canceled the save operation @@ -613,6 +490,44 @@ Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime d #endregion + private static Window? GetMainWindow() + { + return (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow; + } + + private async Task PickSavePathAsync(string title, string suggestedFileName) + { + var mainWindow = GetMainWindow(); + if (mainWindow?.StorageProvider == null) + { + UpdateUIMessage("Could not show save dialog. Using default location."); + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + suggestedFileName); + } + + var options = new FilePickerSaveOptions + { + Title = title, + SuggestedFileName = suggestedFileName, + DefaultExtension = "txt", + FileTypeChoices = new List + { + new("Text Files") + { + Patterns = new List { "*.txt" } + }, + new("All Files") + { + Patterns = new List { "*" } + } + } + }; + + var file = await mainWindow.StorageProvider.SaveFilePickerAsync(options); + return file?.TryGetLocalPath(); + } + #region Event Handlers private void OnAppMessage(AppMessage message) @@ -647,4 +562,4 @@ private void OnAppMessage(AppMessage message) } #endregion -} \ No newline at end of file +} diff --git a/Companion/ViewModels/CameraSettingsTabViewModel.cs b/Companion/ViewModels/CameraSettingsTabViewModel.cs index 09a249a..7f809e9 100644 --- a/Companion/ViewModels/CameraSettingsTabViewModel.cs +++ b/Companion/ViewModels/CameraSettingsTabViewModel.cs @@ -25,27 +25,27 @@ public partial class CameraSettingsTabViewModel : ViewModelBase #endregion #region Observable Collections - [ObservableProperty] private ObservableCollection _resolution; - [ObservableProperty] private ObservableCollection _fps; - [ObservableProperty] private ObservableCollection _codec; - [ObservableProperty] private ObservableCollection _bitrate; - [ObservableProperty] private ObservableCollection _exposure; - [ObservableProperty] private ObservableCollection _contrast; - [ObservableProperty] private ObservableCollection _hue; - [ObservableProperty] private ObservableCollection _saturation; - [ObservableProperty] private ObservableCollection _luminance; - [ObservableProperty] private ObservableCollection _flip; - [ObservableProperty] private ObservableCollection _mirror; + [ObservableProperty] private ObservableCollection _resolution = new(); + [ObservableProperty] private ObservableCollection _fps = new(); + [ObservableProperty] private ObservableCollection _codec = new(); + [ObservableProperty] private ObservableCollection _bitrate = new(); + [ObservableProperty] private ObservableCollection _exposure = new(); + [ObservableProperty] private ObservableCollection _contrast = new(); + [ObservableProperty] private ObservableCollection _hue = new(); + [ObservableProperty] private ObservableCollection _saturation = new(); + [ObservableProperty] private ObservableCollection _luminance = new(); + [ObservableProperty] private ObservableCollection _flip = new(); + [ObservableProperty] private ObservableCollection _mirror = new(); #endregion #region FPV Settings Collections - [ObservableProperty] private ObservableCollection _fpvEnabled; - [ObservableProperty] private ObservableCollection _fpvNoiseLevel; - [ObservableProperty] private ObservableCollection _fpvRoiQp; - [ObservableProperty] private ObservableCollection _fpvRefEnhance; - [ObservableProperty] private ObservableCollection _fpvRefPred; - [ObservableProperty] private ObservableCollection _fpvIntraLine; - [ObservableProperty] private ObservableCollection _fpvIntraQp; + [ObservableProperty] private ObservableCollection _fpvEnabled = new(); + [ObservableProperty] private ObservableCollection _fpvNoiseLevel = new(); + [ObservableProperty] private ObservableCollection _fpvRoiQp = new(); + [ObservableProperty] private ObservableCollection _fpvRefEnhance = new(); + [ObservableProperty] private ObservableCollection _fpvRefPred = new(); + [ObservableProperty] private ObservableCollection _fpvIntraLine = new(); + [ObservableProperty] private ObservableCollection _fpvIntraQp = new(); [ObservableProperty] private ObservableCollection _fpvRoiRectLeft = new() { "" }; [ObservableProperty] private ObservableCollection _fpvRoiRectTop = new() { "" }; [ObservableProperty] private ObservableCollection _fpvRoiRectHeight = new() { "" }; @@ -53,28 +53,28 @@ public partial class CameraSettingsTabViewModel : ViewModelBase #endregion #region Selected Values - [ObservableProperty] private string _selectedResolution; - [ObservableProperty] private string _selectedFps; - [ObservableProperty] private string _selectedCodec; - [ObservableProperty] private string _selectedBitrate; - [ObservableProperty] private string _selectedExposure; - [ObservableProperty] private string _selectedContrast; - [ObservableProperty] private string _selectedHue; - [ObservableProperty] private string _selectedSaturation; - [ObservableProperty] private string _selectedLuminance; - [ObservableProperty] private string _selectedFlip; - [ObservableProperty] private string _selectedMirror; + [ObservableProperty] private string _selectedResolution = string.Empty; + [ObservableProperty] private string _selectedFps = string.Empty; + [ObservableProperty] private string _selectedCodec = string.Empty; + [ObservableProperty] private string _selectedBitrate = string.Empty; + [ObservableProperty] private string _selectedExposure = string.Empty; + [ObservableProperty] private string _selectedContrast = string.Empty; + [ObservableProperty] private string _selectedHue = string.Empty; + [ObservableProperty] private string _selectedSaturation = string.Empty; + [ObservableProperty] private string _selectedLuminance = string.Empty; + [ObservableProperty] private string _selectedFlip = string.Empty; + [ObservableProperty] private string _selectedMirror = string.Empty; #endregion #region FPV Selected Values - [ObservableProperty] private string _selectedFpvEnabled; - [ObservableProperty] private string _selectedFpvNoiseLevel; - [ObservableProperty] private string _selectedFpvRoiQp; - [ObservableProperty] private string _selectedFpvRefEnhance; - [ObservableProperty] private string _selectedFpvRefPred; - [ObservableProperty] private string _selectedFpvIntraLine; - [ObservableProperty] private string _selectedFpvIntraQp; - [ObservableProperty] private string _combinedFpvRoiRectValue; + [ObservableProperty] private string _selectedFpvEnabled = string.Empty; + [ObservableProperty] private string _selectedFpvNoiseLevel = string.Empty; + [ObservableProperty] private string _selectedFpvRoiQp = string.Empty; + [ObservableProperty] private string _selectedFpvRefEnhance = string.Empty; + [ObservableProperty] private string _selectedFpvRefPred = string.Empty; + [ObservableProperty] private string _selectedFpvIntraLine = string.Empty; + [ObservableProperty] private string _selectedFpvIntraQp = string.Empty; + [ObservableProperty] private string _combinedFpvRoiRectValue = string.Empty; #endregion #region Other Properties @@ -437,7 +437,7 @@ await SshClientService.UploadFileStringAsync( OpenIPC.MajesticFileLoc, updatedYamlContent); - SshClientService.ExecuteCommandAsync( + await SshClientService.ExecuteCommandAsync( DeviceConfig.Instance, DeviceCommands.MajesticRestartCommand); diff --git a/Companion/ViewModels/ConnectControlsViewModel.cs b/Companion/ViewModels/ConnectControlsViewModel.cs index dfc455d..974f4a2 100644 --- a/Companion/ViewModels/ConnectControlsViewModel.cs +++ b/Companion/ViewModels/ConnectControlsViewModel.cs @@ -27,8 +27,7 @@ namespace Companion.ViewModels; public partial class ConnectControlsViewModel : ViewModelBase { #region Private Fields - private readonly CancellationTokenSource? _cancellationTokenSourc; - private readonly DispatcherTimer _dispatcherTimer; + private readonly DispatcherTimer _dispatcherTimer = new(); private readonly IEventSubscriptionService _eventSubscriptionService; private readonly SolidColorBrush _offlineColorBrush = new(Colors.Red); private readonly SolidColorBrush _onlineColorBrush = new(Colors.Green); @@ -36,20 +35,20 @@ public partial class ConnectControlsViewModel : ViewModelBase private readonly TimeSpan _pingInterval = TimeSpan.FromSeconds(1); private readonly TimeSpan _pingTimeout = TimeSpan.FromMilliseconds(500); private bool _canConnect; - private DeviceConfig _deviceConfig; + private DeviceConfig _deviceConfig = DeviceConfig.Instance; private SolidColorBrush _pingStatusColor = new(Colors.Red); - private DispatcherTimer _pingTimer; + private DispatcherTimer _pingTimer = new(); private DeviceType _selectedDeviceType; #endregion #region Observable Properties - [ObservableProperty] private string _ipAddress; - [ObservableProperty] private string _password; + [ObservableProperty] private string _ipAddress = string.Empty; + [ObservableProperty] private string _password = string.Empty; [ObservableProperty] private int _port = 22; // Add these properties to your ConnectControlsViewModel class [ObservableProperty] private ObservableCollection _cachedIpAddresses = new(); - [ObservableProperty] private string _selectedIpAddress; + [ObservableProperty] private string _selectedIpAddress = string.Empty; #endregion @@ -111,7 +110,7 @@ public Orientation Orientation #endregion #region Commands - public ICommand ConnectCommand { get; private set; } + public ICommand ConnectCommand { get; private set; } = new RelayCommand(() => { }); #endregion #region Constructor @@ -252,7 +251,7 @@ private async void PingTimer_Tick(object? sender, EventArgs e) } catch (Exception ex) { - Logger.Error( "Error occurred during ping"); + Logger.Error(ex, "Error occurred during ping"); PingStatusColor = _offlineColorBrush; } } @@ -557,4 +556,4 @@ private void SaveConfig() SettingsManager.SaveSettings(_deviceConfig); } #endregion -} \ No newline at end of file +} diff --git a/Companion/ViewModels/FirmwareTabViewModel.cs b/Companion/ViewModels/FirmwareTabViewModel.cs index 0bdc186..2c60e01 100644 --- a/Companion/ViewModels/FirmwareTabViewModel.cs +++ b/Companion/ViewModels/FirmwareTabViewModel.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Controls; +using Avalonia.Platform.Storage; using Avalonia.Media; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -46,8 +47,8 @@ private enum SysupgradePhase private readonly HttpClient _httpClient; private readonly SysUpgradeService _sysupgradeService; - private CancellationTokenSource _cancellationTokenSource; - private FirmwareData _firmwareData; + private CancellationTokenSource? _cancellationTokenSource; + private FirmwareData _firmwareData = new() { Manufacturers = new ObservableCollection() }; private readonly IGitHubService _gitHubService; private bool _bInitializedCommands = false; private bool _bRecursionSelectGuard = false; @@ -92,17 +93,17 @@ private enum SysupgradePhase [ObservableProperty] private bool _isManufacturerDeviceFirmwareComboSelected; [ObservableProperty] private bool _canDownloadFirmware; [ObservableProperty] private bool _isManualUpdateEnabled; - [ObservableProperty] private string _selectedDevice; - [ObservableProperty] private string _selectedFirmware; - [ObservableProperty] private string _selectedFirmwareBySoc; - [ObservableProperty] private string _selectedManufacturer; - [ObservableProperty] private string _manualLocalFirmwarePackageFile; + [ObservableProperty] private string _selectedDevice = string.Empty; + [ObservableProperty] private string _selectedFirmware = string.Empty; + [ObservableProperty] private string _selectedFirmwareBySoc = string.Empty; + [ObservableProperty] private string _selectedManufacturer = string.Empty; + [ObservableProperty] private string _manualLocalFirmwarePackageFile = string.Empty; [ObservableProperty] private int _progressValue; [ObservableProperty] private IBrush _progressBarBrush = new SolidColorBrush(Color.Parse("#4C61D8")); - [ObservableProperty] private string _selectedBootloader; + [ObservableProperty] private string _selectedBootloader = string.Empty; [ObservableProperty] private bool _bootloaderConfirmed; [ObservableProperty] private int _bootloaderProgressValue; - [ObservableProperty] private string _bootloaderProgressText; + [ObservableProperty] private string _bootloaderProgressText = string.Empty; [ObservableProperty] private bool _bootloaderInProgress; [ObservableProperty] private string _bootloaderStorageTypeLabel = "Detected storage: Unknown"; [ObservableProperty] private bool _firmwareUpgradeInProgress; @@ -160,11 +161,11 @@ private enum SysupgradePhase #region Commands - public IAsyncRelayCommand SelectLocalFirmwarePackageCommand { get; set; } - public IAsyncRelayCommand PerformFirmwareUpgradeAsyncCommand { get; set; } - public IAsyncRelayCommand ReplaceBootloaderAsyncCommand { get; set; } - public ICommand ClearFormCommand { get; set; } - public IAsyncRelayCommand DownloadFirmwareAsyncCommand { get; set; } + public IAsyncRelayCommand SelectLocalFirmwarePackageCommand { get; set; } = new AsyncRelayCommand(_ => Task.CompletedTask); + public IAsyncRelayCommand PerformFirmwareUpgradeAsyncCommand { get; set; } = new AsyncRelayCommand(() => Task.CompletedTask); + public IAsyncRelayCommand ReplaceBootloaderAsyncCommand { get; set; } = new AsyncRelayCommand(() => Task.CompletedTask); + public ICommand ClearFormCommand { get; set; } = new RelayCommand(() => { }); + public IAsyncRelayCommand DownloadFirmwareAsyncCommand { get; set; } = new AsyncRelayCommand(() => Task.CompletedTask); #endregion @@ -185,6 +186,7 @@ public FirmwareTabViewModel( _messageBoxService = messageBoxService; _httpClient = new HttpClient(); _sysupgradeService = new SysUpgradeService(sshClientService, logger); + _cancellationTokenSource = new CancellationTokenSource(); _bInitializedCommands = false; _bRecursionSelectGuard = false; InitializeProperties(); @@ -701,7 +703,7 @@ private async Task FetchFirmwareListAsync() catch (Exception ex) { Logger.Error(ex, "Error fetching firmware list."); - return null; + return new FirmwareData(); } } @@ -773,13 +775,15 @@ private async Task> GetBootloaderFilenamesAsync() if (string.IsNullOrEmpty(response)) return Enumerable.Empty(); - var releaseData = JObject.Parse(response.ToString()); + var releaseData = JObject.Parse(response); var assets = releaseData["assets"]; - return assets?.Select(asset => asset["name"]?.ToString()).Where(name => !string.IsNullOrEmpty(name)) ?? + return assets?.Select(asset => asset["name"]?.ToString()) + .Where(name => !string.IsNullOrEmpty(name)) + .Select(name => name!) ?? Enumerable.Empty(); } - private static IEnumerable FilterBootloadersByStorage(IEnumerable filenames, string storageType) + private static IEnumerable FilterBootloadersByStorage(IEnumerable filenames, string? storageType) { var bootloaderFiles = filenames .Where(name => name.Contains("u-boot", StringComparison.OrdinalIgnoreCase) && @@ -810,7 +814,7 @@ private static bool IsBootloaderForStorage(string filename, string storageType) : (lower.Contains("-nand") || lower.Contains("_nand")); } - private async Task GetDeviceStorageTypeAsync() + private async Task GetDeviceStorageTypeAsync() { if (!IsConnected) return null; @@ -839,7 +843,7 @@ private async Task GetDeviceStorageTypeAsync() return null; } - private void UpdateBootloaderStorageTypeLabel(string storageType) + private void UpdateBootloaderStorageTypeLabel(string? storageType) { var label = "Detected storage: Unknown"; if (string.Equals(storageType, "nor", StringComparison.OrdinalIgnoreCase)) @@ -1062,10 +1066,16 @@ private FirmwarePackage CreateAndAddFirmwarePackage(Manufacturer manufacturer, D return firmwarePackage; } - private async Task DownloadFirmwareAsync(string url = null, string filename = null) + private async Task DownloadFirmwareAsync(string? url = null, string? filename = null) { try { + if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(filename)) + { + Logger.Warning("Download firmware skipped: missing url or filename."); + return null; + } + var filePath = Path.Combine(Path.GetTempPath(), filename); Logger.Information($"Downloading firmware from {url} to {filePath}"); @@ -1092,7 +1102,7 @@ private async Task DownloadFirmwareAsync(string url = null, string filen } } - private async Task DownloadBootloaderAsync(string filename) + private async Task DownloadBootloaderAsync(string filename) { try { @@ -1155,7 +1165,7 @@ private void UpdateBootloaders(IEnumerable filenames) Bootloaders.Add(bootloader); if (Bootloaders.Any() && string.IsNullOrEmpty(SelectedBootloader)) - SelectedBootloader = Bootloaders.FirstOrDefault(); + SelectedBootloader = Bootloaders.FirstOrDefault() ?? string.Empty; Logger.Information($"Loaded {Bootloaders.Count} bootloader entries."); } @@ -1202,9 +1212,15 @@ private string UncompressFirmware(string tgzFilePath) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { + if (string.IsNullOrEmpty(entry.Key)) + continue; + var destinationPath = Path.Combine(tempDir, entry.Key); var directoryPath = Path.GetDirectoryName(destinationPath); + if (string.IsNullOrEmpty(directoryPath)) + continue; + if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath); @@ -1458,7 +1474,7 @@ private async Task PerformFirmwareUpgradeFromSocAsync() return; } - string firmwareFilePath = await DownloadFirmwareAsync(downloadUrl, filename); + string? firmwareFilePath = await DownloadFirmwareAsync(downloadUrl, filename); if (!string.IsNullOrEmpty(firmwareFilePath)) { await UpgradeFirmwareFromFileAsync(firmwareFilePath); @@ -1514,7 +1530,7 @@ private async Task PerformFirmwareUpgradeFromDropdownAsync() return; } - string firmwareFilePath = await DownloadFirmwareAsync(downloadUrl, filename); + string? firmwareFilePath = await DownloadFirmwareAsync(downloadUrl, filename); if (!string.IsNullOrEmpty(firmwareFilePath)) { await UpgradeFirmwareFromFileAsync(firmwareFilePath); @@ -1756,25 +1772,29 @@ await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => } } - public async Task SelectLocalFirmwarePackage(Window window) + public async Task SelectLocalFirmwarePackage(Window? window) { - var dialog = new OpenFileDialog + if (window is null) + return; + + if (window.StorageProvider == null) + return; + + var options = new FilePickerOpenOptions { Title = "Select a File", - Filters = new List + FileTypeFilter = new List { - new() { Name = "Compressed", Extensions = { "tgz" } }, - new() { Name = "Bin Files", Extensions = { "bin" } }, - new() { Name = "All Files", Extensions = { "*" } } + new("Compressed") { Patterns = new List { "*.tgz" } }, + new("Bin Files") { Patterns = new List { "*.bin" } }, + new("All Files") { Patterns = new List { "*" } } } }; - var result = await dialog.ShowAsync(window); - - if (result != null && result.Length > 0) + var files = await window.StorageProvider.OpenFilePickerAsync(options); + var selectedFile = files.FirstOrDefault()?.TryGetLocalPath(); + if (!string.IsNullOrEmpty(selectedFile)) { - var selectedFile = result[0]; - var fileName = Path.GetFileName(selectedFile); Console.WriteLine($"Selected File: {selectedFile}"); _bRecursionSelectGuard = true; ManualLocalFirmwarePackageFile = selectedFile; @@ -1835,7 +1855,7 @@ private string BuildFirmwareDownloadUrl(string firmwareFilename) // public class FirmwareData { - public ObservableCollection Manufacturers { get; set; } + public ObservableCollection Manufacturers { get; set; } = new(); public void SortCollections() { @@ -1852,9 +1872,9 @@ public void SortCollections() public class Manufacturer { - public string Name { get; set; } - public string FriendlyName { get; set; } - public ObservableCollection Devices { get; set; } + public string Name { get; set; } = string.Empty; + public string FriendlyName { get; set; } = string.Empty; + public ObservableCollection Devices { get; set; } = new(); public void SortCollections() { @@ -1868,9 +1888,9 @@ public void SortCollections() public class Device { - public string Name { get; set; } - public string FriendlyName { get; set; } - public ObservableCollection FirmwarePackages { get; set; } + public string Name { get; set; } = string.Empty; + public string FriendlyName { get; set; } = string.Empty; + public ObservableCollection FirmwarePackages { get; set; } = new(); public void SortCollections() { @@ -1880,8 +1900,8 @@ public void SortCollections() public class FirmwarePackage { - public string Name { get; set; } - public string FriendlyName { get; set; } - public string PackageFile { get; set; } + public string Name { get; set; } = string.Empty; + public string FriendlyName { get; set; } = string.Empty; + public string PackageFile { get; set; } = string.Empty; } #endregion diff --git a/Companion/ViewModels/LogViewerViewModel.cs b/Companion/ViewModels/LogViewerViewModel.cs index a2e8da0..a1b6f8c 100644 --- a/Companion/ViewModels/LogViewerViewModel.cs +++ b/Companion/ViewModels/LogViewerViewModel.cs @@ -19,14 +19,14 @@ public class LogViewerViewModel : ViewModelBase private int _duplicateCount; private DateTime _lastFlushTime = DateTime.Now; private string _lastMessage = string.Empty; - private string _messageText; + private string _messageText = string.Empty; #endregion #region Public Properties /// /// Collection of log messages to display /// - public ObservableCollection LogMessages { get; set; } + public ObservableCollection LogMessages { get; set; } = new(); /// /// Gets or sets the current message text @@ -123,7 +123,7 @@ private void HandleDuplicateMessage() // Periodically flush duplicate count (every 5 seconds) if ((DateTime.Now - _lastFlushTime).TotalSeconds >= 5 && _duplicateCount > 0) { - FlushDuplicateMessage(); + _ = FlushDuplicateMessage(); } } @@ -143,7 +143,7 @@ private async void HandleNewMessage(string message, string formattedMessage) _lastMessage = message; // Add new message to log - Dispatcher.UIThread.InvokeAsync(() => LogMessages.Insert(0, formattedMessage)); + await Dispatcher.UIThread.InvokeAsync(() => LogMessages.Insert(0, formattedMessage)); } /// @@ -156,10 +156,10 @@ private async Task FlushDuplicateMessage() var duplicateMessage = FormatLogMessage( $"[Last message repeated {_duplicateCount} times]"); - Dispatcher.UIThread.InvokeAsync(() => LogMessages.Insert(0, duplicateMessage)); + await Dispatcher.UIThread.InvokeAsync(() => LogMessages.Insert(0, duplicateMessage)); _duplicateCount = 0; _lastFlushTime = DateTime.Now; } } #endregion -} \ No newline at end of file +} diff --git a/Companion/ViewModels/MainViewModel.cs b/Companion/ViewModels/MainViewModel.cs index 80be422..6823274 100644 --- a/Companion/ViewModels/MainViewModel.cs +++ b/Companion/ViewModels/MainViewModel.cs @@ -19,6 +19,7 @@ using Companion.Models; using Companion.Services; using Serilog; +using DeviceConfigModel = Companion.Models.DeviceConfig; namespace Companion.ViewModels; @@ -28,12 +29,12 @@ public partial class MainViewModel : ViewModelBase // With this singleton service private readonly PingService _pingService; - private DispatcherTimer _pingTimer; + private DispatcherTimer? _pingTimer; private readonly TimeSpan _pingInterval = TimeSpan.FromSeconds(1); private readonly TimeSpan _pingTimeout = TimeSpan.FromMilliseconds(500); private readonly IMessageBoxService _messageBoxService; - [ObservableProperty] private string _svgPath; + [ObservableProperty] private string _svgPath = string.Empty; private bool _isTabsCollapsed; [ObservableProperty] private bool _isMobile; @@ -51,7 +52,7 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _isConnected; [ObservableProperty] private ObservableCollection _cachedIpAddresses = new(); - [ObservableProperty] private string _selectedCachedIpAddress; + [ObservableProperty] private string _selectedCachedIpAddress = string.Empty; public MainViewModel(ILogger logger, @@ -166,15 +167,15 @@ public bool IsTabsCollapsed public ICommand OpenLogFolderCommand { get; } - public WfbTabViewModel WfbTabViewModel { get; } - public WfbGSTabViewModel WfbGSTabViewModel { get; } - public TelemetryTabViewModel TelemetryTabViewModel { get; } - public CameraSettingsTabViewModel CameraSettingsTabViewModel { get; } - public VRXTabViewModel VRXTabViewModel { get; } - public SetupTabViewModel SetupTabViewModel { get; } - public ConnectControlsViewModel ConnectControlsViewModel { get; } - public LogViewerViewModel LogViewerViewModel { get; } - public StatusBarViewModel StatusBarViewModel { get; } + public WfbTabViewModel? WfbTabViewModel { get; } + public WfbGSTabViewModel? WfbGSTabViewModel { get; } + public TelemetryTabViewModel? TelemetryTabViewModel { get; } + public CameraSettingsTabViewModel? CameraSettingsTabViewModel { get; } + public VRXTabViewModel? VRXTabViewModel { get; } + public SetupTabViewModel? SetupTabViewModel { get; } + public ConnectControlsViewModel? ConnectControlsViewModel { get; } + public LogViewerViewModel? LogViewerViewModel { get; } + public StatusBarViewModel? StatusBarViewModel { get; } private void UpdateSvgPath() { @@ -211,7 +212,7 @@ private string GetFormattedAppVersion() var parts = fullVersion.Split('+', 2); var baseVersion = parts[0]; - string shortHash = null; + var shortHash = string.Empty; var gitSuffixIndex = baseVersion.IndexOf("-g", StringComparison.OrdinalIgnoreCase); if (gitSuffixIndex >= 0) @@ -439,7 +440,7 @@ private void AddCurrentIpToCache() } // Update the cached IPs in DeviceConfig - _deviceConfig.CachedIpAddresses = CachedIpAddresses.ToList(); + DeviceConfig.CachedIpAddresses = CachedIpAddresses.ToList(); // Log the update _logger.Debug($"Added IP {IpAddress} to cache. Cache now contains {CachedIpAddresses.Count} IPs."); @@ -453,26 +454,26 @@ private async Task ConnectAsync() var appMessage = new AppMessage(); //DeviceConfig deviceConfig = new DeviceConfig(); - _deviceConfig.Username = "root"; - _deviceConfig.IpAddress = IpAddress; - _deviceConfig.Password = Password; - _deviceConfig.Port = Port; - _deviceConfig.DeviceType = SelectedDeviceType; + DeviceConfig.Username = "root"; + DeviceConfig.IpAddress = IpAddress; + DeviceConfig.Password = Password; + DeviceConfig.Port = Port; + DeviceConfig.DeviceType = SelectedDeviceType; UpdateUIMessage("Getting hostname"); - await getHostname(_deviceConfig); - if (_deviceConfig.Hostname == string.Empty) + await getHostname(DeviceConfig); + if (DeviceConfig.Hostname == string.Empty) { _logger.Error("Failed to get hostname, stopping"); return; } var validator = App.ServiceProvider.GetRequiredService(); - if (!validator.IsDeviceConfigValid(_deviceConfig)) + if (!validator.IsDeviceConfigValid(DeviceConfig)) { UpdateUIMessage("Hostname Error!"); var msBox = MessageBoxManager.GetMessageBoxStandard("Hostname Error!", - $"Hostname does not match device type! \nHostname: {_deviceConfig.Hostname} Device Type: {_selectedDeviceType}.\nPlease check device..\nOk to continue anyway\nCancel to quit", + $"Hostname does not match device type! \nHostname: {DeviceConfig.Hostname} Device Type: {_selectedDeviceType}.\nPlease check device..\nOk to continue anyway\nCancel to quit", ButtonEnum.OkCancel); var result = await msBox.ShowAsync(); @@ -483,37 +484,37 @@ private async Task ConnectAsync() } } - await getSensorType(_deviceConfig); - Sensor = _deviceConfig.SensorType; + await getSensorType(DeviceConfig); + Sensor = DeviceConfig.SensorType; - await getNetworkCardType(_deviceConfig); - NetworkCardType = _deviceConfig.NetworkCardType; + await getNetworkCardType(DeviceConfig); + NetworkCardType = DeviceConfig.NetworkCardType; - await getChipType(_deviceConfig); - Soc = _deviceConfig.ChipType; + await getChipType(DeviceConfig); + Soc = DeviceConfig.ChipType; // Save the config to app settings SaveConfig(); // Publish the event - EventSubscriptionService.Publish(new AppMessage { DeviceConfig = _deviceConfig }); + EventSubscriptionService.Publish(new AppMessage { DeviceConfig = DeviceConfig }); // set the background to gray EntryBoxBgColor = new SolidColorBrush(Colors.Gray); - appMessage.DeviceConfig = _deviceConfig; + appMessage.DeviceConfig = DeviceConfig; - if (_deviceConfig != null) + if (DeviceConfig != null) { // used to update the ui for the different views - EventSubscriptionService.Publish(_deviceConfig.DeviceType); + EventSubscriptionService.Publish(DeviceConfig.DeviceType); - if (_deviceConfig.DeviceType == DeviceType.Camera) + if (DeviceConfig.DeviceType == DeviceType.Camera) { UpdateUIMessage("Processing Camera..."); await processCameraFiles(); UpdateUIMessage("Processing Camera...done"); } - else if (_deviceConfig.DeviceType == DeviceType.Radxa) + else if (DeviceConfig.DeviceType == DeviceType.Radxa) { UpdateUIMessage("Processing Radxa..."); await processRadxaFiles(); @@ -528,16 +529,16 @@ private async Task ConnectAsync() private void SaveConfig() { - _deviceConfig.DeviceType = SelectedDeviceType; - _deviceConfig.IpAddress = IpAddress; - _deviceConfig.Port = Port; - _deviceConfig.Password = Password; + DeviceConfig.DeviceType = SelectedDeviceType; + DeviceConfig.IpAddress = IpAddress; + DeviceConfig.Port = Port; + DeviceConfig.Password = Password; // Save the cached IPs - _deviceConfig.CachedIpAddresses = CachedIpAddresses.ToList(); + DeviceConfig.CachedIpAddresses = CachedIpAddresses.ToList(); // save config to file - SettingsManager.SaveSettings(_deviceConfig); + SettingsManager.SaveSettings(DeviceConfig); } /// @@ -572,7 +573,7 @@ await SshClientService.ExecuteCommandWithResponseAsync(deviceConfig, DeviceComma var hostName = Utilities.RemoveSpecialCharacters(cmdResult.Result); deviceConfig.Hostname = hostName; - //_deviceConfig.Hostname = hostName; + //DeviceConfig.Hostname = hostName; //Hostname = hostName; // Cleanup @@ -687,7 +688,7 @@ await SshClientService.ExecuteCommandWithResponseAsync(deviceConfig, DeviceComma var lsusbResults = Utilities.RemoveLastChar(cmdResult.Result); var networkCardType = WifiCardDetector.DetectWifiCard(lsusbResults); - deviceConfig.NetworkCardType = networkCardType; + deviceConfig.NetworkCardType = networkCardType ?? string.Empty; // Cleanup @@ -710,7 +711,7 @@ private async Task processCameraFiles() // download file wfb.yaml _logger.Debug($"Reading wfb.yaml"); - var wfbContent = await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.WfbYamlFileLoc); + var wfbContent = await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.WfbYamlFileLoc); if (wfbContent != null) { @@ -726,7 +727,7 @@ private async Task processCameraFiles() try { var majesticContent = - await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.MajesticFileLoc); + await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.MajesticFileLoc); // Publish a message to WfbSettingsTabViewModel EventSubscriptionService.Publish(new MajesticContentUpdatedMessage(majesticContent)); @@ -740,7 +741,7 @@ private async Task processCameraFiles() { _logger.Debug($"Reading legacy settings"); // download file wfb.conf - var wfbConfContent = await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.WfbConfFileLoc); + var wfbConfContent = await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.WfbConfFileLoc); if (wfbConfContent != null) @@ -750,7 +751,7 @@ private async Task processCameraFiles() try { var majesticContent = - await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.MajesticFileLoc); + await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.MajesticFileLoc); // Publish a message to WfbSettingsTabViewModel EventSubscriptionService.Publish(new MajesticContentUpdatedMessage(majesticContent)); @@ -763,7 +764,7 @@ private async Task processCameraFiles() try { var telemetryContent = - await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.TelemetryConfFileLoc); + await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.TelemetryConfFileLoc); // Publish a message to WfbSettingsTabViewModel EventSubscriptionService.Publish(new AppMessage { CanConnect = DeviceConfig.Instance.CanConnect, DeviceConfig = _deviceConfig }); + AppMessage>(new AppMessage { CanConnect = DeviceConfigModel.Instance.CanConnect, DeviceConfig = DeviceConfig }); _logger.Information("Done reading files from device."); - _messageBoxService.ShowMessageBox("Read Device", "Done reading from device!"); + await _messageBoxService.ShowMessageBox("Read Device", "Done reading from device!"); } @@ -819,7 +820,7 @@ private async Task GetAndComputeAirMd5Hash() { // get /home/radxa/scripts/screen-mode var droneKeyContent = - await SshClientService.DownloadFileBytesAsync(_deviceConfig, OpenIPC.RemoteDroneKeyPath); + await SshClientService.DownloadFileBytesAsync(DeviceConfig, OpenIPC.RemoteDroneKeyPath); if (droneKeyContent != null) @@ -829,9 +830,9 @@ private async Task GetAndComputeAirMd5Hash() var droneKey = Utilities.ComputeMd5Hash(droneKeyContent); var deviceContentUpdatedMessage = new DeviceContentUpdatedMessage(); - _deviceConfig = DeviceConfig.Instance; - _deviceConfig.KeyChksum = droneKey; - deviceContentUpdatedMessage.DeviceConfig = _deviceConfig; + DeviceConfig = DeviceConfigModel.Instance; + DeviceConfig.KeyChksum = droneKey; + deviceContentUpdatedMessage.DeviceConfig = DeviceConfig; EventSubscriptionService.Publish(deviceContentUpdatedMessage); @@ -852,7 +853,7 @@ private async Task processRadxaFiles() // get /etc/wifibroadcast.cfg var wifibroadcastContent = - await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.WifiBroadcastFileLoc); + await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.WifiBroadcastFileLoc); if (!string.IsNullOrEmpty(wifibroadcastContent)) { @@ -881,7 +882,7 @@ await MessageBoxManager.GetMessageBoxStandard("Error", "Failed to download /etc/ UpdateUIMessage("Downloading modprod.d/wfb.conf"); // get /etc/modprobe.d/wfb.conf var wfbModProbeContent = - await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.WifiBroadcastModProbeFileLoc); + await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.WifiBroadcastModProbeFileLoc); if (wfbModProbeContent != null) { @@ -904,7 +905,7 @@ await MessageBoxManager.GetMessageBoxStandard("Error", "Failed to download /etc/ UpdateUIMessage("Downloading screen-mode"); // get /home/radxa/scripts/screen-mode var screenModeContent = - await SshClientService.DownloadFileAsync(_deviceConfig, OpenIPC.ScreenModeFileLoc); + await SshClientService.DownloadFileAsync(DeviceConfig, OpenIPC.ScreenModeFileLoc); if (screenModeContent != null) { @@ -927,7 +928,7 @@ await MessageBoxManager.GetMessageBoxStandard("Error", "Failed to download /etc/ UpdateUIMessage("Downloading gskey"); var gsKeyContent = - await SshClientService.DownloadFileBytesAsync(_deviceConfig, OpenIPC.RemoteGsKeyPath); + await SshClientService.DownloadFileBytesAsync(DeviceConfig, OpenIPC.RemoteGsKeyPath); if (gsKeyContent != null) { @@ -951,7 +952,7 @@ await MessageBoxManager.GetMessageBoxStandard("Error", "Failed to download /etc/ } EventSubscriptionService.Publish(new AppMessage - { CanConnect = DeviceConfig.Instance.CanConnect, DeviceConfig = _deviceConfig }); + { CanConnect = DeviceConfigModel.Instance.CanConnect, DeviceConfig = DeviceConfig }); _logger.Information("Done reading files from device."); } @@ -961,7 +962,7 @@ private void LoadSettings() IsWaiting = true; // Load settings via the SettingsManager var settings = SettingsManager.LoadSettings(); - _deviceConfig = DeviceConfig.Instance; + DeviceConfig = DeviceConfigModel.Instance; IpAddress = settings.IpAddress; Password = settings.Password; Port = settings.Port == 0 ? 22 : settings.Port; @@ -1084,14 +1085,14 @@ private void OnDeviceTypeChangeEvent(DeviceType deviceTypeEvent) #region Observable Properties [ObservableProperty] private bool _canConnect; - [ObservableProperty] private string _appVersionText; - [ObservableProperty] private string _ipAddress; + [ObservableProperty] private string _appVersionText = string.Empty; + [ObservableProperty] private string _ipAddress = string.Empty; [ObservableProperty] private int _port; - [ObservableProperty] private string _password; - [ObservableProperty] private string _device; + [ObservableProperty] private string _password = string.Empty; + [ObservableProperty] private string _device = string.Empty; [ObservableProperty] private bool isVRXEnabled; - [ObservableProperty] private DeviceConfig _deviceConfig; - [ObservableProperty] private TabItemViewModel _selectedTab; + [ObservableProperty] private DeviceConfig _deviceConfig = DeviceConfig.Instance; + [ObservableProperty] private TabItemViewModel _selectedTab = null!; #endregion } diff --git a/Companion/ViewModels/PresetsTabViewModel.cs b/Companion/ViewModels/PresetsTabViewModel.cs index b12008b..0ac0402 100644 --- a/Companion/ViewModels/PresetsTabViewModel.cs +++ b/Companion/ViewModels/PresetsTabViewModel.cs @@ -121,8 +121,7 @@ public PresetsTabViewModel( ClearFiltersCommand = new RelayCommand(ClearFilters); ShowPresetDetailsCommand = new RelayCommand(ShowPresetDetails); SyncRepositoryCommand = new AsyncRelayCommand(SyncRepositoryAsync); - - // CreatePresetCommand = new RelayCommand(async () => await CreatePresetAsync()); + CreatePresetCommand = new AsyncRelayCommand(CreatePresetAsync); LoadInitialRepositories(); @@ -345,10 +344,11 @@ private async Task LoadRemotePresetsAsync() { _logger.Information($"Processing repository: {repository.Name}"); - // Don't create any directory here, just pass null to let the service handle it + var localPresetsDirectory = GetTempPresetsDirectory(SanitizeRepositoryName(repository)); + var downloadedPresets = await _gitHubPresetService.SyncRepositoryPresetsAsync( repository, - null // Pass null to let GitHubPresetService handle directory creation + localPresetsDirectory ); // Process downloaded presets as before @@ -485,7 +485,7 @@ private void AddDefaultRepository() #region Repository Management Methods - private async Task AddRepository() + private Task AddRepository() { if (!EnsurePresetsEnabled()) return; @@ -506,6 +506,11 @@ private async Task AddRepository() if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + if (desktop.MainWindow is null) + { + return Task.CompletedTask; + } + var window = new Window { Title = "Add Repo", @@ -518,7 +523,7 @@ private async Task AddRepository() } if (string.IsNullOrWhiteSpace(NewRepositoryUrl)) - return; + return Task.CompletedTask; try { @@ -534,6 +539,8 @@ private async Task AddRepository() _logger.Warning($"Error adding repository: {ex.Message}"); UpdateUIMessage($"Failed to add repository: {ex.Message}"); } + + return Task.CompletedTask; } private void RemoveRepository(Repository? repository) @@ -707,6 +714,11 @@ private async Task CreatePresetAsync() var dialog = new PresetDetailsDialog(); if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + if (desktop.MainWindow is null) + { + return; + } + var result = await dialog.ShowDialog(desktop.MainWindow); if (result != null && !string.IsNullOrEmpty(result.Name)) @@ -776,6 +788,11 @@ public void ShowPresetDetails(Preset? preset) if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { + if (desktop.MainWindow is null) + { + return; + } + var window = new Window { Title = "Preset Details", @@ -863,8 +880,9 @@ private void ClearFilters() #region Utility Methods - private void UpdateUIMessage(string message) + public override void UpdateUIMessage(string message) { + base.UpdateUIMessage(message); LogMessage = message; } @@ -922,9 +940,9 @@ public PresetDetailsDialog() }; saveButton.Click += (s, e) => Close(new PresetDetailsResult { - Name = _nameTextBox.Text, - Category = _categoryTextBox.Text, - Description = _descriptionTextBox.Text + Name = _nameTextBox.Text ?? string.Empty, + Category = _categoryTextBox.Text ?? string.Empty, + Description = _descriptionTextBox.Text ?? string.Empty }); var cancelButton = new Button @@ -947,7 +965,7 @@ public PresetDetailsDialog() /// public class PresetDetailsResult { - public string Name { get; set; } - public string Category { get; set; } - public string Description { get; set; } + public string Name { get; set; } = string.Empty; + public string Category { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; } diff --git a/Companion/ViewModels/SetupTabViewModel.cs b/Companion/ViewModels/SetupTabViewModel.cs index f048ed3..2ba9c4a 100644 --- a/Companion/ViewModels/SetupTabViewModel.cs +++ b/Companion/ViewModels/SetupTabViewModel.cs @@ -13,6 +13,10 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; using Avalonia.Media; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -48,57 +52,51 @@ public partial class SetupTabViewModel : ViewModelBase #region Observable Properties [ObservableProperty] private bool _canConnect; - [ObservableProperty] private string _chkSumStatusColor; + [ObservableProperty] private string _chkSumStatusColor = string.Empty; [ObservableProperty] private int _downloadProgress; - [ObservableProperty] private ObservableCollection _droneKeyActionItems; - [ObservableProperty] private ObservableCollection _firmwareVersions; + [ObservableProperty] private ObservableCollection _droneKeyActionItems = new(); + [ObservableProperty] private ObservableCollection _firmwareVersions = new(); [ObservableProperty] private bool _isCamera; [ObservableProperty] private bool _isGS; [ObservableProperty] private bool _isRadxa; [ObservableProperty] private bool _isProgressBarVisible; - [ObservableProperty] private string _keyChecksum; - [ObservableProperty] private string _localIp; - [ObservableProperty] private ObservableCollection _localSensors; - [ObservableProperty] private string _progressText; - [ObservableProperty] private string _scanIpLabel; - [ObservableProperty] private string _scanIPResultTextBox; - [ObservableProperty] private string _scanMessages; + [ObservableProperty] private string _keyChecksum = string.Empty; + [ObservableProperty] private string _localIp = string.Empty; + [ObservableProperty] private ObservableCollection _localSensors = new(); + [ObservableProperty] private string _progressText = string.Empty; + [ObservableProperty] private string _scanIpLabel = string.Empty; + [ObservableProperty] private string _scanIPResultTextBox = string.Empty; + [ObservableProperty] private string _scanMessages = string.Empty; [ObservableProperty] private bool _isScanning; - [ObservableProperty] private ObservableCollection _scriptFileActionItems; - [ObservableProperty] private string _selectedDroneKeyAction; - [ObservableProperty] private string _selectedFwVersion; - [ObservableProperty] private string _selectedScriptFileAction; - [ObservableProperty] private string _selectedSensor; - [ObservableProperty] private ObservableCollectionExtended _keyManagementActionItems; - [ObservableProperty] private string _selectedKeyManagementAction; - [ObservableProperty] private IBrush _keyValidationColor; - [ObservableProperty] private string _keyValidationMessage; - - private string _localKeyPath; - private string _localDefaultKeyPath; - private bool _isGeneratingKey; - - IMessageBoxService _messageBoxService; + [ObservableProperty] private ObservableCollection _scriptFileActionItems = new(); + [ObservableProperty] private string _selectedDroneKeyAction = string.Empty; + [ObservableProperty] private string _selectedFwVersion = string.Empty; + [ObservableProperty] private string _selectedScriptFileAction = string.Empty; + [ObservableProperty] private string _selectedSensor = string.Empty; + [ObservableProperty] private ObservableCollectionExtended _keyManagementActionItems = new(); + [ObservableProperty] private string _selectedKeyManagementAction = string.Empty; + [ObservableProperty] private IBrush _keyValidationColor = Brushes.Transparent; + [ObservableProperty] private string _keyValidationMessage = string.Empty; + + private string _localKeyPath = string.Empty; + private string _localDefaultKeyPath = string.Empty; + private readonly IMessageBoxService _messageBoxService; #endregion #region Commands - private ICommand _encryptionKeyActionCommand; - private ICommand _firmwareUpdateCommand; - private ICommand _generateKeysCommand; - private ICommand _offlineUpdateCommand; - private ICommand _recvDroneKeyCommand; - private ICommand _recvGSKeyCommand; - private ICommand _resetCameraCommand; - private ICommand _scanCommand; - private ICommand _cancelScanCommand; - private ICommand _scriptFilesCommand; - private ICommand _scriptFilesBackupCommand; - private ICommand _scriptFilesRestoreCommand; - private ICommand _sendDroneKeyCommand; - private ICommand _sendGSKeyCommand; - private ICommand _sensorDriverUpdateCommand; - private ICommand _sensorFilesBackupCommand; - private ICommand _sensorFilesUpdateCommand; + private ICommand? _encryptionKeyActionCommand; + private ICommand? _firmwareUpdateCommand; + private ICommand? _offlineUpdateCommand; + private ICommand? _recvDroneKeyCommand; + private ICommand? _recvGSKeyCommand; + private ICommand? _resetCameraCommand; + private ICommand? _scanCommand; + private ICommand? _cancelScanCommand; + private ICommand? _scriptFilesCommand; + private ICommand? _sendDroneKeyCommand; + private ICommand? _sendGSKeyCommand; + private ICommand? _sensorFilesUpdateCommand; + private ICommand? _keyManagementCommand; public ICommand KeyManagementCommand => _keyManagementCommand ??= new AsyncRelayCommand(ExecuteKeyManagementActionAsync); #endregion @@ -106,12 +104,12 @@ public partial class SetupTabViewModel : ViewModelBase // Command Properties #region Command Properties - public ICommand ShowProgressBarCommand { get; private set; } + public ICommand ShowProgressBarCommand { get; private set; } = new RelayCommand(() => { }); public ICommand SendGSKeyCommand => _sendGSKeyCommand ??= new AsyncRelayCommand(SendGSKeyAsync); public ICommand RecvGSKeyCommand => _recvGSKeyCommand ??= new AsyncRelayCommand(RecvGSKeyAsync); public ICommand ScriptFilesCommand => _scriptFilesCommand ??= new AsyncRelayCommand(ScriptFilesActionAsync); public ICommand EncryptionKeyActionCommand => - _encryptionKeyActionCommand ??= new AsyncRelayCommand(EncryptionKeyActionAsync); + _encryptionKeyActionCommand ??= new AsyncRelayCommand(EncryptionKeyActionAsync); public ICommand SensorFilesUpdateCommand => _sensorFilesUpdateCommand ??= new AsyncRelayCommand(SensorFilesUpdateAsync); public ICommand FirmwareUpdateCommand => @@ -147,7 +145,6 @@ public SetupTabViewModel( : base(logger, sshClientService, eventSubscriptionService) { _messageBoxService = messageBoxService; - ; InitializeKeyManagement(); InitializeCollections(); InitializeProperties(); @@ -249,18 +246,17 @@ private void InitializeFirmwareVersions() #region Event Handlers private void OnDeviceTypeChange(DeviceType deviceType) { - if (deviceType != null) - switch (deviceType) - { - case DeviceType.Camera: - IsCamera = true; - IsRadxa = false; - break; - case DeviceType.Radxa: - IsCamera = false; - IsRadxa = true; - break; - } + switch (deviceType) + { + case DeviceType.Camera: + IsCamera = true; + IsRadxa = false; + break; + case DeviceType.Radxa: + IsCamera = false; + IsRadxa = true; + break; + } } private void OnDeviceContentUpdate(DeviceContentUpdatedMessage message) @@ -279,13 +275,16 @@ private void OnAppMessage(AppMessage appMessage) #endregion #region Command Handlers - private async Task ScriptFilesActionAsync() + private Task ScriptFilesActionAsync() { - var action = SelectedScriptFileAction; + return Task.CompletedTask; } - private async Task EncryptionKeyActionAsync(string comboBoxName) + private async Task EncryptionKeyActionAsync(string? comboBoxName) { + if (string.IsNullOrWhiteSpace(comboBoxName)) + return; + var action = SelectedDroneKeyAction; switch (action) { @@ -331,8 +330,6 @@ private async Task ScriptFilesBackup() // Key Management Methods to add to your SetupTabViewModel.cs file -private ICommand _keyManagementCommand; - private async Task ExecuteKeyManagementActionAsync() { try @@ -389,7 +386,6 @@ private async Task GenerateNewKeyAsync() { try { - _isGeneratingKey = true; IsProgressBarVisible = true; ProgressText = "Generating secure key..."; DownloadProgress = 0; @@ -464,11 +460,10 @@ await Task.Run(() => } finally { - _isGeneratingKey = false; IsProgressBarVisible = false; } } -private async Task UploadKeyAsync(string specificKeyPath = null) +private async Task UploadKeyAsync(string? specificKeyPath = null) { try { @@ -476,31 +471,14 @@ private async Task UploadKeyAsync(string specificKeyPath = null) ProgressText = "Preparing to upload key..."; DownloadProgress = 0; - string keyPath = specificKeyPath; + string? keyPath = specificKeyPath; // If no specific key path was provided, ask the user to select a key file if (string.IsNullOrEmpty(keyPath)) { - var openFileDialog = new Avalonia.Controls.OpenFileDialog - { - Title = "Select Key File", - Filters = new List - { - new Avalonia.Controls.FileDialogFilter { Name = "Key Files", Extensions = new List { "key" } } - }, - Directory = Path.Combine(OpenIPC.AppDataConfigDirectory, "keys") - }; - - var window = Avalonia.Application.Current.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime; - var result = await openFileDialog.ShowAsync(window?.MainWindow); - - if (result == null || result.Length == 0) - { - IsProgressBarVisible = false; + keyPath = await PickKeyFileAsync(); + if (string.IsNullOrEmpty(keyPath)) return; - } - - keyPath = result[0]; } ProgressText = "Reading key file..."; @@ -530,13 +508,13 @@ private async Task UploadKeyAsync(string specificKeyPath = null) // Get the checksum from the device ProgressText = "Verifying key upload..."; - string remoteChecksum = await GetDeviceKeyChecksumAsync(); + var remoteChecksum = await GetDeviceKeyChecksumAsync(); DownloadProgress = 100; ProgressText = "Key upload complete"; // Verify the checksums match - if (localChecksum == remoteChecksum) + if (!string.IsNullOrEmpty(remoteChecksum) && localChecksum == remoteChecksum) { KeyChecksum = remoteChecksum; ChkSumStatusColor = "Green"; @@ -551,7 +529,7 @@ private async Task UploadKeyAsync(string specificKeyPath = null) } else { - KeyChecksum = $"Local: {localChecksum}, Device: {remoteChecksum}"; + KeyChecksum = $"Local: {localChecksum}, Device: {remoteChecksum ?? "Unavailable"}"; ChkSumStatusColor = "Red"; KeyValidationColor = Brushes.Red; KeyValidationMessage = "Key upload failed verification"; @@ -691,7 +669,7 @@ private async Task VerifyKeyAsync() // Get the device key checksum DownloadProgress = 30; ProgressText = "Getting device key checksum..."; - string deviceChecksum = await GetDeviceKeyChecksumAsync(); + var deviceChecksum = await GetDeviceKeyChecksumAsync(); if (string.IsNullOrEmpty(deviceChecksum)) { @@ -787,7 +765,7 @@ private async Task GetKeyChecksumAsync() DownloadProgress = 50; // Get the checksum from the device - string deviceChecksum = await GetDeviceKeyChecksumAsync(); + var deviceChecksum = await GetDeviceKeyChecksumAsync(); DownloadProgress = 100; ProgressText = "Checksum retrieved"; @@ -818,7 +796,7 @@ private async Task GetKeyChecksumAsync() } } -private async Task GetDeviceKeyChecksumAsync() +private async Task GetDeviceKeyChecksumAsync() { try { @@ -862,9 +840,10 @@ private string CalculateChecksum(byte[] data) return sb.ToString(); } } - private async Task ScriptFilesRestore() + private Task ScriptFilesRestore() { Log.Debug("Restore script executed...not implemented yet"); + return Task.CompletedTask; } private void PopulateSensorFileNames(string directoryPath) @@ -881,7 +860,7 @@ private void PopulateSensorFileNames(string directoryPath) } } - private async Task SensorDriverUpdate() + private Task SensorDriverUpdate() { Log.Debug("SensorDriverUpdate executed"); DownloadProgress = 0; @@ -895,6 +874,7 @@ private async Task SensorDriverUpdate() ProgressText = "Sensor driver updated!"; Log.Debug("SensorDriverUpdate executed..done"); + return Task.CompletedTask; } public async Task SensorFilesUpdateAsync() @@ -939,7 +919,7 @@ private async Task OfflineUpdateAsync() { Log.Debug("OfflineUpdate executed"); IsProgressBarVisible = true; - DownloadStart(); + await DownloadStart(); //Log.Debug("OfflineUpdate executed..done"); } @@ -1042,7 +1022,7 @@ await Dispatcher.UIThread.InvokeAsync(() => IsScanning = false; } - private CancellationTokenSource _scanCancellationTokenSource; + private CancellationTokenSource? _scanCancellationTokenSource; private void CancelScan() { @@ -1197,7 +1177,7 @@ private static List BuildScanTargets(string input) /// The string to extract the value from. /// The regular expression pattern to use for extraction. /// The extracted value, or null if the pattern does not match. - public static string ExtractValue(string input, string pattern) + public static string? ExtractValue(string input, string pattern) { var match = Regex.Match(input, pattern); if (match.Success) @@ -1268,7 +1248,7 @@ public async Task DownloadStart() sensorType = ExtractValue($"{SelectedFwVersion}", openipcPattern); } - if (SelectedFwVersion != string.Empty && sensorType != string.Empty) + if (!string.IsNullOrEmpty(SelectedFwVersion) && !string.IsNullOrEmpty(sensorType)) { var firmwarePath = Path.Combine(OpenIPC.AppDataConfigDirectory, "firmware", $"{SelectedFwVersion}.tgz"); @@ -1365,7 +1345,7 @@ public async Task DownloadStart() // Provide a way for the user to cancel (e.g., a button) var cancelToken = cts.Token; - PerformSystemUpgradeAsync(kernelPath, rootfsPath, cancelToken); + await PerformSystemUpgradeAsync(kernelPath, rootfsPath, cancelToken); } } @@ -1538,7 +1518,7 @@ private async Task ResetCameraAsync() var result = await box.ShowAsync(); if (result == ButtonResult.Ok) { - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.ResetCameraCommand); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.ResetCameraCommand); await Task.Delay(1000); // Non-blocking pause } else @@ -1549,8 +1529,6 @@ private async Task ResetCameraAsync() await confirmBox.ShowAsync(); return; } - - return; Log.Debug("ResetCamera executed...done"); } @@ -1612,10 +1590,37 @@ private async Task RecvGSKeyAsync() { UpdateUIMessage("Receiving keys..."); - SshClientService.DownloadFileLocalAsync(DeviceConfig.Instance, OpenIPC.RemoteGsKeyPath, + await SshClientService.DownloadFileLocalAsync(DeviceConfig.Instance, OpenIPC.RemoteGsKeyPath, $"{OpenIPC.LocalTempFolder}/gs.key"); await Task.Delay(1000); // Non-blocking pause UpdateUIMessage("Receiving keys...done"); } + + private static Window? GetMainWindow() + { + return (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow; + } + + private async Task PickKeyFileAsync() + { + var mainWindow = GetMainWindow(); + if (mainWindow?.StorageProvider == null) + return null; + + var options = new FilePickerOpenOptions + { + Title = "Select Key File", + FileTypeFilter = new List + { + new("Key Files") + { + Patterns = new List { "*.key" } + } + } + }; + + var files = await mainWindow.StorageProvider.OpenFilePickerAsync(options); + return files.FirstOrDefault()?.TryGetLocalPath(); + } } diff --git a/Companion/ViewModels/StatusBarViewModel.cs b/Companion/ViewModels/StatusBarViewModel.cs index 6848b6f..50210d1 100644 --- a/Companion/ViewModels/StatusBarViewModel.cs +++ b/Companion/ViewModels/StatusBarViewModel.cs @@ -7,13 +7,13 @@ namespace Companion.ViewModels; public partial class StatusBarViewModel : ViewModelBase { - [ObservableProperty] private string _appVersionText; + [ObservableProperty] private string _appVersionText = string.Empty; - [ObservableProperty] private string _hostNameText; + [ObservableProperty] private string _hostNameText = string.Empty; - [ObservableProperty] private string _messageText; + [ObservableProperty] private string _messageText = string.Empty; - [ObservableProperty] private string _statusText; + [ObservableProperty] private string _statusText = string.Empty; public StatusBarViewModel(ILogger logger, ISshClientService sshClientService, @@ -45,4 +45,4 @@ private void UpdateStatus(AppMessage appMessage) if (!string.IsNullOrEmpty(appMessage.DeviceConfig.Hostname)) HostNameText = appMessage.DeviceConfig.Hostname; } -} \ No newline at end of file +} diff --git a/Companion/ViewModels/TelemetryTabViewModel.cs b/Companion/ViewModels/TelemetryTabViewModel.cs index 079268c..84267e0 100644 --- a/Companion/ViewModels/TelemetryTabViewModel.cs +++ b/Companion/ViewModels/TelemetryTabViewModel.cs @@ -44,14 +44,14 @@ public partial class TelemetryTabViewModel : ViewModelBase #region Observable Properties [ObservableProperty] private bool _canConnect; - [ObservableProperty] private string _selectedAggregate; - [ObservableProperty] private string _selectedBaudRate; - [ObservableProperty] private string _selectedMcsIndex; - [ObservableProperty] private string _selectedRcChannel; - [ObservableProperty] private string _selectedRouter; - [ObservableProperty] private string _selectedMSPFps; - [ObservableProperty] private string _selectedSerialPort; - [ObservableProperty] private string _telemetryContent; + [ObservableProperty] private string _selectedAggregate = string.Empty; + [ObservableProperty] private string _selectedBaudRate = string.Empty; + [ObservableProperty] private string _selectedMcsIndex = string.Empty; + [ObservableProperty] private string _selectedRcChannel = string.Empty; + [ObservableProperty] private string _selectedRouter = string.Empty; + [ObservableProperty] private string _selectedMSPFps = string.Empty; + [ObservableProperty] private string _selectedSerialPort = string.Empty; + [ObservableProperty] private string _telemetryContent = string.Empty; @@ -89,49 +89,49 @@ public partial class TelemetryTabViewModel : ViewModelBase /// /// Available serial ports for telemetry /// - public ObservableCollection SerialPorts { get; private set; } + public ObservableCollection SerialPorts { get; private set; } = new(); /// /// Available baud rates for serial communication /// - public ObservableCollection BaudRates { get; private set; } + public ObservableCollection BaudRates { get; private set; } = new(); /// /// Available MCS index values /// - public ObservableCollection McsIndex { get; private set; } + public ObservableCollection McsIndex { get; private set; } = new(); /// /// Available aggregate values /// - public ObservableCollection Aggregate { get; private set; } + public ObservableCollection Aggregate { get; private set; } = new(); /// /// Available RC channel options /// - public ObservableCollection RC_Channel { get; private set; } + public ObservableCollection RC_Channel { get; private set; } = new(); /// /// Available router options /// - public ObservableCollection Router { get; private set; } + public ObservableCollection Router { get; private set; } = new(); /// /// Available msposd fps options /// - public ObservableCollection MSPFps { get; private set; } + public ObservableCollection MSPFps { get; private set; } = new(); #endregion #region Commands - public ICommand EnableUART0Command { get; private set; } - public ICommand DisableUART0Command { get; private set; } - public ICommand AddMavlinkCommand { get; private set; } - public ICommand UploadLatestVtxMenuCommand { get; private set; } - public ICommand Enable40MhzCommand { get; private set; } - public ICommand MSPOSDExtraCameraCommand { get; private set; } - public ICommand MSPOSDExtraGSCommand { get; private set; } - public ICommand RemoveMSPOSDExtraCommand { get; private set; } - public ICommand SaveAndRestartTelemetryCommand { get; private set; } + public ICommand EnableUART0Command { get; private set; } = new RelayCommand(() => { }); + public ICommand DisableUART0Command { get; private set; } = new RelayCommand(() => { }); + public ICommand AddMavlinkCommand { get; private set; } = new RelayCommand(() => { }); + public ICommand UploadLatestVtxMenuCommand { get; private set; } = new RelayCommand(() => { }); + public ICommand Enable40MhzCommand { get; private set; } = new RelayCommand(() => { }); + public ICommand MSPOSDExtraCameraCommand { get; private set; } = new RelayCommand(() => { }); + public ICommand MSPOSDExtraGSCommand { get; private set; } = new RelayCommand(() => { }); + public ICommand RemoveMSPOSDExtraCommand { get; private set; } = new RelayCommand(() => { }); + public ICommand SaveAndRestartTelemetryCommand { get; private set; } = new RelayCommand(() => { }); @@ -302,7 +302,7 @@ await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, $"sed -i 's/sleep 5/#sleep 5/' {remoteTelemetryFile}"); await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.DataLinkRestart); - _messageBoxService.ShowMessageBox("Done!", "Please wait for datalink to restart!"); + await _messageBoxService.ShowMessageBox("Done!", "Please wait for datalink to restart!"); } } @@ -325,7 +325,7 @@ private async Task AddMSPOSDCameraExtra() await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.DataLinkRestart); - _messageBoxService.ShowMessageBox("Done!", "Please wait for datalink to restart!"); + await _messageBoxService.ShowMessageBox("Done!", "Please wait for datalink to restart!"); } } @@ -347,7 +347,7 @@ private async Task AddMSPOSDGSExtra() await SshClientService.UploadFileAsync(DeviceConfig.Instance, telemetryFile, remoteTelemetryFile); await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.DataLinkRestart); - _messageBoxService.ShowMessageBox("Done!", "Please wait for datalink to restart!"); + await _messageBoxService.ShowMessageBox("Done!", "Please wait for datalink to restart!"); } } @@ -393,7 +393,7 @@ private async Task RebootDevice() { await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.RebootCommand); - _messageBoxService.ShowMessageBox("Rebooting Device!", "Rebooting device, please wait for device to be ready and reconnect, then validate settings."); + await _messageBoxService.ShowMessageBox("Rebooting Device!", "Rebooting device, please wait for device to be ready and reconnect, then validate settings."); } #endregion @@ -460,7 +460,7 @@ private void UpdateViewModelPropertiesFromYaml() { if (_yamlConfig.TryGetValue(WfbYaml.TelemetrySerialPort, out var serialPort)) { - if (SerialPorts?.Contains(serialPort) ?? false) + if (SerialPorts.Contains(serialPort)) { SelectedSerialPort = serialPort; } @@ -473,7 +473,7 @@ private void UpdateViewModelPropertiesFromYaml() if (_yamlConfig.TryGetValue(WfbYaml.TelemetryRouter, out var router)) { - if (Router?.Contains(router) ?? false) + if (Router.Contains(router)) { SelectedRouter = router; } @@ -487,7 +487,7 @@ private void UpdateViewModelPropertiesFromYaml() if (_yamlConfig.TryGetValue(WfbYaml.TelemetryOsdFps, out var osd_fps)) { - if (MSPFps?.Contains(osd_fps) ?? false) + if (MSPFps.Contains(osd_fps)) { SelectedMSPFps = osd_fps; } @@ -516,7 +516,7 @@ private void UpdatePropertyFromTelemetryLine(string key, string value) serialPortBaseName = value.Substring("/dev/".Length); } - if (SerialPorts?.Contains(serialPortBaseName) ?? false) + if (SerialPorts.Contains(serialPortBaseName)) { SelectedSerialPort = serialPortBaseName; } @@ -528,7 +528,7 @@ private void UpdatePropertyFromTelemetryLine(string key, string value) break; case Telemetry.Baud: - if (BaudRates?.Contains(value) ?? false) + if (BaudRates.Contains(value)) { SelectedBaudRate = value; } @@ -547,7 +547,7 @@ private void UpdatePropertyFromTelemetryLine(string key, string value) routerName = mappedRouter; } - if (Router?.Contains(routerName) ?? false) + if (Router.Contains(routerName)) { SelectedRouter = routerName; } @@ -559,7 +559,7 @@ private void UpdatePropertyFromTelemetryLine(string key, string value) break; case Telemetry.McsIndex: - if (McsIndex?.Contains(value) ?? false) + if (McsIndex.Contains(value)) { SelectedMcsIndex = value; } @@ -571,7 +571,7 @@ private void UpdatePropertyFromTelemetryLine(string key, string value) break; case Telemetry.Aggregate: - if (Aggregate?.Contains(value) ?? false) + if (Aggregate.Contains(value)) { SelectedAggregate = value; } @@ -583,7 +583,7 @@ private void UpdatePropertyFromTelemetryLine(string key, string value) break; case Telemetry.RcChannel: - if (RC_Channel?.Contains(value) ?? false) + if (RC_Channel.Contains(value)) { SelectedRcChannel = value; } diff --git a/Companion/ViewModels/VRXTabViewModel.cs b/Companion/ViewModels/VRXTabViewModel.cs index 17bb542..50003ef 100644 --- a/Companion/ViewModels/VRXTabViewModel.cs +++ b/Companion/ViewModels/VRXTabViewModel.cs @@ -21,20 +21,20 @@ public partial class VRXTabViewModel : ViewModelBase { [ObservableProperty] private bool _canConnect; - [ObservableProperty] private string _droneKeyChecksum; + [ObservableProperty] private string _droneKeyChecksum = string.Empty; - [ObservableProperty] private ObservableCollection _fps; + [ObservableProperty] private ObservableCollection _fps = new(); [ObservableProperty] private bool _isExtendedMavLinkOSD; [ObservableProperty] private bool _isSimpleMavLinkOSD; - [ObservableProperty] private ObservableCollection _resolution; + [ObservableProperty] private ObservableCollection _resolution = new(); - [ObservableProperty] private string _selectedFps; + [ObservableProperty] private string _selectedFps = string.Empty; - [ObservableProperty] private string _selectedResolution; + [ObservableProperty] private string _selectedResolution = string.Empty; - [ObservableProperty] private string _wfbConfContent; + [ObservableProperty] private string _wfbConfContent = string.Empty; public VRXTabViewModel(ILogger logger, ISshClientService sshClientService, @@ -142,10 +142,10 @@ private async Task EnableVrxMajestic() { //basic UpdateUIMessage("Setting Simple Mavlink OSD"); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavBasic1); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavBasic2); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavBasic1); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavBasic2); UpdateUIMessage("Rebooting device"); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.RebootCommand); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.RebootCommand); } catch (Exception e) { @@ -157,10 +157,10 @@ private async Task EnableVrxMajestic() try { UpdateUIMessage("Setting Extended Mavlink OSD"); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavExtended1); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavExtended2); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavExtended1); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GsMavExtended2); UpdateUIMessage("Rebooting device"); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.RebootCommand); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.RebootCommand); } catch (Exception e) { @@ -187,13 +187,13 @@ private async Task EnableVrxMajestic() private async Task EnableVrxMSPDisplayport() { Log.Information("EnableVrxMSPDisplayport clicked"); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GSMSPDisplayportCommand); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GSMSPDisplayport2Command); - SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.RebootCommand); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GSMSPDisplayportCommand); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.GSMSPDisplayport2Command); + await SshClientService.ExecuteCommandAsync(DeviceConfig.Instance, DeviceCommands.RebootCommand); Log.Information("EnableVrxMSPDisplaypor..done"); //mspgs //plink -ssh root@%2 -pw %3 sed -i '/pixelpilot --osd --screen-mode $SCREEN_MODE --dvr-framerate $REC_FPS --dvr-fmp4 --dvr record_${current_date}.mp4/c\pixelpilot --osd --osd-elements video,wfbng --screen-mode $SCREEN_MODE --dvr-framerate $REC_FPS --dvr-fmp4 --dvr record_${current_date}.mp4 "&"' /config/scripts/stream.sh //plink -ssh root@%2 -pw %3 sed -i '/pixelpilot --osd --screen-mode $SCREEN_MODE/c\pixelpilot --osd --osd-elements video,wfbng --screen-mode $SCREEN_MODE "&"' /config/scripts/stream.sh //plink -ssh root@%2 -pw %3 reboot } -} \ No newline at end of file +} diff --git a/Companion/ViewModels/WfbGSTabViewModel.cs b/Companion/ViewModels/WfbGSTabViewModel.cs index 0b74bd0..9f70f5e 100644 --- a/Companion/ViewModels/WfbGSTabViewModel.cs +++ b/Companion/ViewModels/WfbGSTabViewModel.cs @@ -66,15 +66,15 @@ public partial class WfbGSTabViewModel : ViewModelBase [ObservableProperty] private bool _canConnect; - [ObservableProperty] private ObservableCollection _frequencies; - [ObservableProperty] private string _gsMavlink; - [ObservableProperty] private string _gsVideo; - [ObservableProperty] private ObservableCollection _power; + [ObservableProperty] private ObservableCollection _frequencies = new(); + [ObservableProperty] private string _gsMavlink = string.Empty; + [ObservableProperty] private string _gsVideo = string.Empty; + [ObservableProperty] private ObservableCollection _power = new(); - [ObservableProperty] private string _selectedFrequencyString; + [ObservableProperty] private string _selectedFrequencyString = string.Empty; [ObservableProperty] private int _selectedPower; - [ObservableProperty] private string _wifiRegion; + [ObservableProperty] private string _wifiRegion = string.Empty; public WfbGSTabViewModel(ILogger logger, ISshClientService sshClientService, @@ -143,7 +143,7 @@ private async Task UpdateModprobeWfbConf() } // Upload the updated configuration file - SshClientService.UploadFileStringAsync(DeviceConfig.Instance, OpenIPC.WifiBroadcastModProbeFileLoc, + await SshClientService.UploadFileStringAsync(DeviceConfig.Instance, OpenIPC.WifiBroadcastModProbeFileLoc, updatedConfigString); Log.Information("Configuration file updated and uploaded successfully."); } @@ -177,7 +177,7 @@ private async Task UpdateWifiBroadcastCfg() } // Upload the updated configuration file - SshClientService.UploadFileStringAsync(DeviceConfig.Instance, OpenIPC.WifiBroadcastFileLoc, + await SshClientService.UploadFileStringAsync(DeviceConfig.Instance, OpenIPC.WifiBroadcastFileLoc, updatedConfigContent); Log.Information("Configuration file updated and uploaded successfully."); } @@ -205,8 +205,8 @@ private void OnRadxaContentUpdateChange(RadxaContentUpdatedMessage radxaContentU var channel = _wifiConfigParser.WifiChannel; - string frequencyString; - if (_frequencyMapping.TryGetValue(channel, out frequencyString)) SelectedFrequencyString = frequencyString; + if (_frequencyMapping.TryGetValue(channel, out var frequencyString)) + SelectedFrequencyString = frequencyString; var wifiRegion = _wifiConfigParser.WifiRegion; if (!string.IsNullOrEmpty(wifiRegion)) WifiRegion = _wifiConfigParser.WifiRegion; @@ -226,4 +226,4 @@ private void OnRadxaContentUpdateChange(RadxaContentUpdatedMessage radxaContentU if (int.TryParse(power, out var parsedPower)) SelectedPower = parsedPower; } } -} \ No newline at end of file +} diff --git a/Companion/ViewModels/WfbTabViewModel.cs b/Companion/ViewModels/WfbTabViewModel.cs index a4c3f1f..2665e55 100644 --- a/Companion/ViewModels/WfbTabViewModel.cs +++ b/Companion/ViewModels/WfbTabViewModel.cs @@ -25,7 +25,6 @@ public partial class WfbTabViewModel : ViewModelBase #region Private Fields private readonly Dictionary _24FrequencyMapping = FrequencyMappings.Frequency24GHz; private readonly Dictionary _58FrequencyMapping = FrequencyMappings.Frequency58GHz; - private bool _isDisposed; private readonly IYamlConfigService _yamlConfigService; private readonly Dictionary _yamlConfig = new(); private readonly IGlobalSettingsService _globalSettingsService; @@ -33,8 +32,8 @@ public partial class WfbTabViewModel : ViewModelBase #region Observable Properties [ObservableProperty] private bool _canConnect; - [ObservableProperty] private string _wfbConfContent; - [ObservableProperty] private string _wfbYamlContent; + [ObservableProperty] private string _wfbConfContent = string.Empty; + [ObservableProperty] private string _wfbYamlContent = string.Empty; [ObservableProperty] private int _selectedChannel; [ObservableProperty] private int _selectedPower24GHz; [ObservableProperty] private int _selectedBandwidth; @@ -44,21 +43,21 @@ public partial class WfbTabViewModel : ViewModelBase [ObservableProperty] private int _selectedStbc; [ObservableProperty] private int _selectedFecK; [ObservableProperty] private int _selectedFecN; - [ObservableProperty] private string _selectedFrequency24String; - [ObservableProperty] private string _selectedFrequency58String; + [ObservableProperty] private string _selectedFrequency24String = string.Empty; + [ObservableProperty] private string _selectedFrequency58String = string.Empty; #endregion #region Collections - [ObservableProperty] private ObservableCollection _frequencies58GHz; - [ObservableProperty] private ObservableCollection _frequencies24GHz; - [ObservableProperty] private ObservableCollection _power58GHz; - [ObservableProperty] private ObservableCollection _power24GHz; - [ObservableProperty] private ObservableCollection _bandwidth; - [ObservableProperty] private ObservableCollection _mcsIndex; - [ObservableProperty] private ObservableCollection _stbc; - [ObservableProperty] private ObservableCollection _ldpc; - [ObservableProperty] private ObservableCollection _fecK; - [ObservableProperty] private ObservableCollection _fecN; + [ObservableProperty] private ObservableCollection _frequencies58GHz = new(); + [ObservableProperty] private ObservableCollection _frequencies24GHz = new(); + [ObservableProperty] private ObservableCollection _power58GHz = new(); + [ObservableProperty] private ObservableCollection _power24GHz = new(); + [ObservableProperty] private ObservableCollection _bandwidth = new(); + [ObservableProperty] private ObservableCollection _mcsIndex = new(); + [ObservableProperty] private ObservableCollection _stbc = new(); + [ObservableProperty] private ObservableCollection _ldpc = new(); + [ObservableProperty] private ObservableCollection _fecK = new(); + [ObservableProperty] private ObservableCollection _fecN = new(); [ObservableProperty] private int _mlink = 0; [ObservableProperty] private int _maxPower58GHz = 60; [ObservableProperty] private int _maxPower24GHz = 60; @@ -68,7 +67,7 @@ public partial class WfbTabViewModel : ViewModelBase /// /// Command to restart the WFB service /// - public ICommand RestartWfbCommand { get; set; } + public ICommand RestartWfbCommand { get; set; } = new AsyncRelayCommand(() => Task.CompletedTask); #endregion #region Constructor @@ -293,7 +292,7 @@ await SshClientService.UploadFileStringAsync( OpenIPC.WfbYamlFileLoc, updatedYamlContent); - SshClientService.ExecuteCommandAsync( + await SshClientService.ExecuteCommandAsync( DeviceConfig.Instance, DeviceCommands.WfbRestartCommand); @@ -449,12 +448,12 @@ private void HandleFrequencyChange(string newValue, Dictionary freq // Reset the other frequency collection to its first value if (frequencyMapping == _24FrequencyMapping) { - SelectedFrequency58String = Frequencies58GHz.FirstOrDefault(); + SelectedFrequency58String = Frequencies58GHz.FirstOrDefault() ?? string.Empty; //SelectedPower = Power58GHz.FirstOrDefault(); } else if (frequencyMapping == _58FrequencyMapping) { - SelectedFrequency24String = Frequencies24GHz.FirstOrDefault(); + SelectedFrequency24String = Frequencies24GHz.FirstOrDefault() ?? string.Empty; //SelectedPower = Power24GHz.FirstOrDefault(); } diff --git a/Companion/Views/HeaderView.axaml.cs b/Companion/Views/HeaderView.axaml.cs index c2cd2f5..f05876d 100644 --- a/Companion/Views/HeaderView.axaml.cs +++ b/Companion/Views/HeaderView.axaml.cs @@ -24,14 +24,14 @@ public HeaderView() private void TelegramButton_OnClick(object? sender, RoutedEventArgs e) { - var launcher = TopLevel.GetTopLevel(this).Launcher; - launcher.LaunchUriAsync(new Uri(TelegramLink)); + var launcher = TopLevel.GetTopLevel(this)?.Launcher; + _ = launcher?.LaunchUriAsync(new Uri(TelegramLink)); } private void GithubButton_OnClick(object? sender, RoutedEventArgs e) { - var launcher = TopLevel.GetTopLevel(this).Launcher; - launcher.LaunchUriAsync(new Uri(GithubLink)); + var launcher = TopLevel.GetTopLevel(this)?.Launcher; + _ = launcher?.LaunchUriAsync(new Uri(GithubLink)); } // private void DiscordButton_OnClick(object? sender, RoutedEventArgs e) @@ -39,4 +39,4 @@ private void GithubButton_OnClick(object? sender, RoutedEventArgs e) // var launcher = TopLevel.GetTopLevel(this).Launcher; // launcher.LaunchUriAsync(new Uri(DiscordLink)); // } -} \ No newline at end of file +} diff --git a/Companion/Views/PresetsTabView.axaml.cs b/Companion/Views/PresetsTabView.axaml.cs index bc197db..3455add 100644 --- a/Companion/Views/PresetsTabView.axaml.cs +++ b/Companion/Views/PresetsTabView.axaml.cs @@ -58,7 +58,7 @@ private void OnShowPresetDetailsClicked(object? sender, RoutedEventArgs e) } } - private void OnApplyPresetClicked(object? sender, RoutedEventArgs e) + private async void OnApplyPresetClicked(object? sender, RoutedEventArgs e) { // Get the Preset from the clicked button's DataContext var preset = (sender as Button)?.DataContext as Preset; @@ -67,9 +67,9 @@ private void OnApplyPresetClicked(object? sender, RoutedEventArgs e) if (DataContext is PresetsTabViewModel viewModel) { // Call the method to apply preset - viewModel.ApplyPresetAsync(preset); + await viewModel.ApplyPresetAsync(preset); } } -} \ No newline at end of file +}