diff --git a/src/MauiSherpa.Core/Interfaces.cs b/src/MauiSherpa.Core/Interfaces.cs index b98a9b2..9b4bf3f 100644 --- a/src/MauiSherpa.Core/Interfaces.cs +++ b/src/MauiSherpa.Core/Interfaces.cs @@ -1730,6 +1730,41 @@ public interface IDoctorService string GetDotNetExecutablePath(); } +public enum MobileDoctorCheckStatus +{ + Ok, + Warning, + Error +} + +public record MobileDoctorCheck( + string Name, + MobileDoctorCheckStatus Status, + string Summary, + IReadOnlyList? Details = null +); + +public record MobileDoctorSection( + string Title, + IReadOnlyList Checks +); + +public record MobileDoctorReport( + IReadOnlyList Sections, + DateTime Timestamp +) +{ + public int OkCount => Sections.Sum(section => section.Checks.Count(check => check.Status == MobileDoctorCheckStatus.Ok)); + public int WarningCount => Sections.Sum(section => section.Checks.Count(check => check.Status == MobileDoctorCheckStatus.Warning)); + public int ErrorCount => Sections.Sum(section => section.Checks.Count(check => check.Status == MobileDoctorCheckStatus.Error)); + public bool HasIssues => WarningCount > 0 || ErrorCount > 0; +} + +public interface IMobileDoctorService +{ + Task RunDoctorAsync(IProgress? progress = null); +} + // ============================================================================ // Process Execution Service - CLI Tool Execution with Terminal UI // ============================================================================ diff --git a/src/MauiSherpa.Core/MauiSherpa.Core.csproj b/src/MauiSherpa.Core/MauiSherpa.Core.csproj index 364b021..ccdfb92 100644 --- a/src/MauiSherpa.Core/MauiSherpa.Core.csproj +++ b/src/MauiSherpa.Core/MauiSherpa.Core.csproj @@ -1,7 +1,9 @@ - net10.0 + net10.0;net10.0-maccatalyst;net10.0-windows10.0.19041.0 + net10.0 + MauiSherpa enable enable true @@ -17,6 +19,14 @@ git + + $(DefineConstants);MOBILE_SHERPA + + + + $(DefineConstants);MAUI_SHERPA + + @@ -47,7 +57,7 @@ - + diff --git a/src/MauiSherpa.Core/Services/AppDataPath.cs b/src/MauiSherpa.Core/Services/AppDataPath.cs index b0188ad..34c3f02 100644 --- a/src/MauiSherpa.Core/Services/AppDataPath.cs +++ b/src/MauiSherpa.Core/Services/AppDataPath.cs @@ -32,7 +32,7 @@ public static string GetAppDataDirectory() baseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); } - _cachedPath = Path.Combine(baseDir, "MauiSherpa"); + _cachedPath = Path.Combine(baseDir, ProductInfo.AppDataDirectoryName); Directory.CreateDirectory(_cachedPath); return _cachedPath; } diff --git a/src/MauiSherpa.Core/Services/CopilotSystemPromptBuilder.cs b/src/MauiSherpa.Core/Services/CopilotSystemPromptBuilder.cs index 6d2d987..a4ff4d7 100644 --- a/src/MauiSherpa.Core/Services/CopilotSystemPromptBuilder.cs +++ b/src/MauiSherpa.Core/Services/CopilotSystemPromptBuilder.cs @@ -62,19 +62,13 @@ private static string GetAppVersion() private static string GetPlatform() { - if (OperatingSystem.IsMacCatalyst()) - return "macOS (Mac Catalyst)"; - - if (OperatingSystem.IsWindows()) - return "Windows"; - - if (OperatingSystem.IsMacOS()) - return "macOS"; - - if (OperatingSystem.IsLinux()) - return "Linux"; - +#if MACCATALYST + return "macOS (Mac Catalyst)"; +#elif WINDOWS + return "Windows"; +#else return "Unknown"; +#endif } /// @@ -82,10 +76,10 @@ private static string GetPlatform() /// private static string GetDefaultPrompt() { - return """ - # MAUI Sherpa Assistant + return $""" + # {ProductInfo.CopilotAssistantTitle} - You are MAUI Sherpa, an expert assistant for .NET MAUI mobile app development. + You are {ProductInfo.ApplicationTitle}, {ProductInfo.CopilotAssistantDescription} ## CRITICAL RULES diff --git a/src/MauiSherpa.Core/Services/MobileDoctorService.cs b/src/MauiSherpa.Core/Services/MobileDoctorService.cs new file mode 100644 index 0000000..5589198 --- /dev/null +++ b/src/MauiSherpa.Core/Services/MobileDoctorService.cs @@ -0,0 +1,225 @@ +using System.Diagnostics; +using MauiSherpa.Core.Interfaces; + +namespace MauiSherpa.Core.Services; + +public class MobileDoctorService : IMobileDoctorService +{ + private readonly IAndroidSdkSettingsService _sdkSettings; + private readonly IOpenJdkSettingsService _jdkSettings; + private readonly IXcodeService _xcodeService; + private readonly ISimulatorService _simulatorService; + private readonly IPlatformService _platform; + + public MobileDoctorService( + IAndroidSdkSettingsService sdkSettings, + IOpenJdkSettingsService jdkSettings, + IXcodeService xcodeService, + ISimulatorService simulatorService, + IPlatformService platform) + { + _sdkSettings = sdkSettings; + _jdkSettings = jdkSettings; + _xcodeService = xcodeService; + _simulatorService = simulatorService; + _platform = platform; + } + + public async Task RunDoctorAsync(IProgress? progress = null) + { + var sections = new List(); + + progress?.Report("Checking Android toolchain..."); + sections.Add(await BuildAndroidSectionAsync()); + + progress?.Report("Checking Java toolchain..."); + sections.Add(await BuildJdkSectionAsync()); + + if (_platform.IsMacOS || _platform.IsMacCatalyst) + { + progress?.Report("Checking Apple toolchain..."); + sections.Add(await BuildAppleSectionAsync()); + } + + progress?.Report("Mobile doctor complete"); + return new MobileDoctorReport(sections, DateTime.UtcNow); + } + + private async Task BuildAndroidSectionAsync() + { + var checks = new List(); + var sdkPath = await _sdkSettings.GetEffectiveSdkPathAsync(); + + if (string.IsNullOrWhiteSpace(sdkPath) || !Directory.Exists(sdkPath)) + { + checks.Add(new MobileDoctorCheck( + "Android SDK", + MobileDoctorCheckStatus.Error, + "Android SDK not found", + ["Set the SDK path in Settings or install the Android SDK."])); + + return new MobileDoctorSection("Android", checks); + } + + checks.Add(new MobileDoctorCheck( + "Android SDK", + MobileDoctorCheckStatus.Ok, + sdkPath, + [$"Path: {sdkPath}"])); + + var adbPath = AppDataPath.GetAdbPath(sdkPath); + checks.Add(new MobileDoctorCheck( + "Platform Tools", + File.Exists(adbPath) ? MobileDoctorCheckStatus.Ok : MobileDoctorCheckStatus.Error, + File.Exists(adbPath) ? "adb is available" : "adb not found", + [$"Expected at: {adbPath}"])); + + var emulatorDir = Path.Combine(sdkPath, "emulator"); + checks.Add(new MobileDoctorCheck( + "Android Emulator", + Directory.Exists(emulatorDir) ? MobileDoctorCheckStatus.Ok : MobileDoctorCheckStatus.Warning, + Directory.Exists(emulatorDir) ? "Emulator tools are installed" : "Emulator tools are not installed", + [$"Expected at: {emulatorDir}"])); + + return new MobileDoctorSection("Android", checks); + } + + private async Task BuildJdkSectionAsync() + { + var checks = new List(); + var jdkPath = await _jdkSettings.GetEffectiveJdkPathAsync(); + + if (string.IsNullOrWhiteSpace(jdkPath) || !Directory.Exists(jdkPath)) + { + checks.Add(new MobileDoctorCheck( + "OpenJDK", + MobileDoctorCheckStatus.Error, + "JDK not found", + ["Install OpenJDK or set a custom JDK path in Settings."])); + + return new MobileDoctorSection("Java", checks); + } + + var javaBinary = Path.Combine(jdkPath, "bin", OperatingSystem.IsWindows() ? "java.exe" : "java"); + var keytoolBinary = Path.Combine(jdkPath, "bin", OperatingSystem.IsWindows() ? "keytool.exe" : "keytool"); + + checks.Add(new MobileDoctorCheck( + "OpenJDK", + File.Exists(javaBinary) ? MobileDoctorCheckStatus.Ok : MobileDoctorCheckStatus.Warning, + File.Exists(javaBinary) ? "java is available" : "java binary not found", + [$"Path: {jdkPath}"])); + + checks.Add(new MobileDoctorCheck( + "Keytool", + File.Exists(keytoolBinary) ? MobileDoctorCheckStatus.Ok : MobileDoctorCheckStatus.Warning, + File.Exists(keytoolBinary) ? "keytool is available" : "keytool binary not found", + [$"Expected at: {keytoolBinary}"])); + + var version = await TryReadJavaVersionAsync(javaBinary); + if (!string.IsNullOrWhiteSpace(version)) + { + checks.Add(new MobileDoctorCheck( + "Java Version", + MobileDoctorCheckStatus.Ok, + version)); + } + + return new MobileDoctorSection("Java", checks); + } + + private async Task BuildAppleSectionAsync() + { + var checks = new List(); + + if (!_xcodeService.IsSupported) + { + checks.Add(new MobileDoctorCheck( + "Xcode", + MobileDoctorCheckStatus.Warning, + "Apple tooling is not supported on this platform")); + + return new MobileDoctorSection("Apple", checks); + } + + var selectedPath = await _xcodeService.GetSelectedXcodePathAsync(); + checks.Add(new MobileDoctorCheck( + "Selected Xcode", + string.IsNullOrWhiteSpace(selectedPath) ? MobileDoctorCheckStatus.Error : MobileDoctorCheckStatus.Ok, + string.IsNullOrWhiteSpace(selectedPath) ? "xcode-select is not configured" : selectedPath!, + string.IsNullOrWhiteSpace(selectedPath) + ? ["Run xcode-select -p or choose an Xcode installation from Xcode Management."] + : [$"Path: {selectedPath}"])); + + var installedXcodes = await _xcodeService.GetInstalledXcodesAsync(); + checks.Add(new MobileDoctorCheck( + "Installed Xcodes", + installedXcodes.Count > 0 ? MobileDoctorCheckStatus.Ok : MobileDoctorCheckStatus.Warning, + installedXcodes.Count > 0 ? $"{installedXcodes.Count} Xcode installation(s) found" : "No Xcode installations found")); + + if (_simulatorService.IsSupported) + { + try + { + var simulators = await _simulatorService.GetSimulatorsAsync(); + var runtimes = await _simulatorService.GetRuntimesAsync(); + + checks.Add(new MobileDoctorCheck( + "Simulators", + simulators.Count > 0 ? MobileDoctorCheckStatus.Ok : MobileDoctorCheckStatus.Warning, + simulators.Count > 0 ? $"{simulators.Count} simulator device(s) available" : "No simulator devices available")); + + checks.Add(new MobileDoctorCheck( + "Simulator Runtimes", + runtimes.Count > 0 ? MobileDoctorCheckStatus.Ok : MobileDoctorCheckStatus.Warning, + runtimes.Count > 0 ? $"{runtimes.Count} runtime(s) installed" : "No simulator runtimes found")); + } + catch (Exception ex) + { + checks.Add(new MobileDoctorCheck( + "Simulators", + MobileDoctorCheckStatus.Warning, + $"Could not query simulators: {ex.Message}")); + } + } + + return new MobileDoctorSection("Apple", checks); + } + + private static async Task TryReadJavaVersionAsync(string javaBinary) + { + if (!File.Exists(javaBinary)) + { + return null; + } + + try + { + var startInfo = new ProcessStartInfo + { + FileName = javaBinary, + Arguments = "-version", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(startInfo) ?? throw new InvalidOperationException($"Failed to start {javaBinary}"); + var output = await process.StandardError.ReadToEndAsync(); + if (string.IsNullOrWhiteSpace(output)) + { + output = await process.StandardOutput.ReadToEndAsync(); + } + + await process.WaitForExitAsync(); + return output + .Split('\n', StringSplitOptions.RemoveEmptyEntries) + .FirstOrDefault()? + .Trim(); + } + catch + { + return null; + } + } +} diff --git a/src/MauiSherpa.Core/Services/ProductInfo.cs b/src/MauiSherpa.Core/Services/ProductInfo.cs new file mode 100644 index 0000000..4c5dafd --- /dev/null +++ b/src/MauiSherpa.Core/Services/ProductInfo.cs @@ -0,0 +1,77 @@ +namespace MauiSherpa.Core.Services; + +public enum ProductEdition +{ + MauiSherpa, + MobileSherpa +} + +public sealed record ProductCapabilities( + bool HasMauiDoctor, + bool HasMobileDoctor, + bool HasAppInspector, + bool HasProfiling, + bool HasAndroidTooling, + bool HasAppleTooling, + bool HasSecretsPublishing, + bool HasPushTesting, + bool HasCopilot +); + +public static class ProductInfo +{ + public static ProductEdition Edition => +#if MOBILE_SHERPA + ProductEdition.MobileSherpa; +#else + ProductEdition.MauiSherpa; +#endif + + public static bool IsMobileSherpa => Edition == ProductEdition.MobileSherpa; + + public static string ApplicationTitle => IsMobileSherpa ? "Mobile Sherpa" : "MAUI Sherpa"; + + public static string ApplicationId => IsMobileSherpa ? "codes.redth.mobilesherpa" : "codes.redth.mauisherpa"; + + public static string AppDataDirectoryName => IsMobileSherpa ? "MobileSherpa" : "MauiSherpa"; + + public static string DashboardWelcomeMessage => IsMobileSherpa + ? "Let Mobile Sherpa guide your mobile development environment needs!" + : "Let .NET MAUI Sherpa guide your development environment needs!"; + + public static string DoctorRoute => IsMobileSherpa ? "/mobile-doctor" : "/doctor"; + + public static string DoctorNavHref => IsMobileSherpa ? "mobile-doctor" : "doctor"; + + public static string DoctorTitle => IsMobileSherpa ? "Mobile Doctor" : "Environment Doctor"; + + public static string CopilotAssistantTitle => IsMobileSherpa ? "Mobile Sherpa Assistant" : "MAUI Sherpa Assistant"; + + public static string CopilotAssistantDescription => IsMobileSherpa + ? "an expert assistant for mobile app development." + : "an expert assistant for .NET MAUI mobile app development."; + + public static ProductCapabilities Capabilities { get; } = CreateCapabilities(); + + private static ProductCapabilities CreateCapabilities() => IsMobileSherpa + ? new ProductCapabilities( + HasMauiDoctor: false, + HasMobileDoctor: true, + HasAppInspector: false, + HasProfiling: false, + HasAndroidTooling: true, + HasAppleTooling: true, + HasSecretsPublishing: true, + HasPushTesting: true, + HasCopilot: true) + : new ProductCapabilities( + HasMauiDoctor: true, + HasMobileDoctor: false, + HasAppInspector: true, + HasProfiling: true, + HasAndroidTooling: true, + HasAppleTooling: true, + HasSecretsPublishing: true, + HasPushTesting: true, + HasCopilot: true); +} diff --git a/src/MauiSherpa.Core/ViewModels/DashboardViewModel.cs b/src/MauiSherpa.Core/ViewModels/DashboardViewModel.cs index b31c96b..d0d3696 100644 --- a/src/MauiSherpa.Core/ViewModels/DashboardViewModel.cs +++ b/src/MauiSherpa.Core/ViewModels/DashboardViewModel.cs @@ -3,7 +3,7 @@ namespace MauiSherpa.Core.ViewModels; public class DashboardViewModel : ViewModelBase { public string Title => "Dashboard"; - public string WelcomeMessage => "Let .NET MAUI Sherpa guide your development environment needs!"; + public string WelcomeMessage => MauiSherpa.Core.Services.ProductInfo.DashboardWelcomeMessage; public DashboardViewModel(Interfaces.IAlertService? alertService = null, Interfaces.ILoggingService? loggingService = null) : base(alertService ?? new StubAlertService(), loggingService ?? new StubLoggingService()) diff --git a/src/MauiSherpa.LinuxGtk/MauiSherpa.LinuxGtk.csproj b/src/MauiSherpa.LinuxGtk/MauiSherpa.LinuxGtk.csproj index c68cde1..3b9174e 100644 --- a/src/MauiSherpa.LinuxGtk/MauiSherpa.LinuxGtk.csproj +++ b/src/MauiSherpa.LinuxGtk/MauiSherpa.LinuxGtk.csproj @@ -2,20 +2,26 @@ net10.0 + MauiSherpa Exe MauiSherpa enable enable false + true $(DefineConstants);LINUXGTK - codes.redth.mauisherpa - MAUI Sherpa + codes.redth.mobilesherpa + codes.redth.mauisherpa + Mobile Sherpa + MAUI Sherpa $(AppVersion) - Developer tools manager for .NET MAUI — manage Android SDK, Apple tools, keystores, and .NET workloads + Developer tools manager for mobile development — manage Android SDK, Apple tools, keystores, and device workflows + Developer tools manager for .NET MAUI — manage Android SDK, Apple tools, keystores, and .NET workloads Jon Dick https://github.com/Redth/MAUI.Sherpa Development%3BIDE%3BBuilding%3B - Manage Android SDK, Apple tools, keystores, and .NET MAUI dependencies + Manage Android SDK, Apple tools, keystores, and mobile development dependencies + Manage Android SDK, Apple tools, keystores, and .NET MAUI dependencies Developer Tools Manager devel Jon Dick <redth@users.noreply.github.com> @@ -25,6 +31,14 @@ $(RestoreSources);$(LocalGtkNugetSource) + + $(DefineConstants);MOBILE_SHERPA + + + + $(DefineConstants);MAUI_SHERPA + + @@ -45,7 +59,7 @@ - + diff --git a/src/MauiSherpa.MacOS/MacOSApp.cs b/src/MauiSherpa.MacOS/MacOSApp.cs index 9fb8d0d..c014e65 100644 --- a/src/MauiSherpa.MacOS/MacOSApp.cs +++ b/src/MauiSherpa.MacOS/MacOSApp.cs @@ -1,6 +1,7 @@ using Microsoft.Maui.Controls; using Microsoft.Maui.Platform.MacOS; using MauiSherpa.Core.Interfaces; +using MauiSherpa.Core.Services; using AppKit; using Foundation; @@ -140,17 +141,20 @@ void AddAppMenuItems(BlazorContentPage blazorPage) settingsItem.Target = settingsHandler; appMenu.InsertItem(settingsItem, insertIndex++); - var doctorHandler = new MenuActionHandler(() => blazorPage.NavigateToRoute("/doctor")); + var doctorHandler = new MenuActionHandler(() => blazorPage.NavigateToRoute(ProductInfo.DoctorRoute)); _menuHandlers.Add(doctorHandler); - var doctorItem = new NSMenuItem("Doctor", new ObjCRuntime.Selector("menuAction:"), ""); + var doctorItem = new NSMenuItem(ProductInfo.DoctorTitle, new ObjCRuntime.Selector("menuAction:"), ""); doctorItem.Target = doctorHandler; appMenu.InsertItem(doctorItem, insertIndex++); - var profilingHandler = new MenuActionHandler(() => blazorPage.NavigateToRoute("/profiling")); - _menuHandlers.Add(profilingHandler); - var profilingItem = new NSMenuItem("Profiling", new ObjCRuntime.Selector("menuAction:"), ""); - profilingItem.Target = profilingHandler; - appMenu.InsertItem(profilingItem, insertIndex++); + if (ProductInfo.Capabilities.HasProfiling) + { + var profilingHandler = new MenuActionHandler(() => blazorPage.NavigateToRoute("/profiling")); + _menuHandlers.Add(profilingHandler); + var profilingItem = new NSMenuItem("Profiling", new ObjCRuntime.Selector("menuAction:"), ""); + profilingItem.Target = profilingHandler; + appMenu.InsertItem(profilingItem, insertIndex++); + } var sep2 = NSMenuItem.SeparatorItem; appMenu.InsertItem(sep2, insertIndex); @@ -161,7 +165,7 @@ FlyoutPage CreateFlyoutPage(BlazorContentPage blazorPage) var flyoutPage = new FlyoutPage { Detail = new NavigationPage(blazorPage), - Flyout = new ContentPage { Title = "MAUI Sherpa" }, + Flyout = new ContentPage { Title = ProductInfo.ApplicationTitle }, FlyoutLayoutBehavior = FlyoutLayoutBehavior.Split, }; MacOSFlyoutPage.SetUseNativeSidebar(flyoutPage, true); @@ -206,19 +210,24 @@ FlyoutPage CreateFlyoutPage(BlazorContentPage blazorPage) new() { Title = "Publish", SystemImage = "square.and.arrow.up", Tag = "/secrets/publish" }, } }, - new MacOSSidebarItem - { - Title = "Tools", - Children = new List - { - new() { Title = "Profiling", SystemImage = "chart.bar.xaxis", Tag = "/profiling" }, - new() { Title = "App Inspector", SystemImage = "wand.and.stars", Tag = "/devflow" }, + }; + + var toolChildren = new List(); + if (ProductInfo.Capabilities.HasProfiling) + toolChildren.Add(new MacOSSidebarItem { Title = "Profiling", SystemImage = "chart.bar.xaxis", Tag = "/profiling" }); + if (ProductInfo.Capabilities.HasAppInspector) + toolChildren.Add(new MacOSSidebarItem { Title = "App Inspector", SystemImage = "wand.and.stars", Tag = "/devflow" }); #if DEBUG - new() { Title = "Debug UI", SystemImage = "ant", Tag = "/debug" }, + toolChildren.Add(new MacOSSidebarItem { Title = "Debug UI", SystemImage = "ant", Tag = "/debug" }); #endif - } - }, - }; + if (toolChildren.Count > 0) + { + sidebarItems.Add(new MacOSSidebarItem + { + Title = "Tools", + Children = toolChildren + }); + } MacOSFlyoutPage.SetSidebarItems(flyoutPage, sidebarItems); _sidebarItems = sidebarItems; diff --git a/src/MauiSherpa.MacOS/MacOSMauiProgram.cs b/src/MauiSherpa.MacOS/MacOSMauiProgram.cs index 5e887cb..0261bf5 100644 --- a/src/MauiSherpa.MacOS/MacOSMauiProgram.cs +++ b/src/MauiSherpa.MacOS/MacOSMauiProgram.cs @@ -110,6 +110,7 @@ public static MauiApp CreateMauiApp() builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/MauiSherpa.MacOS/MauiSherpa.MacOS.csproj b/src/MauiSherpa.MacOS/MauiSherpa.MacOS.csproj index a370b25..13bac06 100644 --- a/src/MauiSherpa.MacOS/MauiSherpa.MacOS.csproj +++ b/src/MauiSherpa.MacOS/MauiSherpa.MacOS.csproj @@ -2,6 +2,7 @@ net10.0-macos + MauiSherpa Exe MauiSherpa true @@ -12,8 +13,10 @@ 14.0 $(DefineConstants);MACOSAPP - MAUI Sherpa - codes.redth.mauisherpa + Mobile Sherpa + MAUI Sherpa + codes.redth.mobilesherpa + codes.redth.mauisherpa $(AppVersion) 1 @@ -25,6 +28,14 @@ Entitlements.Debug.plist + + $(DefineConstants);MOBILE_SHERPA + + + + $(DefineConstants);MAUI_SHERPA + + diff --git a/src/MauiSherpa/App.cs b/src/MauiSherpa/App.cs index 519a785..ec4cc21 100644 --- a/src/MauiSherpa/App.cs +++ b/src/MauiSherpa/App.cs @@ -1,4 +1,5 @@ using MauiSherpa.Core.Interfaces; +using MauiSherpa.Core.Services; namespace MauiSherpa; @@ -39,7 +40,7 @@ protected override Window CreateWindow(IActivationState? activationState) var window = new Window { - Title = "MAUI Sherpa", + Title = ProductInfo.ApplicationTitle, Page = new MainPage(splashService), Width = savedWidth, Height = savedHeight, diff --git a/src/MauiSherpa/Components/MainLayout.razor b/src/MauiSherpa/Components/MainLayout.razor index 22bde18..80ffddc 100644 --- a/src/MauiSherpa/Components/MainLayout.razor +++ b/src/MauiSherpa/Components/MainLayout.razor @@ -1,5 +1,6 @@ @inherits LayoutComponentBase @using MauiSherpa.Core.Interfaces +@using MauiSherpa.Core.Services @inject IThemeService ThemeService @inject IEncryptedSettingsService EncryptedSettings @inject ISplashService SplashService @@ -95,28 +96,37 @@ Publish - - - - Profiling - - - - App Inspector - - - @if (isDebugBuild) + @if (ShowToolsSection) { - - - Debug UI - + + @if (Capabilities.HasProfiling) + { + + + Profiling + + } + @if (Capabilities.HasAppInspector) + { + + + App Inspector + + } + + @if (isDebugBuild) + { + + + Debug UI + + } } @if (!PlatformInfo.HasNativeToolbar) {