From d339c1c0b6e1b428d1e56a4854c86c2cee2e6c52 Mon Sep 17 00:00:00 2001 From: Hamna Rauf Date: Thu, 12 Oct 2023 22:04:48 +0500 Subject: [PATCH 01/69] Make error messages selectable (#1668) --- tools/SetupFlow/DevHome.SetupFlow/Views/LoadingView.xaml | 2 +- tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/LoadingView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/LoadingView.xaml index 49edd0b17b..6a389674b6 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/LoadingView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/LoadingView.xaml @@ -142,7 +142,7 @@ - + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml index c8be61a069..7583b4fd5a 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml @@ -150,7 +150,7 @@ - + From 4d78487998330aeb7a80828e9057168c1249ae89 Mon Sep 17 00:00:00 2001 From: Darren Hoehna Date: Thu, 12 Oct 2023 10:39:47 -0700 Subject: [PATCH 02/69] First commit. (#1681) Co-authored-by: Darren Hoehna --- tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs index 2ea6cf2c1e..2a3ff42805 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs @@ -193,7 +193,7 @@ IAsyncOperation ISetupTask.Execute() { ProviderOperationResult result; Log.Logger?.ReportInfo(Log.Component.RepoConfig, $"Cloning repository {RepositoryToClone.DisplayName}"); - TelemetryFactory.Get().Log("CloneTask_CloneRepo_Event", LogLevel.Critical, new ReposCloneEvent(ProviderName, _developerId)); + TelemetryFactory.Get().Log("CloneTask_CloneRepo_Event", LogLevel.Critical, new ReposCloneEvent(ProviderName, _developerId), _activityId); if (RepositoryToClone.GetType() == typeof(GenericRepository)) { From 78804a12556956ab3047b870ad739eca84a5b3f6 Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:09:56 -0400 Subject: [PATCH 03/69] Add package checking to PackageDeploymentService (#1680) --- common/Helpers/Log.cs | 13 ++++ common/Services/IPackageDeploymentService.cs | 9 +++ common/Services/PackageDeploymentService.cs | 68 +++++++++++++++++ .../Helpers/WidgetServiceHelper.cs | 73 ------------------- .../ViewModels/DashboardViewModel.cs | 56 +++++++++++--- .../Views/DashboardView.xaml.cs | 6 +- 6 files changed, 137 insertions(+), 88 deletions(-) create mode 100644 common/Helpers/Log.cs delete mode 100644 tools/Dashboard/DevHome.Dashboard/Helpers/WidgetServiceHelper.cs diff --git a/common/Helpers/Log.cs b/common/Helpers/Log.cs new file mode 100644 index 0000000000..d789b69dc3 --- /dev/null +++ b/common/Helpers/Log.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using DevHome.Logging; + +namespace DevHome.Common.Helpers; + +public class Log +{ + private static readonly ComponentLogger _logger = new ("Common"); + + public static Logger? Logger() => _logger.Logger; +} diff --git a/common/Services/IPackageDeploymentService.cs b/common/Services/IPackageDeploymentService.cs index 5d7722bae3..de248c1abc 100644 --- a/common/Services/IPackageDeploymentService.cs +++ b/common/Services/IPackageDeploymentService.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Windows.ApplicationModel; using Windows.Management.Deployment; namespace DevHome.Common.Services; @@ -20,6 +22,13 @@ public interface IPackageDeploymentService /// Register package options /// Exception thrown if registration failed public Task RegisterPackageForCurrentUserAsync(string packageFamilyName, RegisterPackageOptions? options = null); + + /// + /// Find packages for the current user. If maxVersion is specified, package versions must be + /// between minVersion and maxVersion. If maxVersion is null, packages must be above minVersion. + /// + /// An IEnumerable containing the installed packages that meet the version criteria. + public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, params (Version minVersion, Version? maxVersion)[] ranges); } /// diff --git a/common/Services/PackageDeploymentService.cs b/common/Services/PackageDeploymentService.cs index f7e2554868..74d1bb698f 100644 --- a/common/Services/PackageDeploymentService.cs +++ b/common/Services/PackageDeploymentService.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using DevHome.Common.Exceptions; +using DevHome.Common.Helpers; +using Windows.ApplicationModel; using Windows.Management.Deployment; namespace DevHome.Common.Services; @@ -31,4 +34,69 @@ public async Task RegisterPackageForCurrentUserAsync(string packageFamilyName, R throw new RegisterPackageException(result.ErrorText, result.ExtendedErrorCode); } } + + /// + public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, params (Version minVersion, Version? maxVersion)[] ranges) + { + var packages = _packageManager.FindPackagesForUser(string.Empty, packageFamilyName); + if (packages.Any()) + { + var versionedPackages = new List(); + foreach (var package in packages) + { + var version = package.Id.Version; + var major = version.Major; + var minor = version.Minor; + + Log.Logger()?.ReportInfo("PackageDeploymentService", $"Found package {package.Id.FullName}"); + + // Create System.Version type from PackageVersion to test. System.Version supports CompareTo() for easy comparisons. + if (IsVersionSupported(new (major, minor), ranges)) + { + versionedPackages.Add(package); + } + } + + return versionedPackages; + } + else + { + // If there is no version installed at all, return the empty enumerable. + Log.Logger()?.ReportInfo("PackageDeploymentService", $"Found no installed version of {packageFamilyName}"); + return packages; + } + } + + /// + /// Tests whether a version is equal to or above the min, but less than the max. + /// + private bool IsVersionBetween(Version target, Version min, Version max) => target.CompareTo(min) >= 0 && target.CompareTo(max) < 0; + + /// + /// Tests whether a version is equal to or above the min. + /// + private bool IsVersionAtOrAbove(Version target, Version min) => target.CompareTo(min) >= 0; + + private bool IsVersionSupported(Version target, params (Version minVersion, Version? maxVersion)[] ranges) + { + foreach (var (minVersion, maxVersion) in ranges) + { + if (maxVersion == null) + { + if (IsVersionAtOrAbove(target, minVersion)) + { + return true; + } + } + else + { + if (IsVersionBetween(target, minVersion, maxVersion)) + { + return true; + } + } + } + + return false; + } } diff --git a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetServiceHelper.cs b/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetServiceHelper.cs deleted file mode 100644 index 6ea3b8a529..0000000000 --- a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetServiceHelper.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation and Contributors. -// Licensed under the MIT License. - -using System; -using System.Linq; -using Windows.Management.Deployment; - -namespace DevHome.Dashboard.Helpers; -internal class WidgetServiceHelper -{ - private readonly Version minSupportedVersion400 = new (423, 3800); - private readonly Version minSupportedVersion500 = new (523, 3300); - private readonly Version version500 = new (500, 0); - - private bool _validatedWebExpPack; - - public WidgetServiceHelper() - { - _validatedWebExpPack = false; - } - - public bool EnsureWebExperiencePack() - { - // If already validated there's a good version, don't check again. - if (_validatedWebExpPack) - { - return true; - } - - // Ensure the application is installed, and the version is high enough. - const string packageName = "MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy"; - - var packageManager = new PackageManager(); - var packages = packageManager.FindPackagesForUser(string.Empty, packageName); - if (packages.Any()) - { - // A user cannot actually have more than one version installed, so only need to look at the first result. - var package = packages.First(); - - var version = package.Id.Version; - var major = version.Major; - var minor = version.Minor; - - Log.Logger()?.ReportInfo("DashboardView", $"{package.Id.FullName} Version: {major}.{minor}"); - - // Create System.Version type from PackageVersion to test. System.Version supports CompareTo() for easy comparisons. - if (!IsVersionSupported(new (major, minor))) - { - return false; - } - } - else - { - // If there is no version installed at all. - return false; - } - - _validatedWebExpPack = true; - return _validatedWebExpPack; - } - - /// - /// Tests whether a version is equal to or above the min, but less than the max. - /// - private bool IsVersionBetween(Version target, Version min, Version max) => target.CompareTo(min) >= 0 && target.CompareTo(max) < 0; - - /// - /// Tests whether a version is equal to or above the min. - /// - private bool IsVersionAtOrAbove(Version target, Version min) => target.CompareTo(min) >= 0; - - private bool IsVersionSupported(Version target) => IsVersionBetween(target, minSupportedVersion400, version500) || IsVersionAtOrAbove(target, minSupportedVersion500); -} diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs index ee7c97d7cb..788f7869ef 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. using System; +using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using DevHome.Common.Services; using Microsoft.UI.Xaml; using Windows.Storage; using Windows.System; @@ -13,6 +15,15 @@ namespace DevHome.Dashboard.ViewModels; public partial class DashboardViewModel : ObservableObject { + private readonly IPackageDeploymentService _packageDeploymentService; + + private readonly Version minSupportedVersion400 = new (423, 3800); + private readonly Version minSupportedVersion500 = new (523, 3300); + private readonly Version version500 = new (500, 0); + + private bool _validatedWebExpPack; + + // Banner properties private const string _hideDashboardBannerKey = "HideDashboardBanner"; [ObservableProperty] @@ -21,11 +32,44 @@ public partial class DashboardViewModel : ObservableObject [ObservableProperty] private bool _isLoading; - public DashboardViewModel() + public DashboardViewModel(IPackageDeploymentService packageDeploymentService) { + _packageDeploymentService = packageDeploymentService; + ShowDashboardBanner = ShouldShowDashboardBanner(); } + public bool EnsureWebExperiencePack() + { + // If already validated there's a good version, don't check again. + if (_validatedWebExpPack) + { + return true; + } + + // Ensure the application is installed, and the version is high enough. + const string packageFamilyName = "MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy"; + var packages = _packageDeploymentService.FindPackagesForCurrentUser( + packageFamilyName, + (minSupportedVersion400, version500), + (minSupportedVersion500, null)); + _validatedWebExpPack = packages.Any(); + return _validatedWebExpPack; + } + + public Visibility GetNoWidgetMessageVisibility(int widgetCount, bool isLoading) + { + if (widgetCount == 0 && !isLoading) + { + return Visibility.Visible; + } + + return Visibility.Collapsed; + } + + // ============================================================================================= + // Banner methods + // ============================================================================================= [RelayCommand] private async Task DashboardBannerButtonAsync() { @@ -46,16 +90,6 @@ private bool ShouldShowDashboardBanner() return !roamingProperties.ContainsKey(_hideDashboardBannerKey); } - public Visibility GetNoWidgetMessageVisibility(int widgetCount, bool isLoading) - { - if (widgetCount == 0 && !isLoading) - { - return Visibility.Visible; - } - - return Visibility.Collapsed; - } - #if DEBUG public void ResetDashboardBanner() { diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs index c782fddd31..36af0c3b94 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs @@ -38,7 +38,6 @@ public partial class DashboardView : ToolPage private static AdaptiveCardRenderer _renderer; private static Microsoft.UI.Dispatching.DispatcherQueue _dispatcher; - private readonly WidgetServiceHelper _widgetServiceHelper; private readonly WidgetIconCache _widgetIconCache; private static bool _widgetHostInitialized; @@ -49,7 +48,6 @@ public partial class DashboardView : ToolPage public DashboardView() { ViewModel = Application.Current.GetService(); - _widgetServiceHelper = new WidgetServiceHelper(); this.InitializeComponent(); if (PinnedWidgets != null) @@ -70,7 +68,7 @@ public DashboardView() // If this is the first time initializing the Dashboard, or if initialization failed last time, initialize now. if (!_widgetHostInitialized) { - if (_widgetServiceHelper.EnsureWebExperiencePack()) + if (ViewModel.EnsureWebExperiencePack()) { _widgetHostInitialized = InitializeWidgetHost(); } @@ -271,7 +269,7 @@ public async Task AddWidgetClickAsync() // If this is the first time we're initializing the Dashboard, or if initialization failed last time, initialize now. if (!_widgetHostInitialized) { - if (_widgetServiceHelper.EnsureWebExperiencePack()) + if (ViewModel.EnsureWebExperiencePack()) { _widgetHostInitialized = InitializeWidgetHost(); await _widgetIconCache.CacheAllWidgetIconsAsync(_widgetCatalog); From df7dbbae03dac77af7551ac6b1183093a00c248e Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:22:50 -0400 Subject: [PATCH 04/69] Update .vsconfig (#1672) --- .vsconfig | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/.vsconfig b/.vsconfig index 42dd860ba9..7c3129ae0d 100644 --- a/.vsconfig +++ b/.vsconfig @@ -1,17 +1,45 @@ { "version": "1.0", "components": [ + "Microsoft.VisualStudio.Component.Roslyn.Compiler", "Microsoft.Component.MSBuild", + "Microsoft.VisualStudio.Component.Roslyn.LanguageServices", + "Microsoft.VisualStudio.Component.SQL.CLR", + "Microsoft.VisualStudio.Component.CoreEditor", + "Microsoft.VisualStudio.Workload.CoreEditor", + "Microsoft.Net.Component.4.8.SDK", + "Microsoft.Net.Component.4.7.2.TargetingPack", + "Microsoft.Net.ComponentGroup.DevelopmentPrerequisites", + "Microsoft.VisualStudio.Component.TypeScript.TSServer", + "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions", + "Microsoft.VisualStudio.Component.JavaScript.TypeScript", + "Microsoft.VisualStudio.Component.TextTemplating", + "Microsoft.VisualStudio.Component.NuGet", + "Microsoft.Component.ClickOnce", + "Microsoft.VisualStudio.Component.ManagedDesktop.Core", "Microsoft.NetCore.Component.Runtime.6.0", + "Microsoft.NetCore.Component.Runtime.7.0", "Microsoft.NetCore.Component.SDK", - "Microsoft.VisualStudio.Component.ManagedDesktop.Core", - "Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites", - "Microsoft.VisualStudio.Component.NuGet", + "Microsoft.VisualStudio.Component.FSharp", + "Microsoft.ComponentGroup.ClickOnce.Publish", + "Microsoft.NetCore.Component.DevelopmentTools", + "Microsoft.Net.Component.4.8.TargetingPack", + "Microsoft.Net.ComponentGroup.4.8.DeveloperTools", + "Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd", + "Microsoft.VisualStudio.Component.DiagnosticTools", + "Microsoft.VisualStudio.Component.EntityFramework", + "Microsoft.VisualStudio.Component.LiveUnitTesting", + "Microsoft.VisualStudio.Component.Debugger.JustInTime", + "Component.Microsoft.VisualStudio.LiveShare.2022", + "Microsoft.VisualStudio.Component.IntelliCode", "Microsoft.VisualStudio.Component.Windows10SDK", - "Microsoft.VisualStudio.Component.Windows10SDK.19041", - "Microsoft.VisualStudio.Component.Windows11SDK.22000", "Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging", + "Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites", + "Microsoft.VisualStudio.Component.DotNetModelBuilder", "Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs", - "Microsoft.VisualStudio.Workload.ManagedDesktop" + "Microsoft.ComponentGroup.Blend", + "Microsoft.VisualStudio.Workload.ManagedDesktop", + "Microsoft.VisualStudio.Component.Windows11SDK.22000", + "Microsoft.VisualStudio.Component.Windows10SDK.19041" ] } \ No newline at end of file From 3c1a81d089a6f838ec9d5f3ac6c9d278bcd88519 Mon Sep 17 00:00:00 2001 From: Felipe G Date: Thu, 12 Oct 2023 13:12:37 -0700 Subject: [PATCH 05/69] Add hostData size support for widgets (#1585) * adjustments after bug bash * Adding support to size in host data for widgets * Revert "adjustments after bug bash" This reverts commit ff491e47fe8ccb78ec3189bed2b54f89dc619012. * Moving object creation to close of where it will be used * fixing identation --------- Co-authored-by: Felipe da Conceicao Guimaraes --- .../DevHome.Dashboard/ViewModels/WidgetViewModel.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs index d0db93ada0..2e3bdcdf74 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs @@ -149,7 +149,15 @@ await Task.Run(async () => try { var template = new AdaptiveCardTemplate(cardTemplate); - var json = template.Expand(cardData); + + var hostData = new JsonObject + { + // TODO Add support to host theme in hostData + { "widgetSize", JsonValue.CreateStringValue(WidgetSize.ToString().ToLowerInvariant()) }, // "small", "medium" or "large" + }.ToString(); + + var context = new EvaluationContext(cardData, hostData); + var json = template.Expand(context); // Use custom parser. var elementParser = new AdaptiveElementParserRegistration(); From ce6eb456a97d37cb2f7a3c7f7fa8ac9f602fda10 Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:14:50 -0700 Subject: [PATCH 06/69] Eliminate type ambiguity in setup flow (#1699) * Eliminiate type ambiguity * TODO * Comment --- src/App.xaml.cs | 5 +++++ .../Extensions/ServiceExtensions.cs | 13 ++++++------- .../TaskGroups/DevDriveTaskGroup.cs | 7 ++----- .../TaskGroups/RepoConfigTaskGroup.cs | 14 +++++--------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/App.xaml.cs b/src/App.xaml.cs index bec8dd8e1e..f79eb56e94 100644 --- a/src/App.xaml.cs +++ b/src/App.xaml.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. +using System.Web.Services.Description; using DevHome.Activation; using DevHome.Common.Contracts; using DevHome.Common.Contracts.Services; @@ -55,6 +56,10 @@ public App() Host = Microsoft.Extensions.Hosting.Host. CreateDefaultBuilder(). UseContentRoot(AppContext.BaseDirectory). + UseDefaultServiceProvider((context, options) => + { + options.ValidateOnBuild = true; + }). ConfigureServices((context, services) => { // Default Activation Handler diff --git a/tools/SetupFlow/DevHome.SetupFlow/Extensions/ServiceExtensions.cs b/tools/SetupFlow/DevHome.SetupFlow/Extensions/ServiceExtensions.cs index 0d46223d5b..918ee3e956 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Extensions/ServiceExtensions.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Extensions/ServiceExtensions.cs @@ -48,10 +48,8 @@ private static IServiceCollection AddAppManagement(this IServiceCollection servi // View models services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); // Services @@ -96,7 +94,9 @@ private static IServiceCollection AddDevDrive(this IServiceCollection services) { // View models services.AddTransient(); - services.AddTransient(); + + // TODO https://github.com/microsoft/devhome/issues/631 + // services.AddTransient(); // Services services.AddTransient(); @@ -123,10 +123,9 @@ private static IServiceCollection AddMainPage(this IServiceCollection services) private static IServiceCollection AddRepoConfig(this IServiceCollection services) { - // View models - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + // TODO https://github.com/microsoft/devhome/issues/631 + // services.AddTransient(); + // services.AddTransient(); // Services services.AddTransient(); diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs index e43d63855c..5775c2f33a 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs @@ -25,11 +25,8 @@ public DevDriveTaskGroup(IHost host, ISetupFlowStringResource stringResource) { _host = host; - // TODO Remove `this` argument from CreateInstance since this task - // group is a registered type. This requires updating dependent classes - // correspondingly. - // https://github.com/microsoft/devhome/issues/631 - _devDriveReviewViewModel = new (() => _host.CreateInstance(this)); + // TODO https://github.com/microsoft/devhome/issues/631 + _devDriveReviewViewModel = new (() => new DevDriveReviewViewModel(host, stringResource, this)); _stringResource = stringResource; } diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/RepoConfigTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/RepoConfigTaskGroup.cs index e6fa7b846e..97ec6d064d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/RepoConfigTaskGroup.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/RepoConfigTaskGroup.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.IO; -using DevHome.Common.Extensions; +using DevHome.Common.Services; using DevHome.Common.TelemetryEvents.SetupFlow; using DevHome.SetupFlow.Common.Helpers; using DevHome.SetupFlow.Models; @@ -12,7 +12,6 @@ using DevHome.SetupFlow.ViewModels; using DevHome.Telemetry; using Microsoft.Extensions.Hosting; -using Microsoft.Windows.DevHome.SDK; namespace DevHome.SetupFlow.TaskGroups; @@ -28,17 +27,14 @@ public class RepoConfigTaskGroup : ISetupTaskGroup private readonly ISetupFlowStringResource _stringResource; - public RepoConfigTaskGroup(IHost host, ISetupFlowStringResource stringResource, SetupFlowOrchestrator setupFlowOrchestrator) + public RepoConfigTaskGroup(IHost host, ISetupFlowStringResource stringResource, SetupFlowOrchestrator setupFlowOrchestrator, IDevDriveManager devDriveManager) { _host = host; _stringResource = stringResource; - // TODO Remove `this` argument from CreateInstance since this task - // group is a registered type. This requires updating dependent classes - // correspondingly. - // https://github.com/microsoft/devhome/issues/631 - _repoConfigViewModel = new (() => _host.CreateInstance(this)); - _repoConfigReviewViewModel = new (() => _host.CreateInstance(this)); + // TODO https://github.com/microsoft/devhome/issues/631 + _repoConfigViewModel = new (() => new RepoConfigViewModel(stringResource, setupFlowOrchestrator, devDriveManager, this, host)); + _repoConfigReviewViewModel = new (() => new RepoConfigReviewViewModel(stringResource, this)); _activityId = setupFlowOrchestrator.ActivityId; } From 5bd4d570a48188958eadbabd9359ea32659c4bc4 Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:59:18 -0400 Subject: [PATCH 07/69] Update README.md (#1689) Co-authored-by: Hamna Rauf Co-authored-by: Kayla Cinnamon --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 236a7d5d3d..a7eb83b8fd 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ Related repositories include: > **Note**: Dev Home requires Windows 11 21H2 (build 22000) or later. -### Microsoft Store [Recommended] +If you are running Windows 11 23H2 (build 22621.2361) or later, you can install and run Dev Home just by finding it in the Start menu. -Install [Dev Home from the Microsoft Store](https://aka.ms/devhome). -This allows you to always be on the latest version when we release new builds with automatic upgrades. +Otherwise, you can install [Dev Home from the Microsoft Store](https://aka.ms/devhome). +This allows you to always be on the latest version when we release new builds with automatic upgrades. Note that widgets may not work on older versions of Windows. This is our preferred method. @@ -82,10 +82,9 @@ Please file new issues, feature requests, and suggestions but **DO search for si If you would like to ask a question that you feel doesn't warrant an issue (yet), please reach out to us via Twitter: -* [Kayla Cinnamon](https://github.com/cinnamon-msft), Product Manager: [@cinnamon_msft](https://twitter.com/cinnamon_msft) -* [Clint Rutkas](https://github.com/crutkas), Senior Product Manager: [@clintrutkas](https://twitter.com/clintrutkas) +* [Kayla Cinnamon](https://github.com/cinnamon-msft), Senior Product Manager: [@cinnamon_msft](https://twitter.com/cinnamon_msft) +* [Clint Rutkas](https://github.com/crutkas), Principal Product Manager: [@clintrutkas](https://twitter.com/clintrutkas) * [Leeza Mathew](https://github.com/mathewleeza), Engineering Lead: [@leezamathew](https://twitter.com/leezamathew) -* [Ujjwal Chadha](https://github.com/ujjwalchadha), Developer: [@ujjwalscript](https://twitter.com/ujjwalscript) ## Developer guidance @@ -95,13 +94,17 @@ If you would like to ask a question that you feel doesn't warrant an issue (yet) ## Building the code 1. Clone the repository -2. Configure your system, please use the [configuration file](.configurations/configuration.dsc.yaml). This can be applied by either: - * Dev Home's machine configuration tool - * WinGet configuration. If you have WinGet version [v1.6.2631 or later](https://github.com/microsoft/winget-cli/releases), run `winget configure .configurations/configuration.dsc.yaml` in an elevated shell from the project root so relative paths resolve correctly +2. Configure your system + * Please use the [configuration file](.configurations/configuration.dsc.yaml). This can be applied by either: + * Dev Home's machine configuration tool + * WinGet configuration. If you have WinGet version [v1.6.2631 or later](https://github.com/microsoft/winget-cli/releases), run `winget configure .configurations/configuration.dsc.yaml` in an elevated shell from the project root so relative paths resolve correctly + * Alternatively, if you already are running the minimum OS version, have Visual Studio installed, and have developer mode enabled, you may configure your Visual Studio directly via the .vsconfig file. To do this: + * Open the Visual Studio Installer, select “More” on your product card and then "Import configuration" + * Specify the .vsconfig file at the root of the repo and select “Review Details” ## Running & debugging -In Visual Studio, you should be able to build and debug Dev Home by hitting F5. Make sure to select either the "x64" or the "x86" platform and set DevHome as the selected startup project. +In Visual Studio, you should be able to build and debug Dev Home by hitting F5. Make sure to select either the `x64` or the `x86` platform and set DevHome as the selected startup project. --- From f4075d62ca8a9f86314b4e69289da21e21045515 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Thu, 19 Oct 2023 00:34:40 +0900 Subject: [PATCH 08/69] Fix typo in DevDriveUtil.cs (#1715) --- tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs index 901bd889d0..ee92bef09b 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs @@ -107,7 +107,7 @@ public static bool IsDevDriveFeatureEnabled } /// - /// Given a byte unit which is either GB or TB converts the value paramter to bytes. + /// Given a byte unit which is either GB or TB converts the value parameter to bytes. /// Since this is just for DevDrives the minimum size is known of value /// is 50 (50 gb) and its max size should be 64 (64 Tb) which is /// the max size that a vhdx file can be. From 42186f8d60f69634cc590c8570e3b3a4f2963d28 Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Wed, 18 Oct 2023 13:50:13 -0400 Subject: [PATCH 09/69] Move Dashboard calls to WidgetHost and WidgetCatalog to a WidgetHostingService (#1691) --- .../Extensions/ServiceExtensions.cs | 5 + .../Services/WidgetHostingService.cs | 52 ++++++++++ .../ViewModels/DashboardViewModel.cs | 6 +- .../Views/AddWidgetDialog.xaml.cs | 28 +++--- .../Views/CustomizeWidgetDialog.xaml.cs | 33 ++++--- .../Views/DashboardView.xaml.cs | 98 +++++++++---------- 6 files changed, 146 insertions(+), 76 deletions(-) create mode 100644 tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs diff --git a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs index fae7a0e336..d785b66cd6 100644 --- a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs +++ b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. +using DevHome.Dashboard.Services; using DevHome.Dashboard.ViewModels; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace DevHome.Settings.Extensions; + public static class ServiceExtensions { public static IServiceCollection AddDashboard(this IServiceCollection services, HostBuilderContext context) @@ -13,6 +15,9 @@ public static IServiceCollection AddDashboard(this IServiceCollection services, // View-models services.AddSingleton(); + // Services + services.AddSingleton(); + return services; } } diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs new file mode 100644 index 0000000000..8254e1ecec --- /dev/null +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using DevHome.Dashboard.Helpers; +using Microsoft.Windows.Widgets.Hosts; + +namespace DevHome.Dashboard.Services; + +public class WidgetHostingService +{ + private WidgetHost _widgetHost; + private WidgetCatalog _widgetCatalog; + + public WidgetHostingService() + { + } + + public WidgetHost GetWidgetHost() + { + if (_widgetHost == null) + { + try + { + _widgetHost = WidgetHost.Register(new WidgetHostContext("BAA93438-9B07-4554-AD09-7ACCD7D4F031")); + } + catch (Exception ex) + { + Log.Logger()?.ReportError("WidgetHostingService", "Exception in WidgetHost.Register:", ex); + } + } + + return _widgetHost; + } + + public WidgetCatalog GetWidgetCatalog() + { + if (_widgetCatalog == null) + { + try + { + _widgetCatalog = WidgetCatalog.GetDefault(); + } + catch (Exception ex) + { + Log.Logger()?.ReportError("WidgetHostingService", "Exception in WidgetCatalog.GetDefault:", ex); + } + } + + return _widgetCatalog; + } +} diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs index 788f7869ef..360eb29a28 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using DevHome.Common.Services; +using DevHome.Dashboard.Services; using Microsoft.UI.Xaml; using Windows.Storage; using Windows.System; @@ -15,6 +16,8 @@ namespace DevHome.Dashboard.ViewModels; public partial class DashboardViewModel : ObservableObject { + public WidgetHostingService WidgetHostingService { get; } + private readonly IPackageDeploymentService _packageDeploymentService; private readonly Version minSupportedVersion400 = new (423, 3800); @@ -32,9 +35,10 @@ public partial class DashboardViewModel : ObservableObject [ObservableProperty] private bool _isLoading; - public DashboardViewModel(IPackageDeploymentService packageDeploymentService) + public DashboardViewModel(IPackageDeploymentService packageDeploymentService, WidgetHostingService widgetHostingService) { _packageDeploymentService = packageDeploymentService; + WidgetHostingService = widgetHostingService; ShowDashboardBanner = ShouldShowDashboardBanner(); } diff --git a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs index 7ea030467c..eabccdba47 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs @@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.Input; using DevHome.Common.Extensions; using DevHome.Dashboard.Helpers; +using DevHome.Dashboard.Services; using DevHome.Dashboard.ViewModels; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; @@ -20,10 +21,9 @@ using WinUIEx; namespace DevHome.Dashboard.Views; + public sealed partial class AddWidgetDialog : ContentDialog { - private readonly WidgetHost _widgetHost; - private readonly WidgetCatalog _widgetCatalog; private Widget _currentWidget; private static DispatcherQueue _dispatcher; @@ -31,18 +31,18 @@ public sealed partial class AddWidgetDialog : ContentDialog public WidgetViewModel ViewModel { get; set; } + private readonly WidgetHostingService _hostingService; + public AddWidgetDialog( - WidgetHost host, - WidgetCatalog catalog, AdaptiveCardRenderer renderer, DispatcherQueue dispatcher, ElementTheme theme) { ViewModel = new WidgetViewModel(null, Microsoft.Windows.Widgets.WidgetSize.Large, null, renderer, dispatcher); + _hostingService = Application.Current.GetService(); + this.InitializeComponent(); - _widgetHost = host; - _widgetCatalog = catalog; _dispatcher = dispatcher; // Strange behavior: just setting the requested theme when we new-up the dialog results in @@ -52,7 +52,7 @@ public AddWidgetDialog( // Get the application root window so we know when it has closed. Application.Current.GetService().Closed += OnMainWindowClosed; - _widgetCatalog.WidgetDefinitionDeleted += WidgetCatalog_WidgetDefinitionDeleted; + _hostingService.GetWidgetCatalog()!.WidgetDefinitionDeleted += WidgetCatalog_WidgetDefinitionDeleted; } [RelayCommand] @@ -66,15 +66,15 @@ private async Task FillAvailableWidgetsAsync() { AddWidgetNavigationView.MenuItems.Clear(); - if (_widgetCatalog is null) + if (_hostingService.GetWidgetCatalog() is null) { // We should never have gotten here if we don't have a WidgetCatalog. Log.Logger()?.ReportError("AddWidgetDialog", $"Opened the AddWidgetDialog, but WidgetCatalog is null."); return; } - var providerDefs = _widgetCatalog.GetProviderDefinitions(); - var widgetDefs = _widgetCatalog.GetWidgetDefinitions(); + var providerDefs = _hostingService.GetWidgetCatalog()!.GetProviderDefinitions(); + var widgetDefs = _hostingService.GetWidgetCatalog()!.GetWidgetDefinitions(); Log.Logger()?.ReportInfo("AddWidgetDialog", $"Filling available widget list, found {providerDefs.Length} providers and {widgetDefs.Length} widgets"); @@ -169,7 +169,7 @@ private bool IsSingleInstanceAndAlreadyPinned(WidgetDefinition widgetDef) // If a WidgetDefinition has AllowMultiple = false, only one of that widget can be pinned at one time. if (!widgetDef.AllowMultiple) { - var currentlyPinnedWidgets = _widgetHost.GetWidgets(); + var currentlyPinnedWidgets = _hostingService.GetWidgetHost()?.GetWidgets(); if (currentlyPinnedWidgets != null) { foreach (var pinnedWidget in currentlyPinnedWidgets) @@ -228,7 +228,7 @@ private async void AddWidgetNavigationView_SelectionChanged( // Create the widget for configuration. We will need to delete it if the user closes the dialog // without pinning, or selects a different widget. - var widget = await _widgetHost.CreateWidgetAsync(selectedWidgetDefinition.Id, size); + var widget = await _hostingService.GetWidgetHost()?.CreateWidgetAsync(selectedWidgetDefinition.Id, size); if (widget is not null) { Log.Logger()?.ReportInfo("AddWidgetDialog", $"Created Widget {widget.Id}"); @@ -237,7 +237,7 @@ private async void AddWidgetNavigationView_SelectionChanged( ViewModel.IsInAddMode = true; PinButton.Visibility = Visibility.Visible; - var widgetDefinition = _widgetCatalog.GetWidgetDefinition(widget.DefinitionId); + var widgetDefinition = _hostingService.GetWidgetCatalog()!.GetWidgetDefinition(widget.DefinitionId); ViewModel.WidgetDefinition = widgetDefinition; clearWidgetTask.Wait(); @@ -281,7 +281,7 @@ private void HideDialog() ViewModel = null; Application.Current.GetService().Closed -= OnMainWindowClosed; - _widgetCatalog.WidgetDefinitionDeleted -= WidgetCatalog_WidgetDefinitionDeleted; + _hostingService.GetWidgetCatalog()!.WidgetDefinitionDeleted -= WidgetCatalog_WidgetDefinitionDeleted; this.Hide(); } diff --git a/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs index 891b73dd88..fc2a0deecb 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs @@ -5,6 +5,7 @@ using AdaptiveCards.Rendering.WinUI3; using DevHome.Common.Extensions; using DevHome.Dashboard.Helpers; +using DevHome.Dashboard.Services; using DevHome.Dashboard.ViewModels; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; @@ -13,32 +14,34 @@ using WinUIEx; namespace DevHome.Dashboard.Views; + public sealed partial class CustomizeWidgetDialog : ContentDialog { public Widget EditedWidget { get; set; } public WidgetViewModel ViewModel { get; set; } + private readonly WidgetHostingService _hostingService; + private readonly WidgetDefinition _widgetDefinition; - private readonly WidgetHost _widgetHost; - private readonly WidgetCatalog _widgetCatalog; private static DispatcherQueue _dispatcher; - public CustomizeWidgetDialog(WidgetHost host, WidgetCatalog catalog, AdaptiveCardRenderer renderer, DispatcherQueue dispatcher, WidgetDefinition widgetDefinition) + public CustomizeWidgetDialog(AdaptiveCardRenderer renderer, DispatcherQueue dispatcher, WidgetDefinition widgetDefinition) { ViewModel = new WidgetViewModel(null, Microsoft.Windows.Widgets.WidgetSize.Large, widgetDefinition, renderer, dispatcher); ViewModel.IsInEditMode = true; + + _hostingService = Application.Current.GetService(); + this.InitializeComponent(); - _widgetHost = host; - _widgetCatalog = catalog; _widgetDefinition = widgetDefinition; _dispatcher = dispatcher; // Get the application root window so we know when it has closed. Application.Current.GetService().Closed += OnMainWindowClosed; - _widgetCatalog.WidgetDefinitionDeleted += WidgetCatalog_WidgetDefinitionDeleted; + _hostingService.GetWidgetCatalog()!.WidgetDefinitionDeleted += WidgetCatalog_WidgetDefinitionDeleted; this.Loaded += InitializeWidgetCustomization; } @@ -48,10 +51,16 @@ private async void InitializeWidgetCustomization(object sender, RoutedEventArgs var size = WidgetHelpers.GetLargestCapabilitySize(_widgetDefinition.GetWidgetCapabilities()); // Create the widget for configuration. We will need to delete it if the dialog is closed without updating. - var widget = await _widgetHost.CreateWidgetAsync(_widgetDefinition.Id, size); - Log.Logger()?.ReportInfo("CustomizeWidgetDialog", $"Created Widget {widget.Id}"); - - ViewModel.Widget = widget; + var widget = await _hostingService.GetWidgetHost()?.CreateWidgetAsync(_widgetDefinition.Id, size); + if (widget != null) + { + Log.Logger()?.ReportInfo("CustomizeWidgetDialog", $"Created Widget {widget.Id}"); + ViewModel.Widget = widget; + } + else + { + Log.Logger()?.ReportError("CustomizeWidgetDialog", $"Error creating Widget {_widgetDefinition.Id}"); + } } private void UpdateWidgetButton_Click(object sender, RoutedEventArgs e) @@ -75,7 +84,7 @@ private async void CancelButton_Click(object sender, RoutedEventArgs e) private void HideDialog() { Application.Current.GetService().Closed -= OnMainWindowClosed; - _widgetCatalog.WidgetDefinitionDeleted -= WidgetCatalog_WidgetDefinitionDeleted; + _hostingService.GetWidgetCatalog()!.WidgetDefinitionDeleted -= WidgetCatalog_WidgetDefinitionDeleted; ViewModel.IsInEditMode = false; this.Hide(); @@ -95,7 +104,7 @@ private void WidgetCatalog_WidgetDefinitionDeleted(WidgetCatalog sender, WidgetD { Log.Logger()?.ReportDebug("CustomizeWidgetDialog", $"Exiting dialog, widget definition was deleted"); EditedWidget = null; - _widgetCatalog.WidgetDefinitionDeleted -= WidgetCatalog_WidgetDefinitionDeleted; + _hostingService.GetWidgetCatalog()!.WidgetDefinitionDeleted -= WidgetCatalog_WidgetDefinitionDeleted; _dispatcher.TryEnqueue(() => { this.Hide(); diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs index 36af0c3b94..d3c6791946 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs @@ -25,6 +25,7 @@ using Windows.System; namespace DevHome.Dashboard.Views; + public partial class DashboardView : ToolPage { public override string ShortName => "Dashboard"; @@ -33,8 +34,6 @@ public partial class DashboardView : ToolPage public static ObservableCollection PinnedWidgets { get; set; } - private static WidgetHost _widgetHost; - private static WidgetCatalog _widgetCatalog; private static AdaptiveCardRenderer _renderer; private static Microsoft.UI.Dispatching.DispatcherQueue _dispatcher; @@ -48,6 +47,7 @@ public partial class DashboardView : ToolPage public DashboardView() { ViewModel = Application.Current.GetService(); + this.InitializeComponent(); if (PinnedWidgets != null) @@ -65,48 +65,28 @@ public DashboardView() ActualThemeChanged += OnActualThemeChanged; - // If this is the first time initializing the Dashboard, or if initialization failed last time, initialize now. - if (!_widgetHostInitialized) - { - if (ViewModel.EnsureWebExperiencePack()) - { - _widgetHostInitialized = InitializeWidgetHost(); - } - } - - if (_widgetHostInitialized) - { - Loaded += OnLoaded; - } - else - { - Log.Logger()?.ReportWarn("DashboardView", $"Initialization failed"); - } + Loaded += OnLoaded; #if DEBUG Loaded += AddResetButton; #endif } - private bool InitializeWidgetHost() + private bool SubscribeToWidgetCatalogEvents() { - Log.Logger()?.ReportInfo("DashboardView", "Register with WidgetHost"); + Log.Logger()?.ReportInfo("DashboardView", "SubscribeToWidgetCatalogEvents"); try { - // The GUID is Dev Home's Host GUID that Widget Platform will use to identify this host. - _widgetHost = WidgetHost.Register(new WidgetHostContext("BAA93438-9B07-4554-AD09-7ACCD7D4F031")); - _widgetCatalog = WidgetCatalog.GetDefault(); - - _widgetCatalog.WidgetProviderDefinitionAdded += WidgetCatalog_WidgetProviderDefinitionAdded; - _widgetCatalog.WidgetProviderDefinitionDeleted += WidgetCatalog_WidgetProviderDefinitionDeleted; - _widgetCatalog.WidgetDefinitionAdded += WidgetCatalog_WidgetDefinitionAdded; - _widgetCatalog.WidgetDefinitionUpdated += WidgetCatalog_WidgetDefinitionUpdated; - _widgetCatalog.WidgetDefinitionDeleted += WidgetCatalog_WidgetDefinitionDeleted; + ViewModel.WidgetHostingService.GetWidgetCatalog()!.WidgetProviderDefinitionAdded += WidgetCatalog_WidgetProviderDefinitionAdded; + ViewModel.WidgetHostingService.GetWidgetCatalog()!.WidgetProviderDefinitionDeleted += WidgetCatalog_WidgetProviderDefinitionDeleted; + ViewModel.WidgetHostingService.GetWidgetCatalog()!.WidgetDefinitionAdded += WidgetCatalog_WidgetDefinitionAdded; + ViewModel.WidgetHostingService.GetWidgetCatalog()!.WidgetDefinitionUpdated += WidgetCatalog_WidgetDefinitionUpdated; + ViewModel.WidgetHostingService.GetWidgetCatalog()!.WidgetDefinitionDeleted += WidgetCatalog_WidgetDefinitionDeleted; } catch (Exception ex) { - Log.Logger()?.ReportError("DashboardView", "Exception in InitializeWidgetHost:", ex); + Log.Logger()?.ReportError("DashboardView", "Exception in SubscribeToWidgetCatalogEvents:", ex); return false; } @@ -172,25 +152,50 @@ private async Task ConfigureWidgetRenderer(AdaptiveCardRenderer renderer) } private async void OnLoaded(object sender, RoutedEventArgs e) + { + await InitializeDashboard(); + } + + private bool EnsureHostingInitialized() + { + if (_widgetHostInitialized) + { + return _widgetHostInitialized; + } + + _widgetHostInitialized = ViewModel.EnsureWebExperiencePack() && ViewModel.WidgetHostingService.GetWidgetCatalog() != null && SubscribeToWidgetCatalogEvents(); + + return _widgetHostInitialized; + } + + private async Task InitializeDashboard() { LoadingWidgetsProgressRing.Visibility = Visibility.Visible; ViewModel.IsLoading = true; - // Cache the widget icons before we display the widgets, since we include the icons in the widgets. - await _widgetIconCache.CacheAllWidgetIconsAsync(_widgetCatalog); - - await ConfigureWidgetRenderer(_renderer); + if (EnsureHostingInitialized()) + { + // Cache the widget icons before we display the widgets, since we include the icons in the widgets. + await _widgetIconCache.CacheAllWidgetIconsAsync(ViewModel.WidgetHostingService.GetWidgetCatalog()!); - await RestorePinnedWidgetsAsync(); + await ConfigureWidgetRenderer(_renderer); + await RestorePinnedWidgetsAsync(); + } + else + { + Log.Logger()?.ReportWarn("DashboardView", $"Initialization failed"); + } LoadingWidgetsProgressRing.Visibility = Visibility.Collapsed; ViewModel.IsLoading = false; + + return _widgetHostInitialized; } private async Task RestorePinnedWidgetsAsync() { Log.Logger()?.ReportInfo("DashboardView", "Get widgets for current host"); - var pinnedWidgets = _widgetHost.GetWidgets(); + var pinnedWidgets = ViewModel.WidgetHostingService.GetWidgetHost()?.GetWidgets(); if (pinnedWidgets != null) { Log.Logger()?.ReportInfo("DashboardView", $"Found {pinnedWidgets.Length} widgets for this host"); @@ -269,13 +274,8 @@ public async Task AddWidgetClickAsync() // If this is the first time we're initializing the Dashboard, or if initialization failed last time, initialize now. if (!_widgetHostInitialized) { - if (ViewModel.EnsureWebExperiencePack()) - { - _widgetHostInitialized = InitializeWidgetHost(); - await _widgetIconCache.CacheAllWidgetIconsAsync(_widgetCatalog); - await ConfigureWidgetRenderer(_renderer); - } - else + var initialized = await InitializeDashboard(); + if (!initialized) { var resourceLoader = new Microsoft.Windows.ApplicationModel.Resources.ResourceLoader("DevHome.Dashboard.pri", "DevHome.Dashboard/Resources"); @@ -299,7 +299,7 @@ public async Task AddWidgetClickAsync() } var configurationRenderer = await GetConfigurationRendererAsync(); - var dialog = new AddWidgetDialog(_widgetHost, _widgetCatalog, configurationRenderer, _dispatcher, ActualTheme) + var dialog = new AddWidgetDialog(configurationRenderer, _dispatcher, ActualTheme) { // XamlRoot must be set in the case of a ContentDialog running in a Desktop app. XamlRoot = this.XamlRoot, @@ -318,7 +318,7 @@ public async Task AddWidgetClickAsync() await newWidget.SetCustomStateAsync(newCustomState); // Put new widget on the Dashboard. - var widgetDef = _widgetCatalog.GetWidgetDefinition(newWidget.DefinitionId); + var widgetDef = ViewModel.WidgetHostingService.GetWidgetCatalog()!.GetWidgetDefinition(newWidget.DefinitionId); if (widgetDef is not null) { var size = WidgetHelpers.GetDefaultWidgetSize(widgetDef.GetWidgetCapabilities()); @@ -334,7 +334,7 @@ await Task.Run(async () => { var widgetDefinitionId = widget.DefinitionId; var widgetId = widget.Id; - var widgetDefinition = _widgetCatalog.GetWidgetDefinition(widgetDefinitionId); + var widgetDefinition = ViewModel.WidgetHostingService.GetWidgetCatalog()!.GetWidgetDefinition(widgetDefinitionId); if (widgetDefinition != null) { @@ -499,10 +499,10 @@ private async Task EditWidget(WidgetViewModel widgetViewModel) // Get info about the widget we're "editing". var index = PinnedWidgets.IndexOf(widgetViewModel); var originalSize = widgetViewModel.WidgetSize; - var widgetDef = _widgetCatalog.GetWidgetDefinition(widgetViewModel.Widget.DefinitionId); + var widgetDef = ViewModel.WidgetHostingService.GetWidgetCatalog()!.GetWidgetDefinition(widgetViewModel.Widget.DefinitionId); var configurationRenderer = await GetConfigurationRendererAsync(); - var dialog = new CustomizeWidgetDialog(_widgetHost, _widgetCatalog, configurationRenderer, _dispatcher, widgetDef) + var dialog = new CustomizeWidgetDialog(configurationRenderer, _dispatcher, widgetDef) { // XamlRoot must be set in the case of a ContentDialog running in a Desktop app. XamlRoot = this.XamlRoot, From b7f9cb695b893920a7bfca312a3077d63076a434 Mon Sep 17 00:00:00 2001 From: Darren Hoehna Date: Wed, 18 Oct 2023 11:48:25 -0700 Subject: [PATCH 10/69] Getting accounts for private urls (#1711) * Hopefully moving a method to the view model. * Moving to repo screen after logging in. * WIP * Works. Now to make it cleaner. * This should be working. * Using while loops and not if blocks * Adding comments. * More comments * More comments and some code fixes * Getting things to build * Oops * Adding the ability to cancel the login dialog. * Adding DeveloperId to some method names. * Update tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com> * Addressing comments --------- Co-authored-by: Darren Hoehna Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com> --- .../{ProviderEvent.cs => ExtensionEvent.cs} | 4 +- .../AddRepoDialogTests.cs | 24 ++ .../Models/RepositoryProvider.cs | 88 ++--- .../Models/RepositoryProviders.cs | 34 +- .../Services/StringResourceKey.cs | 2 + .../Strings/en-us/Resources.resw | 8 + .../ViewModels/AddRepoViewModel.cs | 334 +++++++++++++----- .../ViewModels/LoadingViewModel.cs | 4 - .../ViewModels/RepoConfigViewModel.cs | 6 + .../Views/AddRepoDialog.xaml | 101 +++--- .../Views/AddRepoDialog.xaml.cs | 130 +++++-- .../Views/RepoConfigView.xaml.cs | 11 +- 12 files changed, 538 insertions(+), 208 deletions(-) rename common/TelemetryEvents/SetupFlow/RepoTool/{ProviderEvent.cs => ExtensionEvent.cs} (85%) create mode 100644 tools/SetupFlow/DevHome.SetupFlow.UnitTest/AddRepoDialogTests.cs diff --git a/common/TelemetryEvents/SetupFlow/RepoTool/ProviderEvent.cs b/common/TelemetryEvents/SetupFlow/RepoTool/ExtensionEvent.cs similarity index 85% rename from common/TelemetryEvents/SetupFlow/RepoTool/ProviderEvent.cs rename to common/TelemetryEvents/SetupFlow/RepoTool/ExtensionEvent.cs index bbd74b31da..77e7969fe5 100644 --- a/common/TelemetryEvents/SetupFlow/RepoTool/ProviderEvent.cs +++ b/common/TelemetryEvents/SetupFlow/RepoTool/ExtensionEvent.cs @@ -10,7 +10,7 @@ namespace DevHome.Common.TelemetryEvents.SetupFlow; [EventData] -public class ProviderEvent : EventBase +public class ExtensionEvent : EventBase { public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServiceUsage; @@ -19,7 +19,7 @@ public int NumberOfProviders get; } - public ProviderEvent(int numberOfProviders) + public ExtensionEvent(int numberOfProviders) { NumberOfProviders = numberOfProviders; } diff --git a/tools/SetupFlow/DevHome.SetupFlow.UnitTest/AddRepoDialogTests.cs b/tools/SetupFlow/DevHome.SetupFlow.UnitTest/AddRepoDialogTests.cs new file mode 100644 index 0000000000..772c52b4d4 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow.UnitTest/AddRepoDialogTests.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using DevHome.Common.Extensions; +using DevHome.SetupFlow.ViewModels; + +namespace DevHome.SetupFlow.UnitTest; + +[TestClass] +public class AddRepoDialogTests : BaseSetupFlowTest +{ + [TestMethod] + [Ignore("AddRepoViewModel's constructor accepts a non-service known item.")] + public void HideRetryBannerTest() + { + var addRepoViewModel = TestHost!.GetService(); + + addRepoViewModel.RepoProviderSelectedCommand.Execute(null); + Assert.IsFalse(addRepoViewModel.ShouldEnablePrimaryButton); + + addRepoViewModel.RepoProviderSelectedCommand.Execute("ThisIsATest"); + Assert.IsTrue(addRepoViewModel.ShouldEnablePrimaryButton); + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs index 23dde2549a..c75e9b5d22 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs @@ -8,16 +8,20 @@ using AdaptiveCards.Rendering.WinUI3; using DevHome.Common.Renderers; using DevHome.Common.Services; +using DevHome.Common.TelemetryEvents.DeveloperId; using DevHome.Common.TelemetryEvents.SetupFlow; using DevHome.Common.Views; using DevHome.Logging; using DevHome.Settings.Views; using DevHome.SetupFlow.Common.Helpers; using DevHome.Telemetry; +using Microsoft.UI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Windows.DevHome.SDK; +using Windows.Foundation; using Windows.Storage; +using Windows.UI.ViewManagement; namespace DevHome.SetupFlow.Models; @@ -58,6 +62,8 @@ public RepositoryProvider(IExtensionWrapper extensionWrapper) public string DisplayName => _repositoryProvider.DisplayName; + public string ExtensionDisplayName => _extensionWrapper.Name; + /// /// Starts the extension if it isn't running. /// @@ -68,7 +74,6 @@ public void StartIfNotRunning() Log.Logger?.ReportInfo(Log.Component.RepoConfig, "Starting DevId and Repository provider extensions"); _devIdProvider = Task.Run(() => _extensionWrapper.GetProviderAsync()).Result; _repositoryProvider = Task.Run(() => _extensionWrapper.GetProviderAsync()).Result; - var myName = _repositoryProvider.DisplayName; } public IRepositoryProvider GetProvider() @@ -76,6 +81,18 @@ public IRepositoryProvider GetProvider() return _repositoryProvider; } + /// + /// Assigns handler as the event handler for the developerIdProvider. + /// + /// The method to run. + public void SetChangedEvent(TypedEventHandler handler) + { + if (_devIdProvider != null) + { + _devIdProvider.Changed += handler; + } + } + /// /// Tries to parse the repo name from the URI and makes a Repository from it. /// @@ -98,7 +115,9 @@ public IRepository GetRepositoryFromUri(Uri uri, IDeveloperId developerId = null if (getResult.Result.Status == ProviderOperationStatus.Failure) { - throw getResult.Result.ExtendedError; + Log.Logger?.ReportInfo(Log.Component.RepoConfig, "Could not get repo from Uri."); + Log.Logger?.ReportInfo(Log.Component.RepoConfig, getResult.Result.DisplayMessage); + return null; } return getResult.Repository; @@ -108,50 +127,23 @@ public IRepository GetRepositoryFromUri(Uri uri, IDeveloperId developerId = null /// Checks with the provider if it understands and can clone a repo via Uri. /// /// The uri to the repository - /// A tuple that containes if the provider can parse the uri and the account it can parse with. - /// If the provider can't parse the Uri, this will try a second time with any logged in accounts. If the repo is - /// public, the developerid can be null. - public (bool, IDeveloperId, IRepositoryProvider) IsUriSupported(Uri uri) + /// True if this provider supports the url. False otherwise. + public bool IsUriSupported(Uri uri) { - var developerIdsResult = _devIdProvider.GetLoggedInDeveloperIds(); - - // Possible that no accounts are loggd in. Try in case the repo is public. - if (developerIdsResult.Result.Status != ProviderOperationStatus.Success) - { - Log.Logger?.ReportError(Log.Component.RepoConfig, $"Could not get logged in accounts. Message: {developerIdsResult.Result.DisplayMessage}", developerIdsResult.Result.ExtendedError); - var uriSupportResult = Task.Run(() => _repositoryProvider.IsUriSupportedAsync(uri).AsTask()).Result; - if (uriSupportResult.IsSupported) - { - return (true, null, _repositoryProvider); - } - } - else + var uriSupportResult = Task.Run(() => _repositoryProvider.IsUriSupportedAsync(uri).AsTask()).Result; + if (uriSupportResult.Result.Status == ProviderOperationStatus.Failure) { - if (developerIdsResult.DeveloperIds.Any()) - { - foreach (var developerId in developerIdsResult.DeveloperIds) - { - var uriSupportResult = Task.Run(() => _repositoryProvider.IsUriSupportedAsync(uri, developerId).AsTask()).Result; - if (uriSupportResult.IsSupported) - { - return (true, developerId, _repositoryProvider); - } - } - } - else - { - var uriSupportResult = Task.Run(() => _repositoryProvider.IsUriSupportedAsync(uri).AsTask()).Result; - if (uriSupportResult.IsSupported) - { - return (true, null, _repositoryProvider); - } - } + return false; } - // no accounts can access this uri or the repo does not exist. - return (false, null, null); + return uriSupportResult.IsSupported; } + /// + /// Gets and configures the UI to show to the user for logging them in. + /// + /// The theme to use. + /// The adaptive panel to show to the user. Can be null. public ExtensionAdaptiveCardPanel GetLoginUi(ElementTheme elementTheme) { try @@ -182,6 +174,12 @@ public ExtensionAdaptiveCardPanel GetLoginUi(ElementTheme elementTheme) return null; } + /// + /// Sets the renderer in the UI. + /// + /// The ui to show + /// The theme to use + /// A task to await on. private async Task ConfigureLoginUIRenderer(AdaptiveCardRenderer renderer, ElementTheme elementTheme) { var dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); @@ -217,6 +215,16 @@ private async Task ConfigureLoginUIRenderer(AdaptiveCardRenderer renderer, Eleme return; } + public AuthenticationExperienceKind GetAuthenticationExperienceKind() + { + return _devIdProvider.GetAuthenticationExperienceKind(); + } + + public IAsyncOperation ShowLogonBehavior(WindowId windowHandle) + { + return _devIdProvider.ShowLogonSession(windowHandle); + } + /// /// Gets all the logged in accounts for this provider. /// diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs index ef17186f20..00f43d0645 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs @@ -11,6 +11,7 @@ using DevHome.Telemetry; using Microsoft.UI.Xaml; using Microsoft.Windows.DevHome.SDK; +using Windows.Foundation; namespace DevHome.SetupFlow.Models; @@ -87,21 +88,27 @@ public void StartIfNotRunning(string providerName) /// /// The uri that points to a remote repository /// THe provider that can clone the repo. Otherwise null. - public (bool, IDeveloperId, IRepositoryProvider) CanAnyProviderSupportThisUri(Uri uri) + public RepositoryProvider CanAnyProviderSupportThisUri(Uri uri) { foreach (var provider in _providers) { provider.Value.StartIfNotRunning(); - var isSupported = provider.Value.IsUriSupported(uri); - if (isSupported.Item1) + var isUriSupported = provider.Value.IsUriSupported(uri); + if (isUriSupported) { - return isSupported; + return provider.Value; } } - return (false, null, null); + return null; } + /// + /// Gets the login UI for the provider with the name providername + /// + /// The provider to search for. + /// The theme to use for the ui. + /// The ui to show. Can be null. public ExtensionAdaptiveCardPanel GetLoginUi(string providerName, ElementTheme elementTheme) { TelemetryFactory.Get().Log( @@ -121,7 +128,7 @@ public IEnumerable GetAllProviderNames() return _providers.Keys; } - public IRepositoryProvider GetProvider(string providerName) + public IRepositoryProvider GetSDKProvider(string providerName) { if (_providers.TryGetValue(providerName, out var repoProvider)) { @@ -131,6 +138,16 @@ public IRepositoryProvider GetProvider(string providerName) return null; } + public RepositoryProvider GetProvider(string providerName) + { + if (_providers.TryGetValue(providerName, out var repoProvider)) + { + return repoProvider; + } + + return null; + } + /// /// Gets all logged in accounts for a specific provider. /// @@ -142,6 +159,11 @@ public IEnumerable GetAllLoggedInAccounts(string providerName) return _providers.GetValueOrDefault(providerName)?.GetAllLoggedInAccounts() ?? new List(); } + public AuthenticationExperienceKind GetAuthenticationExperienceKind(string providerName) + { + return _providers.GetValueOrDefault(providerName)?.GetAuthenticationExperienceKind() ?? AuthenticationExperienceKind.CardSession; + } + /// /// Gets all the repositories for an account and provider. The account will be logged in if they aren't already. /// diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs index 733b36d93b..fc054bf227 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs @@ -143,6 +143,8 @@ public static class StringResourceKey public static readonly string UrlValidationBadUrl = nameof(UrlValidationBadUrl); public static readonly string UrlValidationNotFound = nameof(UrlValidationNotFound); public static readonly string UrlValidationRepoAlreadyAdded = nameof(UrlValidationRepoAlreadyAdded); + public static readonly string UrlNoAccountsHaveAccess = nameof(UrlNoAccountsHaveAccess); + public static readonly string UrlCancelButtonText = nameof(UrlCancelButtonText); // Install errors public static readonly string InstallPackageError = nameof(InstallPackageError); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw index 9c706d1225..0bb43a9041 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw +++ b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw @@ -1235,4 +1235,12 @@ Starting to install package {0} {Locked="{0}"} String letting users know that DevHome has started to install a package. {0} is the package id + + Press the X button to cancel. + String directing users to use the x button on the dialog to cancel the log-in process. + + + No accounts have access to this repository. + Error string letting users no that no accounts in an extension have access to the repository. + \ No newline at end of file diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs index 1f562f5637..96b222d806 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs @@ -9,18 +9,24 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using DevHome.Common.Extensions; using DevHome.Common.Services; +using DevHome.Common.TelemetryEvents.DeveloperId; using DevHome.Common.TelemetryEvents.SetupFlow; -using DevHome.Common.Views; using DevHome.Contracts.Services; +using DevHome.Logging; using DevHome.SetupFlow.Common.Helpers; using DevHome.SetupFlow.Models; using DevHome.SetupFlow.Services; using DevHome.Telemetry; +using Microsoft.Extensions.Hosting; +using Microsoft.UI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Windows.DevHome.SDK; +using Windows.Foundation; +using WinUIEx; using static DevHome.SetupFlow.Models.Common; namespace DevHome.SetupFlow.ViewModels; @@ -32,15 +38,23 @@ namespace DevHome.SetupFlow.ViewModels; /// public partial class AddRepoViewModel : ObservableObject { + private readonly IHost _host; + private readonly Guid _activityId; private readonly ISetupFlowStringResource _stringResource; private readonly List _previouslySelectedRepos; - private readonly IThemeSelectorService _themeSelectorService; + private ElementTheme SelectedTheme => _host.GetService().Theme; - private ElementTheme SelectedTheme => _themeSelectorService.Theme; + /// + /// Gets or sets a value indicating whether the log-in prompt is on screen. + /// + public bool IsLoggingIn + { + get; set; + } /// /// Gets or sets the list that keeps all repositories the user wants to clone. @@ -129,12 +143,6 @@ public List EverythingToClone [ObservableProperty] private bool? _isUrlAccountButtonChecked; - /// - /// Controls if the primary button is enabled. Turns true if everything is correct. - /// - [ObservableProperty] - private bool _shouldPrimaryButtonBeEnabled; - [ObservableProperty] private string _primaryButtonText; @@ -149,6 +157,21 @@ public List EverythingToClone [ObservableProperty] private bool _isFetchingRepos; + [ObservableProperty] + private bool _shouldEnablePrimaryButton; + + [ObservableProperty] + private Style _styleForPrimaryButton; + + [ObservableProperty] + private bool _shouldShowLoginUi; + + [ObservableProperty] + private bool _shouldShowXButtonInLoginUi; + + [ObservableProperty] + private bool _isCancelling; + /// /// Indicates if the ListView is currently filtering items. A result of manually filtering a list view /// is that the SelectionChanged is fired for any selected item that is removed and the item isn't "re-selected" @@ -244,25 +267,54 @@ internal PageKind CurrentPage get; set; } + private TypedEventHandler _developerIdChangedEvent; + + /// + /// Logs the user into the provider if they aren't already. + /// Changes the page to show all repositories for the user. + /// + /// + /// Fired when the combo box on the account page is changed. + /// + [RelayCommand] + private void RepoProviderSelected(string repositoryProviderName) + { + if (!string.IsNullOrEmpty(repositoryProviderName)) + { + StyleForPrimaryButton = Application.Current.Resources["SystemAccentColor"] as Style; + ShouldEnablePrimaryButton = true; + } + else + { + StyleForPrimaryButton = Application.Current.Resources["DefaultButtonStyle"] as Style; + ShouldEnablePrimaryButton = false; + } + } + + [RelayCommand] + private void CancelButtonPressed() + { + IsLoggingIn = false; + IsCancelling = true; + } + public AddRepoViewModel( ISetupFlowStringResource stringResource, List previouslySelectedRepos, - IThemeSelectorService themeSelector, + IHost host, Guid activityId) { _stringResource = stringResource; + _host = host; ChangeToUrlPage(); // override changes ChangeToUrlPage to correctly set the state. UrlParsingError = string.Empty; ShouldShowUrlError = Visibility.Collapsed; - ShouldPrimaryButtonBeEnabled = false; ShowErrorTextBox = Visibility.Collapsed; _previouslySelectedRepos = previouslySelectedRepos ?? new List(); EverythingToClone = new List(_previouslySelectedRepos); - - _themeSelectorService = themeSelector; _activityId = activityId; } @@ -288,11 +340,16 @@ public void GetExtensions() _providers.StartAllExtensions(); ProviderNames = new ObservableCollection(_providers.GetAllProviderNames()); - TelemetryFactory.Get().Log("RepoTool_SearchForProviders_Event", LogLevel.Critical, new ProviderEvent(ProviderNames.Count), _activityId); + TelemetryFactory.Get().Log("RepoTool_SearchForExtensions_Event", LogLevel.Critical, new ExtensionEvent(ProviderNames.Count), _activityId); IsAccountButtonEnabled = extensions.Any(); } + public void SetChangedEvents(TypedEventHandler handler) + { + _developerIdChangedEvent = handler; + } + public void ChangeToUrlPage() { Log.Logger?.ReportInfo(Log.Component.RepoConfig, "Changing to Url page"); @@ -303,6 +360,7 @@ public void ChangeToUrlPage() IsAccountToggleButtonChecked = false; CurrentPage = PageKind.AddViaUrl; PrimaryButtonText = _stringResource.GetLocalized(StringResourceKey.RepoEverythingElsePrimaryButtonText); + ShouldShowLoginUi = false; } public void ChangeToAccountPage() @@ -316,6 +374,7 @@ public void ChangeToAccountPage() IsAccountToggleButtonChecked = true; CurrentPage = PageKind.AddViaAccount; PrimaryButtonText = _stringResource.GetLocalized(StringResourceKey.RepoAccountPagePrimaryButtonText); + ShouldShowLoginUi = false; // List of extensions needs to be refreshed before accessing GetExtensions(); @@ -338,6 +397,7 @@ public void ChangeToRepoPage() ShowRepoPage = Visibility.Visible; CurrentPage = PageKind.Repositories; PrimaryButtonText = _stringResource.GetLocalized(StringResourceKey.RepoEverythingElsePrimaryButtonText); + ShouldShowLoginUi = false; // The only way to get the repo page is through the account page. // No need to change toggle buttons. @@ -386,16 +446,6 @@ public bool ValidateRepoInformation() } } - public void EnablePrimaryButton() - { - ShouldPrimaryButtonBeEnabled = true; - } - - public void DisablePrimaryButton() - { - ShouldPrimaryButtonBeEnabled = false; - } - /// /// Gets all the accounts for a provider and updates the UI. /// @@ -406,8 +456,24 @@ public async Task GetAccountsAsync(string repositoryProviderName, Frame loginFra var loggedInAccounts = await Task.Run(() => _providers.GetAllLoggedInAccounts(repositoryProviderName)); if (!loggedInAccounts.Any()) { - var loginUi = _providers.GetLoginUi(repositoryProviderName, SelectedTheme); - loginFrame.Content = loginUi; + IsLoggingIn = true; + ShouldShowLoginUi = true; + + // AddRepoDialog can handle the close button click. Don't show the x button. + ShouldShowXButtonInLoginUi = false; + InitiateAddAccountUserExperienceAsync(_providers.GetProvider(repositoryProviderName), loginFrame); + + // Wait 30 seconds for user to log in. + var maxIterationsToWait = 30; + var currentIteration = 0; + var waitDelay = Convert.ToInt32(new TimeSpan(0, 0, 1).TotalMilliseconds); + while ((IsLoggingIn && !IsCancelling) && currentIteration++ <= maxIterationsToWait) + { + await Task.Delay(waitDelay); + } + + ShouldShowLoginUi = false; + loggedInAccounts = await Task.Run(() => _providers.GetAllLoggedInAccounts(repositoryProviderName)); TelemetryFactory.Get().Log("RepoTool_GetAccount_Event", LogLevel.Critical, new RepoDialogGetAccountEvent(repositoryProviderName, alreadyLoggedIn: false), _activityId); } else @@ -468,7 +534,7 @@ public void AddOrRemoveRepository(string providerName, string accountName, IList } var cloningInformation = new CloningInformation(repoToAdd); - cloningInformation.RepositoryProvider = _providers.GetProvider(providerName); + cloningInformation.RepositoryProvider = _providers.GetSDKProvider(providerName); cloningInformation.ProviderName = _providers.DisplayName(providerName); cloningInformation.OwningAccount = developerId; cloningInformation.EditClonePathAutomationName = _stringResource.GetLocalized(StringResourceKey.RepoPageEditClonePathAutomationProperties, $"{providerName}/{repositoryToAdd}"); @@ -478,20 +544,15 @@ public void AddOrRemoveRepository(string providerName, string accountName, IList } /// - /// Adds a repository from the URL page. Steps to determine what repoProvider to use. - /// 1. All providers are asked "Can you parse this into a URL you understand." If yes, that provider to clone the repo. - /// 2. If no providers can parse the URL a fall back "GitProvider" is used that uses libgit2sharp to clone the repo. - /// ShouldShowUrlError is set here. + /// Validates that url is a valid url and changes url to be absolute if valid. /// - /// - /// If ShouldShowUrlError == Visible the repo is not added to the list of repos to clone. - /// - /// The location to clone the repo to - public void AddRepositoryViaUri(string url, string cloneLocation) + /// The url to validate + /// The Uri after validation. + /// If the url is not valid this method sets UrlParsingError and ShouldShowUrlError to the correct values. + private void ValidateUriAndChangeUiIfBad(string url, out Uri uri) { // If the url isn't valid don't bother finding a provider. - Uri parsedUri; - if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out parsedUri)) + if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri)) { UrlParsingError = _stringResource.GetLocalized(StringResourceKey.UrlValidationBadUrl); ShouldShowUrlError = Visibility.Visible; @@ -500,73 +561,61 @@ public void AddRepositoryViaUri(string url, string cloneLocation) // If user entered a relative Uri put it into a UriBuilder to turn it into an // absolute Uri. UriBuilder prepends the https scheme - if (!parsedUri.IsAbsoluteUri) + if (!uri.IsAbsoluteUri) { try { - var uriBuilder = new UriBuilder(parsedUri.OriginalString); + var uriBuilder = new UriBuilder(uri.OriginalString); uriBuilder.Port = -1; - parsedUri = uriBuilder.Uri; + uri = uriBuilder.Uri; } catch (Exception e) { - Log.Logger?.ReportError(Log.Component.RepoConfig, $"Invalid URL {parsedUri.OriginalString}", e); + Log.Logger?.ReportError(Log.Component.RepoConfig, $"Invalid URL {uri.OriginalString}", e); UrlParsingError = _stringResource.GetLocalized(StringResourceKey.UrlValidationBadUrl); ShouldShowUrlError = Visibility.Visible; return; } } - var providerToCloneRepo = _providers.CanAnyProviderSupportThisUri(parsedUri); + return; + } + + /// + /// Adds a repository from the URL page. Steps to determine what repoProvider to use. + /// 1. All providers are asked "Can you parse this into a URL you understand." If yes, that provider to clone the repo. + /// 2. If no providers can parse the URL a fall back "GitProvider" is used that uses libgit2sharp to clone the repo. + /// ShouldShowUrlError is set here. + /// + /// + /// If ShouldShowUrlError == Visible the repo is not added to the list of repos to clone. + /// + /// The location to clone the repo to + public void AddRepositoryViaUri(string url, string cloneLocation, Frame loginFrame) + { + ShouldEnablePrimaryButton = false; + Uri uri = null; + ValidateUriAndChangeUiIfBad(url, out uri); - // true not-null can use developer ID to clone repo. - // True, null, do not use developer ID to clone the repo - // false. Can't clone the repo. - CloningInformation cloningInformation; - if (providerToCloneRepo.Item1) + if (uri == null) { - if (providerToCloneRepo.Item2 != null) - { - // A provider parsed the URL and at least 1 logged in account has access to the repo. - var repoResult = providerToCloneRepo.Item3.GetRepositoryFromUriAsync(parsedUri, providerToCloneRepo.Item2).AsTask().Result; - if (repoResult.Result.Status == ProviderOperationStatus.Failure) - { - // something happened. - throw repoResult.Result.ExtendedError; - } + return; + } - cloningInformation = new CloningInformation(repoResult.Repository); - cloningInformation.RepositoryProvider = providerToCloneRepo.Item3; - cloningInformation.ProviderName = providerToCloneRepo.Item3.DisplayName; - cloningInformation.CloningLocation = new DirectoryInfo(cloneLocation); - cloningInformation.OwningAccount = providerToCloneRepo.Item2; - } - else - { - // A provider parsed the URL but no logged in accounts can access it (Could be public). - var repoResult = providerToCloneRepo.Item3.GetRepositoryFromUriAsync(parsedUri).AsTask().Result; - if (repoResult.Result.Status == ProviderOperationStatus.Failure) - { - // something happened. - throw repoResult.Result.ExtendedError; - } + // This will return null even if the repo uri has a typo in it. + // Causing GetCloningInformationFromURL to fall back to git. + var provider = _providers.CanAnyProviderSupportThisUri(uri); - cloningInformation = new CloningInformation(repoResult.Repository); - cloningInformation.RepositoryProvider = providerToCloneRepo.Item3; - cloningInformation.ProviderName = providerToCloneRepo.Item3.DisplayName; - cloningInformation.CloningLocation = new DirectoryInfo(cloneLocation); - cloningInformation.OwningAccount = providerToCloneRepo.Item2; - } - } - else + var cloningInformation = GetCloningInformationFromUrl(provider, cloneLocation, uri, loginFrame); + if (cloningInformation == null) { - // No providers can parse the Url. - // Fall back to a git Url. - cloningInformation = new CloningInformation(new GenericRepository(parsedUri)); - cloningInformation.ProviderName = "git"; - cloningInformation.CloningLocation = new DirectoryInfo(cloneLocation); + // Error information is already set. + // Error string is visible + return; } + ShouldShowUrlError = Visibility.Collapsed; + // User could paste in a url of an already added repo. Check for that here. if (_previouslySelectedRepos.Any(x => x.RepositoryToClone.OwningAccountName.Equals(cloningInformation.RepositoryToClone.OwningAccountName, StringComparison.OrdinalIgnoreCase) && x.RepositoryToClone.DisplayName.Equals(cloningInformation.RepositoryToClone.DisplayName, StringComparison.OrdinalIgnoreCase))) @@ -581,6 +630,121 @@ public void AddRepositoryViaUri(string url, string cloneLocation) Log.Logger?.ReportInfo(Log.Component.RepoConfig, $"Adding repository to clone {cloningInformation.RepositoryId} to location '{cloneLocation}'"); EverythingToClone.Add(cloningInformation); + ShouldEnablePrimaryButton = true; + } + + /// + /// Tries to assign a provider to a validated uri. + /// + /// The provider to test with. + /// The location the user wnats to clone the repo. + /// The uri to the repo (Should be a valid uri) + /// The frame to show OAUTH login if the user needs to log in. + /// non-null cloning information if a provider is selected for cloning. Null for all other cases. + /// If the repo is either private, or does not exist, this will ask the user to log in. + private CloningInformation GetCloningInformationFromUrl(RepositoryProvider provider, string cloneLocation, Uri uri, Frame loginFrame) + { + if (provider == null) + { + // Fallback to a generic git provider. + // Code path lights up for a repo that has a typo. + var cloningInformation = new CloningInformation(new GenericRepository(uri)); + cloningInformation.ProviderName = "git"; + cloningInformation.CloningLocation = new DirectoryInfo(cloneLocation); + + return cloningInformation; + } + + // Repo may be public. Try that. + var repo = provider.GetRepositoryFromUri(uri); + if (repo != null) + { + var cloningInformation = new CloningInformation(repo); + cloningInformation.RepositoryProvider = provider.GetProvider(); + cloningInformation.ProviderName = provider.DisplayName; + cloningInformation.CloningLocation = new DirectoryInfo(cloneLocation); + + return cloningInformation; + } + + // Repo may be private, or not exist. Try to get repo info with all logged in accounts. + var loggedInAccounts = provider.GetAllLoggedInAccounts(); + if (loggedInAccounts.Any()) + { + foreach (var loggedInAccount in loggedInAccounts) + { + repo = provider.GetRepositoryFromUri(uri, loggedInAccount); + if (repo != null) + { + var cloningInformation = new CloningInformation(repo); + cloningInformation.RepositoryProvider = provider.GetProvider(); + cloningInformation.ProviderName = provider.DisplayName; + cloningInformation.CloningLocation = new DirectoryInfo(cloneLocation); + cloningInformation.OwningAccount = loggedInAccount; + + return cloningInformation; + } + } + + // In the case that no logged in accounts can access it, return null + // until DevHome can handle multiple accounts. + // Should have a better error string. + // TODO: Figure out a better error message? + UrlParsingError = _stringResource.GetLocalized(StringResourceKey.UrlNoAccountsHaveAccess); + ShouldShowUrlError = Visibility.Visible; + + return null; + } + + // At this point one of three things are true + // 1. The repo is private and no accounts are logged in. + // 2. The repo does not exist (Might have been a typo in the name) + // Because DevHome cannot tell if a repo is private, or does not exist, prompt the user to log in. + // Only ask if DevHome hasn't asked already. + UrlParsingError = _stringResource.GetLocalized(StringResourceKey.UrlNoAccountsHaveAccess); + ShouldShowUrlError = Visibility.Visible; + IsLoggingIn = true; + InitiateAddAccountUserExperienceAsync(provider, loginFrame); + return null; + } + + /// + /// Launches the login experience for the provided provider. + /// + /// The provider used to log the user in. + /// The frame to use to display the OAUTH path + private void InitiateAddAccountUserExperienceAsync(RepositoryProvider provider, Frame loginFrame) + { + TelemetryFactory.Get().Log( + "EntryPoint_DevId_Event", + LogLevel.Critical, + new EntryPointEvent(EntryPointEvent.EntryPoint.Settings)); + + provider.SetChangedEvent(_developerIdChangedEvent); + var authenticationFlow = provider.GetAuthenticationExperienceKind(); + if (authenticationFlow == AuthenticationExperienceKind.CardSession) + { + var loginUi = _providers.GetLoginUi(provider.ExtensionDisplayName, SelectedTheme); + loginFrame.Content = loginUi; + } + else if (authenticationFlow == AuthenticationExperienceKind.CustomProvider) + { + var windowHandle = _host.GetService().GetWindowHandle(); + var windowPtr = Win32Interop.GetWindowIdFromWindow(windowHandle); + try + { + var developerIdResult = provider.ShowLogonBehavior(windowPtr).AsTask().Result; + if (developerIdResult.Result.Status == ProviderOperationStatus.Failure) + { + GlobalLog.Logger?.ReportError($"{developerIdResult.Result.DisplayMessage} - {developerIdResult.Result.DiagnosticText}"); + return; + } + } + catch (Exception ex) + { + GlobalLog.Logger?.ReportError($"Exception thrown while calling show logon session", ex); + } + } } /// diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs index 1dab580e04..6e7a3d6a7a 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs @@ -13,18 +13,14 @@ using DevHome.Common.Extensions; using DevHome.Common.TelemetryEvents.SetupFlow; using DevHome.Contracts.Services; -using DevHome.SetupFlow.Common.Contracts; -using DevHome.SetupFlow.Common.Elevation; using DevHome.SetupFlow.Common.Helpers; using DevHome.SetupFlow.Models; using DevHome.SetupFlow.Services; using DevHome.Telemetry; using Microsoft.Extensions.Hosting; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; -using Projection::DevHome.SetupFlow.ElevatedComponent; using WinUIEx; namespace DevHome.SetupFlow.ViewModels; diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/RepoConfigViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/RepoConfigViewModel.cs index 5a229cba5b..4b7b00b1cd 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/RepoConfigViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/RepoConfigViewModel.cs @@ -30,6 +30,11 @@ public partial class RepoConfigViewModel : SetupPageViewModelBase /// private readonly RepoConfigTaskGroup _taskGroup; + public IHost Host + { + get; + } + private readonly IDevDriveManager _devDriveManager; private readonly IThemeSelectorService _themeSelectorService; @@ -101,6 +106,7 @@ public RepoConfigViewModel( NextPageButtonToolTipText = stringResource.GetLocalized(StringResourceKey.RepoToolNextButtonTooltip); _themeSelectorService = host.GetService(); _themeSelectorService.ThemeChanged += OnThemeChanged; + Host = host; } private void OnThemeChanged(object sender, ElementTheme e) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml index c2af4cc332..ca9e5716d2 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml @@ -13,7 +13,7 @@ x:Uid="ms-resource:///DevHome.SetupFlow/Resources/CloneRepoDialog" x:Name="AddRepoContentDialog" PrimaryButtonClick="AddRepoContentDialog_PrimaryButtonClick" - IsPrimaryButtonEnabled="{x:Bind AddRepoViewModel.ShouldPrimaryButtonBeEnabled, Mode=OneWay}" + IsPrimaryButtonEnabled="{x:Bind AddRepoViewModel.ShouldEnablePrimaryButton, Mode=OneWay}" DefaultButton="Primary" Style="{StaticResource DefaultContentDialogStyle}" PrimaryButtonText="{x:Bind AddRepoViewModel.PrimaryButtonText, Mode=OneWay}" @@ -60,13 +60,28 @@ - - + + + + + + + + - + + + + + @@ -148,46 +163,46 @@ - - - - - - - - - - - - + + + + + + + + + + + + Visibility="{x:Bind EditDevDriveViewModel.DevDriveValidationError, Mode=OneWay}"/> diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs index 7d422ea548..7e0d3bb77a 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs @@ -6,9 +6,9 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using DevHome.Common.Extensions; using DevHome.Common.Models; using DevHome.Common.Services; -using DevHome.Contracts.Services; using DevHome.SetupFlow.Models; using DevHome.SetupFlow.Services; using DevHome.SetupFlow.ViewModels; @@ -16,6 +16,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Data; +using Microsoft.Windows.DevHome.SDK; using static DevHome.SetupFlow.Models.Common; namespace DevHome.SetupFlow.Views; @@ -23,10 +24,12 @@ namespace DevHome.SetupFlow.Views; /// /// Dialog to allow users to select repositories they want to clone. /// -internal partial class AddRepoDialog +internal partial class AddRepoDialog : ContentDialog { private readonly string _defaultClonePath; + private readonly IHost _host; + private readonly List _previouslySelectedRepos = new (); /// @@ -62,12 +65,12 @@ public AddRepoDialog( IDevDriveManager devDriveManager, ISetupFlowStringResource stringResource, List previouslySelectedRepos, - IThemeSelectorService themeSelectorService, - Guid activityId) + Guid activityId, + IHost host) { this.InitializeComponent(); _previouslySelectedRepos = previouslySelectedRepos; - AddRepoViewModel = new AddRepoViewModel(stringResource, previouslySelectedRepos, themeSelectorService, activityId); + AddRepoViewModel = new AddRepoViewModel(stringResource, previouslySelectedRepos, host, activityId); EditDevDriveViewModel = new EditDevDriveViewModel(devDriveManager); FolderPickerViewModel = new FolderPickerViewModel(stringResource); @@ -83,9 +86,10 @@ public AddRepoDialog( // Changing view to account so the selection changed event for Segment correctly shows URL. AddRepoViewModel.CurrentPage = PageKind.AddViaAccount; - IsPrimaryButtonEnabled = false; + AddRepoViewModel.ShouldEnablePrimaryButton = false; AddViaUrlSegmentedItem.IsSelected = true; SwitchViewsSegmentedView.SelectedIndex = 1; + _host = host; } /// @@ -96,6 +100,36 @@ public async Task GetExtensionsAsync() await Task.Run(() => AddRepoViewModel.GetExtensions()); } + /// + /// Sets the event handler on all providers to listen when the user has logged in. + /// + public void SetDeveloperIdChangedEvents() + { + AddRepoViewModel.SetChangedEvents(DeveloperIdChangedEventHandler); + } + + /// + /// Changes the flag that indicates if the user is logging in to false to indicate the user is done logging in. + /// + /// The object that raised this event, should only be IDeveloperId + /// The developer the log in action is applied to. + public void DeveloperIdChangedEventHandler(object sender, IDeveloperId developerId) + { + if (sender is IDeveloperIdProvider devIdProvider) + { + var authenticationState = devIdProvider.GetDeveloperIdState(developerId); + + if (authenticationState == AuthenticationState.LoggedIn) + { + // AddRepoViewModel uses this to wait for the user to log in before continuing. + AddRepoViewModel.IsLoggingIn = false; + } + + // Remove the handler so multiple hooks aren't attached. + devIdProvider.Changed -= DeveloperIdChangedEventHandler; + } + } + /// /// Sets up the UI for dev drives. /// @@ -116,7 +150,6 @@ await Task.Run(() => private void ChangeToAccountPage() { - SwitchViewsSegmentedView.IsEnabled = false; AddRepoViewModel.ChangeToAccountPage(); FolderPickerViewModel.CloseFolderPicker(); EditDevDriveViewModel.HideDevDriveUI(); @@ -155,12 +188,12 @@ private void RepositoryProviderNamesComboBox_SelectionChanged(object sender, Sel if (!string.IsNullOrEmpty(repositoryProviderName)) { PrimaryButtonStyle = AddRepoStackPanel.Resources["ContentDialogLogInButtonStyle"] as Style; - IsPrimaryButtonEnabled = true; + AddRepoViewModel.ShouldEnablePrimaryButton = true; } else { PrimaryButtonStyle = Application.Current.Resources["DefaultButtonStyle"] as Style; - IsPrimaryButtonEnabled = false; + AddRepoViewModel.ShouldEnablePrimaryButton = false; } } @@ -267,18 +300,77 @@ private void RepositoriesListView_SelectionChanged(object sender, SelectionChang } /// - /// Adds the repository from the URL screen to the list of repos to be cloned. + /// The primary button has different behavior based on the screen the user is currently in. /// - private void AddRepoContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) + private async void AddRepoContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { if (AddRepoViewModel.CurrentPage == PageKind.AddViaUrl) { - AddRepoViewModel.AddRepositoryViaUri(AddRepoViewModel.Url, FolderPickerViewModel.CloneLocation); + // If the user is logging in, the close button text will change. + // Keep a copy of the original to revert when this button click is done. + var originalCloseButtonText = AddRepoContentDialog.CloseButtonText; + + // Try to pair a provider with the repo to clone. + // If the repo is private or does not exist the user will be asked to log in. + AddRepoViewModel.ShouldShowLoginUi = true; + AddRepoViewModel.ShouldShowXButtonInLoginUi = true; + + // Get the number of repos already selected to clone in a previous instance. + // Used to figure out if the repo was added after the user logged into an account. + var numberOfReposToCloneCount = AddRepoViewModel.EverythingToClone.Count; + AddRepoViewModel.AddRepositoryViaUri(AddRepoViewModel.Url, FolderPickerViewModel.CloneLocation, LoginUIContent); + + // On the first run, ignore any warnings. + // If this is set to visible and the user needs to log in they'll see an error message after the log-in + // prompt exits even if they logged in successfully. + AddRepoViewModel.ShouldShowUrlError = Visibility.Collapsed; + + // Get deferral to prevent the dialog from closing when awaiting operations. + var deferral = args.GetDeferral(); + + // Two click events can not be processed at the same time. + // UI will not respond to the close button when inside this method. + // Change the text of the close button to notify users of the X button in the upper-right corner of the log-in ui. + if (AddRepoViewModel.IsLoggingIn) + { + AddRepoContentDialog.CloseButtonText = _host.GetService().GetLocalized(StringResourceKey.UrlCancelButtonText); + } + + // Wait roughly 30 seconds for the user to log in. + var maxIterationsToWait = 30; + var currentIteration = 0; + var waitDelay = Convert.ToInt32(new TimeSpan(0, 0, 1).TotalMilliseconds); + while ((AddRepoViewModel.IsLoggingIn && !AddRepoViewModel.IsCancelling) && currentIteration++ <= maxIterationsToWait) + { + await Task.Delay(waitDelay); + } + + deferral.Complete(); + AddRepoViewModel.ShouldShowLoginUi = false; + AddRepoViewModel.ShouldShowXButtonInLoginUi = false; + + // User cancelled the login prompt. Don't re-check repo access. + if (AddRepoViewModel.IsCancelling) + { + return; + } + + // If the repo was rejected and the user needed to sign in, no repos would've been added + // and the number of repos to clone will not be changed. + if (numberOfReposToCloneCount == AddRepoViewModel.EverythingToClone.Count) + { + AddRepoViewModel.AddRepositoryViaUri(AddRepoViewModel.Url, FolderPickerViewModel.CloneLocation, LoginUIContent); + AddRepoViewModel.ShouldShowUrlError = Visibility.Collapsed; + } + if (AddRepoViewModel.ShouldShowUrlError == Visibility.Visible) { - IsPrimaryButtonEnabled = false; + AddRepoViewModel.ShouldEnablePrimaryButton = false; args.Cancel = true; } + + // Revert the close button text. + AddRepoContentDialog.CloseButtonText = originalCloseButtonText; } else if (AddRepoViewModel.CurrentPage == PageKind.AddViaAccount) { @@ -300,8 +392,7 @@ private async void SwitchToRepoPage(string repositoryProviderName) FolderPickerViewModel.ShowFolderPicker(); EditDevDriveViewModel.ShowDevDriveUIIfEnabled(); AccountsComboBox.SelectedValue = AddRepoViewModel.Accounts.First(); - PrimaryButtonStyle = Application.Current.Resources["DefaultButtonStyle"] as Style; - IsPrimaryButtonEnabled = false; + AddRepoViewModel.ShouldEnablePrimaryButton = false; } } @@ -349,14 +440,7 @@ private void ToggleCloneButton() isEverythingGood &= EditDevDriveViewModel.IsDevDriveValid(); } - if (isEverythingGood) - { - IsPrimaryButtonEnabled = true; - } - else - { - IsPrimaryButtonEnabled = false; - } + AddRepoViewModel.ShouldEnablePrimaryButton = isEverythingGood; // Fill in EverythingToClone with the location if (isEverythingGood) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs index be73f94757..07568642b9 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs @@ -13,6 +13,7 @@ using DevHome.SetupFlow.Models; using DevHome.SetupFlow.ViewModels; using DevHome.Telemetry; +using Microsoft.Extensions.Hosting; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -24,8 +25,6 @@ namespace DevHome.SetupFlow.Views; [INotifyPropertyChanged] public sealed partial class RepoConfigView : UserControl { - private readonly IThemeSelectorService _themeSelectorService; - private Guid ActivityId => ViewModel.Orchestrator.ActivityId; public RepoConfigViewModel ViewModel => (RepoConfigViewModel)this.DataContext; @@ -36,8 +35,6 @@ public RepoConfigView() { this.InitializeComponent(); ActualThemeChanged += OnActualThemeChanged; - - _themeSelectorService = Application.Current.GetService(); } public void OnActualThemeChanged(FrameworkElement sender, object args) @@ -70,7 +67,7 @@ private async Task AddRepoAsync() telemetryLogger.Log(EventName, LogLevel.Critical, new DialogEvent("Open", dialogName), ActivityId); - _addRepoDialog = new AddRepoDialog(ViewModel.DevDriveManager, ViewModel.LocalStringResource, ViewModel.RepoReviewItems.ToList(), _themeSelectorService, ActivityId); + _addRepoDialog = new AddRepoDialog(ViewModel.DevDriveManager, ViewModel.LocalStringResource, ViewModel.RepoReviewItems.ToList(), ActivityId, ViewModel.Host); var getExtensionsTask = _addRepoDialog.GetExtensionsAsync(); var setupDevDrivesTask = _addRepoDialog.SetupDevDrivesAsync(); _addRepoDialog.XamlRoot = RepoConfigGrid.XamlRoot; @@ -79,11 +76,15 @@ private async Task AddRepoAsync() // Start await getExtensionsTask; await setupDevDrivesTask; + + _addRepoDialog.SetDeveloperIdChangedEvents(); + if (_addRepoDialog.EditDevDriveViewModel.CanShowDevDriveUI && ViewModel.ShouldAutoCheckDevDriveCheckbox) { _addRepoDialog.UpdateDevDriveInfo(); } + _addRepoDialog.IsSecondaryButtonEnabled = true; var result = await _addRepoDialog.ShowAsync(ContentDialogPlacement.InPlace); var devDrive = _addRepoDialog.EditDevDriveViewModel.DevDrive; From 19ff23ae6068e94798b16688b33307a01a37f3d8 Mon Sep 17 00:00:00 2001 From: Alexander Sklar Date: Thu, 19 Oct 2023 14:53:27 -0700 Subject: [PATCH 11/69] Add "Experimental Features" in settings (#1707) * add experimental features page * binding of feature <--> tool * . * . * remove * jsonc * PR feedback * Update settings/DevHome.Settings/Strings/en-us/Resources.resw Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com> * Update tools/Experiments/src/Strings/en-us/Resources.resw Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com> * Update tools/Experiments/src/ExperimentalFeatures.md Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com> * Update tools/Experiments/src/DevHome.Experiments.csproj Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com> * pr fb * Git attributes * fix usings * using namespace * looks like jsonc EOL was LF * . * change line endings in NavConfig.jsonc to crfl * move md doc to docs folder --------- Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com> --- DevHome.sln | 19 +++++ common/Services/LocalSettingsService.cs | 1 + docs/ExperimentalFeatures.md | 30 ++++++++ docs/navconfig.md | 10 ++- .../DevHome.Settings/DevHome.Settings.csproj | 7 ++ .../DevHome.Settings/ExperimentalFeature.cs | 52 ++++++++++++++ .../Extensions/ServiceExtensions.cs | 1 + .../Strings/en-us/Resources.resw | 14 +++- .../ExperimentalFeaturesViewModel.cs | 30 ++++++++ .../ViewModels/SettingsViewModel.cs | 4 ++ .../Views/ExperimentalFeaturesPage.xaml | 43 ++++++++++++ .../Views/ExperimentalFeaturesPage.xaml.cs | 62 +++++++++++++++++ src/App.xaml.cs | 23 ++++++- src/DevHome.csproj | 3 +- src/Helpers/NavConfigHelper.cs | 6 ++ src/{NavConfig.json => NavConfig.jsonc} | 17 ++++- src/Services/PageService.cs | 9 +++ src/Views/ShellPage.xaml.cs | 47 +++++++++++++ .../src/DevHome.Experiments.csproj | 23 +++++++ .../src/Strings/en-us/Resources.resw | 69 +++++++++++++++++++ .../src/ViewModels/TestExperimentViewModel.cs | 16 +++++ .../src/Views/TestExperimentPage.xaml | 15 ++++ .../src/Views/TestExperimentPage.xaml.cs | 40 +++++++++++ 23 files changed, 534 insertions(+), 7 deletions(-) create mode 100644 docs/ExperimentalFeatures.md create mode 100644 settings/DevHome.Settings/ExperimentalFeature.cs create mode 100644 settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs create mode 100644 settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml create mode 100644 settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs rename src/{NavConfig.json => NavConfig.jsonc} (67%) create mode 100644 tools/Experiments/src/DevHome.Experiments.csproj create mode 100644 tools/Experiments/src/Strings/en-us/Resources.resw create mode 100644 tools/Experiments/src/ViewModels/TestExperimentViewModel.cs create mode 100644 tools/Experiments/src/Views/TestExperimentPage.xaml create mode 100644 tools/Experiments/src/Views/TestExperimentPage.xaml.cs diff --git a/DevHome.sln b/DevHome.sln index edf1d679da..dff8216347 100644 --- a/DevHome.sln +++ b/DevHome.sln @@ -71,6 +71,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.ExtensionLibrary", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExtensionLibrary", "ExtensionLibrary", "{F6EAB7D3-8F0A-4455-8969-2EF4A67314A0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Experiments", "tools\Experiments\src\DevHome.Experiments.csproj", "{2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -395,6 +397,22 @@ Global {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Release|x64.Build.0 = Release|x64 {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Release|x86.ActiveCfg = Release|x86 {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Release|x86.Build.0 = Release|x86 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|Any CPU.ActiveCfg = Debug|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|Any CPU.Build.0 = Debug|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|arm64.ActiveCfg = Debug|arm64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|arm64.Build.0 = Debug|arm64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|x64.ActiveCfg = Debug|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|x64.Build.0 = Debug|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|x86.ActiveCfg = Debug|x86 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|x86.Build.0 = Debug|x86 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|Any CPU.ActiveCfg = Release|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|Any CPU.Build.0 = Release|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|arm64.ActiveCfg = Release|arm64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|arm64.Build.0 = Release|arm64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|x64.ActiveCfg = Release|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|x64.Build.0 = Release|x64 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|x86.ActiveCfg = Release|x86 + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -415,6 +433,7 @@ Global {0D879E08-99AA-4019-9D04-DEA9F7C7BFC1} = {32C0052F-10AF-48C0-A7A3-B0A1793397FD} {69F8B7DF-F52B-4B74-9A16-AB3241BB8912} = {F6EAB7D3-8F0A-4455-8969-2EF4A67314A0} {F6EAB7D3-8F0A-4455-8969-2EF4A67314A0} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD} + {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {030B5641-B206-46BB-BF71-36FF009088FA} diff --git a/common/Services/LocalSettingsService.cs b/common/Services/LocalSettingsService.cs index 412bc7f282..9bcf31cdad 100644 --- a/common/Services/LocalSettingsService.cs +++ b/common/Services/LocalSettingsService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using DevHome.Common.Contracts; using DevHome.Common.Helpers; diff --git a/docs/ExperimentalFeatures.md b/docs/ExperimentalFeatures.md new file mode 100644 index 0000000000..5fa1a1279a --- /dev/null +++ b/docs/ExperimentalFeatures.md @@ -0,0 +1,30 @@ +# Experimental features + +Dev Home supports "Experimental features" - these are features that are disabled by default, but can be enabled by the user. +This is useful for features that are not ready for general use, but can be tested by users who are interested in trying them out and providing feedback to inform the development of the feature. + + +## Adding a new experimental feature + +1. Add a new string in the `experiments` array in `navconfig.jsonc`, e.g. MyExperimentalFeature +2. Add Name and Description strings under settings/DevHome.Settings/Strings/en-us/Resources.resw: +```xml + + Some description + + + My experimental feature's name + +``` + +## Making a tool page visible only when the feature is enabled + +In `navConfig.jsonc`, add the following in the tool's definition: +`"experimentId": "MyExperimentalFeature"` + +## Checking if a feature is enabled + +```csharp +var experimentalFeaturesViewModel = App.Current.GetService(); +var isEnabled = experimentalFeaturesViewModel.Features.Where(x => string.Equals(x.Id, "MyExperimentalFeature", StringComparison.OrdinalIgnoreCase).First().IsEnabled; +``` diff --git a/docs/navconfig.md b/docs/navconfig.md index e3c4e3cdf5..8f1efc001c 100644 --- a/docs/navconfig.md +++ b/docs/navconfig.md @@ -42,4 +42,12 @@ - icon `required` - Unicode point of tool's icon - Type: `string` - - path: #/navMenu/groups/items/tools/items/icons \ No newline at end of file + - path: #/navMenu/groups/items/tools/items/icons + - experimentId `optional` + - This page is experimental and tied to an experiment ID + - Type: `string` + - path: #/navMenu/groups/items/tools/items/experimentId +- experiments `optional` + - Defines the list of experimental feature IDs. + - Type: `array` + - path: #/experiments \ No newline at end of file diff --git a/settings/DevHome.Settings/DevHome.Settings.csproj b/settings/DevHome.Settings/DevHome.Settings.csproj index 17bcab9f2a..c64a2b1829 100644 --- a/settings/DevHome.Settings/DevHome.Settings.csproj +++ b/settings/DevHome.Settings/DevHome.Settings.csproj @@ -19,6 +19,7 @@ + @@ -70,4 +71,10 @@ + + + + MSBuild:Compile + + diff --git a/settings/DevHome.Settings/ExperimentalFeature.cs b/settings/DevHome.Settings/ExperimentalFeature.cs new file mode 100644 index 0000000000..bcf655cb3b --- /dev/null +++ b/settings/DevHome.Settings/ExperimentalFeature.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.Common.Contracts; +using DevHome.Common.Services; + +namespace DevHome.Settings; + +public partial class ExperimentalFeature : ObservableObject +{ + public string Id { get; init; } + + public static ILocalSettingsService? LocalSettingsService { get; set; } + + public ExperimentalFeature(string id) + { + Id = id; + IsEnabled = GetIsEnabled(); + } + + public string Name + { + get + { + var stringResource = new StringResource("DevHome.Settings/Resources"); + return stringResource.GetLocalized(Id + "_Name"); + } + } + + public string Description + { + get + { + var stringResource = new StringResource("DevHome.Settings/Resources"); + return stringResource.GetLocalized(Id + "_Description"); + } + } + + [ObservableProperty] + private bool isEnabled; + + public bool GetIsEnabled() + { + return LocalSettingsService!.ReadSettingAsync($"ExperimentalFeature_{Id}").Result; + } + + partial void OnIsEnabledChanging(bool value) + { + LocalSettingsService!.SaveSettingAsync($"ExperimentalFeature_{Id}", value).Wait(); + } +} diff --git a/settings/DevHome.Settings/Extensions/ServiceExtensions.cs b/settings/DevHome.Settings/Extensions/ServiceExtensions.cs index 6513f72c91..8b6061027d 100644 --- a/settings/DevHome.Settings/Extensions/ServiceExtensions.cs +++ b/settings/DevHome.Settings/Extensions/ServiceExtensions.cs @@ -26,6 +26,7 @@ public static IServiceCollection AddSettings(this IServiceCollection services, H services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddSingleton(); return services; } diff --git a/settings/DevHome.Settings/Strings/en-us/Resources.resw b/settings/DevHome.Settings/Strings/en-us/Resources.resw index 36d7a10746..5d2c3fd83d 100644 --- a/settings/DevHome.Settings/Strings/en-us/Resources.resw +++ b/settings/DevHome.Settings/Strings/en-us/Resources.resw @@ -406,4 +406,16 @@ Cancel - + + Configure experimental features + The description for the Experimental Features settings card + + + Experimental features + The header for the Experimental Features settings card + + + There are currently no experimental features + Message shown when there are no available experimental features + + \ No newline at end of file diff --git a/settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs b/settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs new file mode 100644 index 0000000000..90bc5af8d4 --- /dev/null +++ b/settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using DevHome.Common.Contracts; +using DevHome.Common.Extensions; +using DevHome.Contracts.Services; +using Microsoft.UI.Xaml; + +namespace DevHome.Settings.ViewModels; + +public class ExperimentalFeaturesViewModel : ObservableObject +{ + private ILocalSettingsService _localSettingsService; + + public ObservableCollection Features { get; } = new (); + + public ExperimentalFeaturesViewModel(ILocalSettingsService localSettingsService) + { + _localSettingsService = localSettingsService; + ExperimentalFeature.LocalSettingsService = localSettingsService; + } +} diff --git a/settings/DevHome.Settings/ViewModels/SettingsViewModel.cs b/settings/DevHome.Settings/ViewModels/SettingsViewModel.cs index 6a41c700f8..95c2d758e1 100644 --- a/settings/DevHome.Settings/ViewModels/SettingsViewModel.cs +++ b/settings/DevHome.Settings/ViewModels/SettingsViewModel.cs @@ -56,6 +56,7 @@ public SettingsViewModel() new Setting("Extensions", string.Empty, stringResource.GetLocalized("Settings_Extensions_Header"), stringResource.GetLocalized("Settings_Extensions_Description"), "\ued35", false, false), new Setting("About", string.Empty, stringResource.GetLocalized("Settings_About_Header"), stringResource.GetLocalized("Settings_About_Description"), "\ue946", false, false), new Setting("Feedback", string.Empty, stringResource.GetLocalized("Settings_Feedback_Header"), stringResource.GetLocalized("Settings_Feedback_Description"), "\ued15", false, false), + new Setting("ExperimentalFeatures", string.Empty, stringResource.GetLocalized("Settings_ExperimentalFeatures_Header"), stringResource.GetLocalized("Settings_ExperimentalFeatures_Description"), "\ue74c", false, false), }; foreach (var setting in settings) @@ -85,6 +86,9 @@ public void Navigate(string path) case "Feedback": navigationService.NavigateTo(typeof(FeedbackViewModel).FullName!); return; + case "ExperimentalFeatures": + navigationService.NavigateTo(typeof(ExperimentalFeaturesViewModel).FullName!); + return; default: return; } diff --git a/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml b/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml new file mode 100644 index 0000000000..d58fae2b1f --- /dev/null +++ b/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs b/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs new file mode 100644 index 0000000000..ee1d0c3712 --- /dev/null +++ b/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.Common.Contracts; +using DevHome.Common.Extensions; +using DevHome.Common.Services; +using DevHome.Settings.Models; +using DevHome.Settings.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using Windows.Foundation; +using Windows.Foundation.Collections; + +namespace DevHome.Settings.Views; + +public sealed partial class ExperimentalFeaturesPage : Page +{ + public ExperimentalFeaturesViewModel ViewModel + { + get; + } + + private readonly ILocalSettingsService _localSettingsService; + + public ObservableCollection Breadcrumbs { get; } + + public ExperimentalFeaturesPage() + { + ViewModel = Application.Current.GetService(); + _localSettingsService = Application.Current.GetService(); + this.InitializeComponent(); + + var stringResource = new StringResource("DevHome.Settings/Resources"); + Breadcrumbs = new ObservableCollection + { + new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new Breadcrumb(stringResource.GetLocalized("Settings_ExperimentalFeatures_Header"), typeof(ExperimentalFeaturesViewModel).FullName!), + }; + } + + private void BreadcrumbBar_ItemClicked(BreadcrumbBar sender, BreadcrumbBarItemClickedEventArgs args) + { + if (args.Index < Breadcrumbs.Count - 1) + { + var crumb = (Breadcrumb)args.Item; + crumb.NavigateTo(); + } + } +} diff --git a/src/App.xaml.cs b/src/App.xaml.cs index f79eb56e94..de5d2578a9 100644 --- a/src/App.xaml.cs +++ b/src/App.xaml.cs @@ -26,7 +26,6 @@ namespace DevHome; -// To learn more about WinUI 3, see https://docs.microsoft.com/windows/apps/winui/winui3/. public partial class App : Application, IApp { private readonly DispatcherQueue _dispatcherQueue; @@ -46,7 +45,27 @@ public T GetService() public static WindowEx MainWindow { get; } = new MainWindow(); - internal static NavConfig NavConfig { get; } = System.Text.Json.JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "navConfig.json")), SourceGenerationContext.Default.NavConfig)!; + private static string RemoveComments(string text) + { + var start = text.IndexOf("/*", StringComparison.Ordinal); + while (start >= 0) + { + var end = text.IndexOf("*/", start + 2, StringComparison.Ordinal); + if (end < 0) + { + end = text.Length; + } + + text = text.Remove(start, end - start + 2); + start = text.IndexOf("/*", start, StringComparison.Ordinal); + } + + return text; + } + + internal static NavConfig NavConfig { get; } = System.Text.Json.JsonSerializer.Deserialize( + RemoveComments(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "navConfig.jsonc"))), + SourceGenerationContext.Default.NavConfig)!; public App() { diff --git a/src/DevHome.csproj b/src/DevHome.csproj index 72857c3eb7..92458e44bc 100644 --- a/src/DevHome.csproj +++ b/src/DevHome.csproj @@ -52,6 +52,7 @@ + @@ -59,7 +60,7 @@ Always - + Always diff --git a/src/Helpers/NavConfigHelper.cs b/src/Helpers/NavConfigHelper.cs index cdf4afcf36..6128ea35ac 100644 --- a/src/Helpers/NavConfigHelper.cs +++ b/src/Helpers/NavConfigHelper.cs @@ -11,6 +11,9 @@ internal class NavConfig { [JsonPropertyName("navMenu")] public NavMenu NavMenu { get; set; } + + [JsonPropertyName("experiments")] + public string[] ExperimentIds { get; set; } } internal class NavMenu @@ -44,6 +47,9 @@ internal class Tool [JsonPropertyName("icon")] public string Icon { get; set; } + + [JsonPropertyName("experimentId")] + public string ExperimentId { get; set; } } // Uses .NET's JSON source generator support for serializing / deserializing NavConfig to get some perf gains at startup. diff --git a/src/NavConfig.json b/src/NavConfig.jsonc similarity index 67% rename from src/NavConfig.json rename to src/NavConfig.jsonc index efceb1f74a..9763488964 100644 --- a/src/NavConfig.json +++ b/src/NavConfig.jsonc @@ -25,8 +25,21 @@ "viewModelFullName": "DevHome.ExtensionLibrary.ViewModels.ExtensionLibraryViewModel", "icon": "ea86" } + /*, + { + "identity": "DevHome.TestExperiment", + "assembly": "DevHome.Experiments", + "viewFullName": "DevHome.Experiments.Views.TestExperimentPage", + "viewModelFullName": "DevHome.Experiments.ViewModels.TestExperimentViewModel", + "icon": "f0f7", + "experimentId": "ExperimentalFeature_1" + } + */ ] } ] - } -} \ No newline at end of file + }, + "experiments": [ /* + "ExperimentalFeature_1" + */ ] +} diff --git a/src/Services/PageService.cs b/src/Services/PageService.cs index 1375dc7739..2189d12847 100644 --- a/src/Services/PageService.cs +++ b/src/Services/PageService.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.Common.Extensions; using DevHome.Contracts.Services; +using DevHome.Settings; using DevHome.Settings.ViewModels; using DevHome.Settings.Views; using DevHome.ViewModels; @@ -25,6 +27,7 @@ public PageService() Configure(); Configure(); Configure(); + Configure(); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var group in App.NavConfig.NavMenu.Groups) @@ -38,6 +41,12 @@ where assembly.GetName().Name == tool.Assembly Configure(tool.ViewModelFullName, toolType.First()); } } + + var experimentalFeaturesVM = App.Current.GetService(); + foreach (var experimentId in App.NavConfig.ExperimentIds ?? Array.Empty()) + { + experimentalFeaturesVM.Features.Add(new ExperimentalFeature(experimentId)); + } } public Type GetPageType(string key) diff --git a/src/Views/ShellPage.xaml.cs b/src/Views/ShellPage.xaml.cs index 25939e9730..b690455c71 100644 --- a/src/Views/ShellPage.xaml.cs +++ b/src/Views/ShellPage.xaml.cs @@ -4,6 +4,8 @@ using DevHome.Common.Extensions; using DevHome.Common.Helpers; using DevHome.Common.Services; +using DevHome.Settings; +using DevHome.Settings.ViewModels; using DevHome.ViewModels; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -101,17 +103,47 @@ private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, Key args.Handled = result; } + public static readonly DependencyProperty ExperimentalFeatureProperty = DependencyProperty.Register( + "ExperimentalFeature", + typeof(ExperimentalFeature), + typeof(ShellPage), + new PropertyMetadata(null, OnExperimentalFeatureChanged)); + + private static void OnExperimentalFeatureChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue != null && e.NewValue is ExperimentalFeature experimentalFeature) + { + var navigationViewItem = (NavigationViewItem)d; + navigationViewItem.Visibility = experimentalFeature.IsEnabled ? Visibility.Visible : Visibility.Collapsed; + } + } + + private static ExperimentalFeature? GetExperimentalFeature(NavigationViewItem navigationViewItem) => (ExperimentalFeature)navigationViewItem.GetValue(ExperimentalFeatureProperty); + + public void UpdateExperimentalPageState(ExperimentalFeature expFeature) + { + var nvis = NavigationViewControl.MenuItems.Where(s => GetExperimentalFeature((NavigationViewItem)s) == expFeature); + foreach (NavigationViewItem nvi in nvis) + { + nvi.Visibility = expFeature.IsEnabled ? Visibility.Visible : Visibility.Collapsed; + } + } + private void UpdateNavigationMenuItems() { + var expVM = App.Current.GetService(); foreach (var group in App.NavConfig.NavMenu.Groups) { foreach (var tool in group.Tools) { + var expFeature = expVM.Features.FirstOrDefault(x => x.Id == tool.ExperimentId); + var navigationViewItemString = $@" @@ -120,6 +152,21 @@ private void UpdateNavigationMenuItems() "; NavigationViewItem navigationViewItem = (NavigationViewItem)XamlReader.Load(navigationViewItemString); + + if (expFeature != null) + { + navigationViewItem.Visibility = expFeature.IsEnabled ? Visibility.Visible : Visibility.Collapsed; + expFeature.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(ExperimentalFeature.IsEnabled)) + { + UpdateExperimentalPageState(expFeature); + } + }; + } + + navigationViewItem.SetValue(ExperimentalFeatureProperty, expFeature); + NavigationViewControl.MenuItems.Add(navigationViewItem); } } diff --git a/tools/Experiments/src/DevHome.Experiments.csproj b/tools/Experiments/src/DevHome.Experiments.csproj new file mode 100644 index 0000000000..18a4d9bb5a --- /dev/null +++ b/tools/Experiments/src/DevHome.Experiments.csproj @@ -0,0 +1,23 @@ + + + net6.0-windows10.0.22000.0 + 10.0.17763.0 + DevHome.Experiments + x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 + true + + + + + + + + + + + + MSBuild:Compile + + + diff --git a/tools/Experiments/src/Strings/en-us/Resources.resw b/tools/Experiments/src/Strings/en-us/Resources.resw new file mode 100644 index 0000000000..02a5ec5771 --- /dev/null +++ b/tools/Experiments/src/Strings/en-us/Resources.resw @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + This is a test experiment page + The name is Experiments + + + Test Experiment page + The name of the Experiments page + + \ No newline at end of file diff --git a/tools/Experiments/src/ViewModels/TestExperimentViewModel.cs b/tools/Experiments/src/ViewModels/TestExperimentViewModel.cs new file mode 100644 index 0000000000..b36729af5b --- /dev/null +++ b/tools/Experiments/src/ViewModels/TestExperimentViewModel.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DevHome.Experiments.ViewModels; +public class TestExperimentViewModel +{ + public TestExperimentViewModel() + { + } +} diff --git a/tools/Experiments/src/Views/TestExperimentPage.xaml b/tools/Experiments/src/Views/TestExperimentPage.xaml new file mode 100644 index 0000000000..f486b1bd64 --- /dev/null +++ b/tools/Experiments/src/Views/TestExperimentPage.xaml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/tools/Experiments/src/Views/TestExperimentPage.xaml.cs b/tools/Experiments/src/Views/TestExperimentPage.xaml.cs new file mode 100644 index 0000000000..ac56566c7b --- /dev/null +++ b/tools/Experiments/src/Views/TestExperimentPage.xaml.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using DevHome.Common; +using DevHome.Experiments.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using Windows.Foundation; +using Windows.Foundation.Collections; + +namespace DevHome.Experiments.Views; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class TestExperimentPage : ToolPage +{ + public override string ShortName => "TestExperiment1"; + + public TestExperimentViewModel ViewModel + { + get; + } + + public TestExperimentPage() + { + ViewModel = new TestExperimentViewModel(); + InitializeComponent(); + } +} From 61b1786a60393e05f555c7ccea3ce185b0b9d9a2 Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Thu, 19 Oct 2023 18:17:37 -0400 Subject: [PATCH 12/69] Add IWidgetHostingService (#1724) --- .../Extensions/ServiceExtensions.cs | 2 +- .../Services/IWidgetHostingService.cs | 13 +++++++++++++ .../Services/WidgetHostingService.cs | 2 +- .../ViewModels/DashboardViewModel.cs | 4 ++-- .../DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs | 4 ++-- .../Views/CustomizeWidgetDialog.xaml.cs | 4 ++-- 6 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs diff --git a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs index d785b66cd6..eb23c1b3a1 100644 --- a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs +++ b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs @@ -16,7 +16,7 @@ public static IServiceCollection AddDashboard(this IServiceCollection services, services.AddSingleton(); // Services - services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs new file mode 100644 index 0000000000..4874974627 --- /dev/null +++ b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using Microsoft.Windows.Widgets.Hosts; + +namespace DevHome.Dashboard.Services; + +public interface IWidgetHostingService +{ + public WidgetHost GetWidgetHost(); + + public WidgetCatalog GetWidgetCatalog(); +} diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs index 8254e1ecec..4a46e93229 100644 --- a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs @@ -7,7 +7,7 @@ namespace DevHome.Dashboard.Services; -public class WidgetHostingService +public class WidgetHostingService : IWidgetHostingService { private WidgetHost _widgetHost; private WidgetCatalog _widgetCatalog; diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs index 360eb29a28..db0a66b465 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs @@ -16,7 +16,7 @@ namespace DevHome.Dashboard.ViewModels; public partial class DashboardViewModel : ObservableObject { - public WidgetHostingService WidgetHostingService { get; } + public IWidgetHostingService WidgetHostingService { get; } private readonly IPackageDeploymentService _packageDeploymentService; @@ -35,7 +35,7 @@ public partial class DashboardViewModel : ObservableObject [ObservableProperty] private bool _isLoading; - public DashboardViewModel(IPackageDeploymentService packageDeploymentService, WidgetHostingService widgetHostingService) + public DashboardViewModel(IPackageDeploymentService packageDeploymentService, IWidgetHostingService widgetHostingService) { _packageDeploymentService = packageDeploymentService; WidgetHostingService = widgetHostingService; diff --git a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs index eabccdba47..e40669ba40 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs @@ -31,7 +31,7 @@ public sealed partial class AddWidgetDialog : ContentDialog public WidgetViewModel ViewModel { get; set; } - private readonly WidgetHostingService _hostingService; + private readonly IWidgetHostingService _hostingService; public AddWidgetDialog( AdaptiveCardRenderer renderer, @@ -39,7 +39,7 @@ public AddWidgetDialog( ElementTheme theme) { ViewModel = new WidgetViewModel(null, Microsoft.Windows.Widgets.WidgetSize.Large, null, renderer, dispatcher); - _hostingService = Application.Current.GetService(); + _hostingService = Application.Current.GetService(); this.InitializeComponent(); diff --git a/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs index fc2a0deecb..dccded6336 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/CustomizeWidgetDialog.xaml.cs @@ -21,7 +21,7 @@ public sealed partial class CustomizeWidgetDialog : ContentDialog public WidgetViewModel ViewModel { get; set; } - private readonly WidgetHostingService _hostingService; + private readonly IWidgetHostingService _hostingService; private readonly WidgetDefinition _widgetDefinition; private static DispatcherQueue _dispatcher; @@ -31,7 +31,7 @@ public CustomizeWidgetDialog(AdaptiveCardRenderer renderer, DispatcherQueue disp ViewModel = new WidgetViewModel(null, Microsoft.Windows.Widgets.WidgetSize.Large, widgetDefinition, renderer, dispatcher); ViewModel.IsInEditMode = true; - _hostingService = Application.Current.GetService(); + _hostingService = Application.Current.GetService(); this.InitializeComponent(); From cddec000f19444730d80f1abc2f5fd290778db98 Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:57:24 -0400 Subject: [PATCH 13/69] Create WidgetIconService (#1725) --- .../Controls/WidgetControl.xaml.cs | 8 +++- .../Extensions/ServiceExtensions.cs | 1 + .../Services/IWidgetIconService.cs | 23 +++++++++++ .../WidgetIconService.cs} | 40 +++++++++---------- .../ViewModels/DashboardViewModel.cs | 8 +++- .../Views/AddWidgetDialog.xaml.cs | 4 +- .../Views/DashboardView.xaml.cs | 10 ++--- 7 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 tools/Dashboard/DevHome.Dashboard/Services/IWidgetIconService.cs rename tools/Dashboard/DevHome.Dashboard/{Helpers/WidgetIconCache.cs => Services/WidgetIconService.cs} (78%) diff --git a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs index 1e69a250bf..579bbaad40 100644 --- a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using DevHome.Common.Extensions; using DevHome.Dashboard.Helpers; +using DevHome.Dashboard.Services; using DevHome.Dashboard.ViewModels; using DevHome.Dashboard.Views; using Microsoft.UI.Xaml; @@ -228,11 +230,13 @@ private void OnCustomizeWidgetClick(object sender, RoutedEventArgs e) private async void OnActualThemeChanged(FrameworkElement sender, object args) { - WidgetHeaderIcon.Fill = await WidgetIconCache.GetBrushForWidgetIconAsync(WidgetSource.WidgetDefinition, ActualTheme); + WidgetHeaderIcon.Fill = await Application.Current.GetService() + .GetBrushForWidgetIconAsync(WidgetSource.WidgetDefinition, ActualTheme); } private async void UpdateWidgetHeaderIconFillAsync() { - WidgetHeaderIcon.Fill = await WidgetIconCache.GetBrushForWidgetIconAsync(WidgetSource.WidgetDefinition, ActualTheme); + WidgetHeaderIcon.Fill = await Application.Current.GetService() + .GetBrushForWidgetIconAsync(WidgetSource.WidgetDefinition, ActualTheme); } } diff --git a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs index eb23c1b3a1..c5fb2bb1c0 100644 --- a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs +++ b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs @@ -17,6 +17,7 @@ public static IServiceCollection AddDashboard(this IServiceCollection services, // Services services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/tools/Dashboard/DevHome.Dashboard/Services/IWidgetIconService.cs b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetIconService.cs new file mode 100644 index 0000000000..48cb800c67 --- /dev/null +++ b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetIconService.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.Windows.Widgets.Hosts; + +namespace DevHome.Dashboard.Services; + +public interface IWidgetIconService +{ + public Task CacheAllWidgetIconsAsync(); + + public Task AddIconsToCacheAsync(WidgetDefinition widgetDef); + + public void RemoveIconsFromCache(string definitionId); + + public Task GetWidgetIconForThemeAsync(WidgetDefinition widgetDefinition, ElementTheme theme); + + public Task GetBrushForWidgetIconAsync(WidgetDefinition widgetDefinition, ElementTheme theme); +} diff --git a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetIconCache.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetIconService.cs similarity index 78% rename from tools/Dashboard/DevHome.Dashboard/Helpers/WidgetIconCache.cs rename to tools/Dashboard/DevHome.Dashboard/Services/WidgetIconService.cs index f2be7d1176..0df81f815d 100644 --- a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetIconCache.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetIconService.cs @@ -4,30 +4,30 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.UI.Dispatching; +using DevHome.Dashboard.Helpers; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.Windows.Widgets.Hosts; using Windows.Storage.Streams; +using WinUIEx; -namespace DevHome.Dashboard.Helpers; -internal class WidgetIconCache +namespace DevHome.Dashboard.Services; + +public class WidgetIconService : IWidgetIconService { - private static DispatcherQueue _dispatcher; + private readonly WindowEx _windowEx; - private static Dictionary _widgetLightIconCache; - private static Dictionary _widgetDarkIconCache; + private readonly IWidgetHostingService _widgetHostingService; - /// - /// Initializes a new instance of the class. - /// - /// - /// The WidgetIconCache is backed by two dictionaries, one for light themed icons and one for dark themed icons. - /// - public WidgetIconCache(DispatcherQueue dispatcher) + private readonly Dictionary _widgetLightIconCache; + private readonly Dictionary _widgetDarkIconCache; + + public WidgetIconService(WindowEx windowEx, IWidgetHostingService widgetHostingService) { - _dispatcher = dispatcher; + _windowEx = windowEx; + _widgetHostingService = widgetHostingService; + _widgetLightIconCache = new Dictionary(); _widgetDarkIconCache = new Dictionary(); } @@ -35,10 +35,10 @@ public WidgetIconCache(DispatcherQueue dispatcher) /// /// Caches icons for all widgets in the WidgetCatalog that are included in Dev Home. /// - public async Task CacheAllWidgetIconsAsync(WidgetCatalog widgetCatalog) + public async Task CacheAllWidgetIconsAsync() { var cacheTasks = new List(); - var widgetDefinitions = widgetCatalog.GetWidgetDefinitions(); + var widgetDefinitions = _widgetHostingService.GetWidgetCatalog()!.GetWidgetDefinitions(); foreach (var widgetDef in widgetDefinitions ?? Array.Empty()) { var task = AddIconsToCacheAsync(widgetDef); @@ -93,12 +93,12 @@ public void RemoveIconsFromCache(string definitionId) _widgetDarkIconCache.Remove(definitionId); } - public static async Task GetWidgetIconForThemeAsync(WidgetDefinition widgetDefinition, ElementTheme theme) + public async Task GetWidgetIconForThemeAsync(WidgetDefinition widgetDefinition, ElementTheme theme) { // Return the WidgetDefinition Id via TaskCompletionSource. Using WCT's EnqueueAsync does not suffice here, since if // we're already on the thread of the DispatcherQueue then it just directly calls the function, with no async involved. var completionSource = new TaskCompletionSource(); - _dispatcher.TryEnqueue(() => + _windowEx.DispatcherQueue.TryEnqueue(() => { completionSource.TrySetResult(widgetDefinition.Id); }); @@ -118,7 +118,7 @@ public static async Task GetWidgetIconForThemeAsync(WidgetDefinitio return image; } - public static async Task GetBrushForWidgetIconAsync(WidgetDefinition widgetDefinition, ElementTheme theme) + public async Task GetBrushForWidgetIconAsync(WidgetDefinition widgetDefinition, ElementTheme theme) { var image = await GetWidgetIconForThemeAsync(widgetDefinition, theme); @@ -135,7 +135,7 @@ private async Task WidgetIconToBitmapImageAsync(IRandomAccessStream // Return the bitmap image via TaskCompletionSource. Using WCT's EnqueueAsync does not suffice here, since if // we're already on the thread of the DispatcherQueue then it just directly calls the function, with no async involved. var completionSource = new TaskCompletionSource(); - _dispatcher.TryEnqueue(async () => + _windowEx.DispatcherQueue.TryEnqueue(async () => { using var bitmapStream = await iconStreamRef.OpenReadAsync(); var itemImage = new BitmapImage(); diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs index db0a66b465..b896937c99 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs @@ -18,6 +18,8 @@ public partial class DashboardViewModel : ObservableObject { public IWidgetHostingService WidgetHostingService { get; } + public IWidgetIconService WidgetIconService { get; } + private readonly IPackageDeploymentService _packageDeploymentService; private readonly Version minSupportedVersion400 = new (423, 3800); @@ -35,9 +37,13 @@ public partial class DashboardViewModel : ObservableObject [ObservableProperty] private bool _isLoading; - public DashboardViewModel(IPackageDeploymentService packageDeploymentService, IWidgetHostingService widgetHostingService) + public DashboardViewModel( + IPackageDeploymentService packageDeploymentService, + IWidgetHostingService widgetHostingService, + IWidgetIconService widgetIconService) { _packageDeploymentService = packageDeploymentService; + WidgetIconService = widgetIconService; WidgetHostingService = widgetHostingService; ShowDashboardBanner = ShouldShowDashboardBanner(); diff --git a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs index e40669ba40..0a7b68e919 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs @@ -32,6 +32,7 @@ public sealed partial class AddWidgetDialog : ContentDialog public WidgetViewModel ViewModel { get; set; } private readonly IWidgetHostingService _hostingService; + private readonly IWidgetIconService _widgetIconService; public AddWidgetDialog( AdaptiveCardRenderer renderer, @@ -40,6 +41,7 @@ public AddWidgetDialog( { ViewModel = new WidgetViewModel(null, Microsoft.Windows.Widgets.WidgetSize.Large, null, renderer, dispatcher); _hostingService = Application.Current.GetService(); + _widgetIconService = Application.Current.GetService(); this.InitializeComponent(); @@ -127,7 +129,7 @@ private async Task FillAvailableWidgetsAsync() private async Task BuildWidgetNavItem(WidgetDefinition widgetDefinition) { - var image = await WidgetIconCache.GetWidgetIconForThemeAsync(widgetDefinition, ActualTheme); + var image = await _widgetIconService.GetWidgetIconForThemeAsync(widgetDefinition, ActualTheme); return BuildNavItem(image, widgetDefinition.DisplayTitle); } diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs index d3c6791946..c0ffbc52da 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs @@ -37,8 +37,6 @@ public partial class DashboardView : ToolPage private static AdaptiveCardRenderer _renderer; private static Microsoft.UI.Dispatching.DispatcherQueue _dispatcher; - private readonly WidgetIconCache _widgetIconCache; - private static bool _widgetHostInitialized; private const string DraggedWidget = "DraggedWidget"; @@ -61,8 +59,6 @@ public DashboardView() _renderer = new AdaptiveCardRenderer(); _dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); - _widgetIconCache = new WidgetIconCache(_dispatcher); - ActualThemeChanged += OnActualThemeChanged; Loaded += OnLoaded; @@ -176,7 +172,7 @@ private async Task InitializeDashboard() if (EnsureHostingInitialized()) { // Cache the widget icons before we display the widgets, since we include the icons in the widgets. - await _widgetIconCache.CacheAllWidgetIconsAsync(ViewModel.WidgetHostingService.GetWidgetCatalog()!); + await ViewModel.WidgetIconService.CacheAllWidgetIconsAsync(); await ConfigureWidgetRenderer(_renderer); await RestorePinnedWidgetsAsync(); @@ -384,7 +380,7 @@ private void WidgetCatalog_WidgetProviderDefinitionDeleted(WidgetCatalog sender, private async void WidgetCatalog_WidgetDefinitionAdded(WidgetCatalog sender, WidgetDefinitionAddedEventArgs args) { Log.Logger()?.ReportInfo("DashboardView", $"WidgetCatalog_WidgetDefinitionAdded {args.Definition.Id}"); - await _widgetIconCache.AddIconsToCacheAsync(args.Definition); + await ViewModel.WidgetIconService.AddIconsToCacheAsync(args.Definition); } private async void WidgetCatalog_WidgetDefinitionUpdated(WidgetCatalog sender, WidgetDefinitionUpdatedEventArgs args) @@ -455,7 +451,7 @@ private void WidgetCatalog_WidgetDefinitionDeleted(WidgetCatalog sender, WidgetD } }); - _widgetIconCache.RemoveIconsFromCache(definitionId); + ViewModel.WidgetIconService.RemoveIconsFromCache(definitionId); } // Listen for widgets being added or removed, so we can add or remove listeners on the WidgetViewModels' properties. From 8da3e2e0c799ce6b3a31ec2a592b1eb05d71ba2e Mon Sep 17 00:00:00 2001 From: Nishit Mehta <80708599+nishitxmehta@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:06:54 +0530 Subject: [PATCH 14/69] Fix typo in HyperlinkTextBlock.xaml.cs (#1727) --- common/Views/HyperlinkTextBlock.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/Views/HyperlinkTextBlock.xaml.cs b/common/Views/HyperlinkTextBlock.xaml.cs index 1947a23e3e..6359ce249f 100644 --- a/common/Views/HyperlinkTextBlock.xaml.cs +++ b/common/Views/HyperlinkTextBlock.xaml.cs @@ -7,7 +7,7 @@ namespace DevHome.Common.Views; /// -/// Custom control for a text block that contains an hyperlink in the text. +/// Custom control for a text block that contains a hyperlink in the text. /// /// /// The property must contain a substring enclosed in square From 6ef26642d44a0ed5ecc7b37a1df5c41ff3cf341c Mon Sep 17 00:00:00 2001 From: David Bennett Date: Mon, 23 Oct 2023 10:00:30 -0700 Subject: [PATCH 15/69] Add DevHomeClosed Telemetry Event (#1726) * Add DevHomeClosed Telemetry Event * Add deployment identifier to Closed Event --- common/Helpers/Deployment.cs | 45 +++++++++++++++++++++++ src/MainWindow.xaml.cs | 6 +++ src/TelemetryEvents/DevHomeClosedEvent.cs | 39 ++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 common/Helpers/Deployment.cs create mode 100644 src/TelemetryEvents/DevHomeClosedEvent.cs diff --git a/common/Helpers/Deployment.cs b/common/Helpers/Deployment.cs new file mode 100644 index 0000000000..c05e74bdc9 --- /dev/null +++ b/common/Helpers/Deployment.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using DevHome.Logging; +using Windows.Storage; + +namespace DevHome.Common.Helpers; + +public static class Deployment +{ + private static readonly string DeploymentIdentifierKeyName = "DevHomeDeploymentIdentifier"; + + // This creates and returns a Guid associated with this deployment of DevHome. This uniquely + // identifies this deployment across multiple launches and will be different per Windows user. + // This will persist across updates, but will be removed upon package removal, or when + // ApplicationData is reset via settings. The purpose of this identifier is to correlate + // telemetry events across multiple launches for product usage metrics. + public static Guid Identifier + { + get + { + try + { + var localSettings = ApplicationData.Current.LocalSettings; + if (localSettings.Values.ContainsKey(DeploymentIdentifierKeyName)) + { + return (Guid)localSettings.Values[DeploymentIdentifierKeyName]; + } + + var newGuid = Guid.NewGuid(); + localSettings.Values[DeploymentIdentifierKeyName] = newGuid; + return newGuid; + } + catch (Exception ex) + { + // We do not want this identifer's access to ever create a problem in the + // application, so if we can't get it, return empty guid. An empty guid is also a + // signal that the data is unknown for filtering purposes. + GlobalLog.Logger?.ReportError($"Failed getting Deployment Identifier", ex); + return Guid.Empty; + } + } + } +} diff --git a/src/MainWindow.xaml.cs b/src/MainWindow.xaml.cs index a7705886ba..7fad9d9817 100644 --- a/src/MainWindow.xaml.cs +++ b/src/MainWindow.xaml.cs @@ -4,12 +4,16 @@ using DevHome.Common.Extensions; using DevHome.Common.Services; using DevHome.Helpers; +using DevHome.Telemetry; +using DevHome.TelemetryEvents; using Microsoft.UI.Xaml; namespace DevHome; public sealed partial class MainWindow : WindowEx { + private readonly DateTime mainWindowCreated; + public MainWindow() { InitializeComponent(); @@ -17,10 +21,12 @@ public MainWindow() AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/DevHome.ico")); Content = null; Title = Application.Current.GetService().GetAppNameLocalized(); + mainWindowCreated = DateTime.UtcNow; } private void MainWindow_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args) { Application.Current.GetService().SignalStopExtensionsAsync(); + TelemetryFactory.Get().Log("DevHome_MainWindow_Closed_Event", LogLevel.Critical, new DevHomeClosedEvent(mainWindowCreated)); } } diff --git a/src/TelemetryEvents/DevHomeClosedEvent.cs b/src/TelemetryEvents/DevHomeClosedEvent.cs new file mode 100644 index 0000000000..b0b363b52c --- /dev/null +++ b/src/TelemetryEvents/DevHomeClosedEvent.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System.Diagnostics.Tracing; +using DevHome.Common.Helpers; +using DevHome.Logging; +using DevHome.Telemetry; +using Microsoft.Diagnostics.Telemetry; +using Microsoft.Diagnostics.Telemetry.Internal; + +namespace DevHome.TelemetryEvents; + +[EventData] +public class DevHomeClosedEvent : EventBase +{ + public double ElapsedTime + { + get; + } + + public Guid DeploymentIdentifier + { + get; + } + + public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServiceUsage; + + public DevHomeClosedEvent(DateTime startTime) + { + ElapsedTime = (DateTime.UtcNow - startTime).TotalMilliseconds; + DeploymentIdentifier = Deployment.Identifier; + GlobalLog.Logger?.ReportDebug($"DevHome Closed Event, ElapsedTime: {ElapsedTime}ms Identifier: {DeploymentIdentifier}"); + } + + public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings) + { + // No sensitive strings to replace. + } +} From 3558adb0229992353e2eca3469fbe247597020cf Mon Sep 17 00:00:00 2001 From: Felipe G Date: Mon, 23 Oct 2023 11:41:01 -0700 Subject: [PATCH 16/69] Fixing bug alignment on Link in FRE Page card (#1720) --- src/Views/WhatsNewPage.xaml | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Views/WhatsNewPage.xaml b/src/Views/WhatsNewPage.xaml index 364cce6f8b..dade63f25c 100644 --- a/src/Views/WhatsNewPage.xaml +++ b/src/Views/WhatsNewPage.xaml @@ -7,7 +7,8 @@ xmlns:animations="using:CommunityToolkit.WinUI.Animations" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:models="using:DevHome.Models" - xmlns:behaviors="using:DevHome.Common.Behaviors" + xmlns:behaviors="using:DevHome.Common.Behaviors" + xmlns:controls="using:CommunityToolkit.WinUI.Controls" behaviors:NavigationViewHeaderBehavior.HeaderMode="Never" Loaded="OnLoaded" SizeChanged="OnSizeChanged" @@ -279,7 +280,6 @@ - - + + + + + - + + + + + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Styles/SetupFlow_ThemeResources.xaml b/tools/SetupFlow/DevHome.SetupFlow/Styles/SetupFlow_ThemeResources.xaml new file mode 100644 index 0000000000..a781a5beb5 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Styles/SetupFlow_ThemeResources.xaml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs index e93ce41816..8183347d2e 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs @@ -1,100 +1,135 @@ -// Copyright (c) Microsoft Corporation and Contributors -// Licensed under the MIT license. - -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using DevHome.SetupFlow.Common.Helpers; -using DevHome.SetupFlow.Services; - -namespace DevHome.SetupFlow.ViewModels; -public partial class PackageCatalogListViewModel : ObservableObject -{ - private readonly IWindowsPackageManager _wpm; - private readonly CatalogDataSourceLoader _catalogDataSourceLoader; - private readonly PackageCatalogViewModelFactory _packageCatalogViewModelFactory; - private bool _initialized; - - /// - /// Gets a list of package catalogs to display - /// - public ObservableCollection PackageCatalogs { get; } = new (); - - /// - /// Gets a list of shimmer indices. - /// This list is used to repeat the shimmer control {Count} times - /// - public ObservableCollection PackageCatalogShimmers { get; } = new (); - - public PackageCatalogListViewModel( - CatalogDataSourceLoader catalogDataSourceLoader, - IWindowsPackageManager wpm, - PackageCatalogViewModelFactory packageCatalogViewModelFactory) - { - _catalogDataSourceLoader = catalogDataSourceLoader; - _wpm = wpm; - _packageCatalogViewModelFactory = packageCatalogViewModelFactory; - } - - /// - /// Load the package catalogs to display - /// - public async Task LoadCatalogsAsync() - { - if (!_initialized) - { - _initialized = true; - AddShimmers(_catalogDataSourceLoader.CatalogCount); - try - { - await Task.Run(async () => await _wpm.WinGetCatalog.ConnectAsync()); - await foreach (var dataSourceCatalogs in _catalogDataSourceLoader.LoadCatalogsAsync()) - { - foreach (var catalog in dataSourceCatalogs) - { - var catalogVM = await Task.Run(() => _packageCatalogViewModelFactory(catalog)); - catalogVM.CanAddAllPackages = true; - PackageCatalogs.Add(catalogVM); - } - - RemoveShimmers(dataSourceCatalogs.Count); - } - } - catch (Exception e) - { - Log.Logger?.ReportError(Log.Component.AppManagement, $"Failed to connect to {nameof(_wpm.WinGetCatalog)}. Skipping catalogs loading operation.", e); - } - - // Remove any remaining shimmers: - // This can happen if for example a catalog was detected but not - // displayed (e.g. catalog with no packages to display) - RemoveShimmers(_catalogDataSourceLoader.CatalogCount); - } - } - - /// - /// Add package catalog shimmers - /// - /// Number of package catalog shimmers to add - private void AddShimmers(int count) - { - while (count-- > 0) - { - PackageCatalogShimmers.Add(PackageCatalogShimmers.Count); - } - } - - /// - /// Remove package catalog shimmers - /// - /// Number of package catalog shimmers to remove - private void RemoveShimmers(int count) - { - while (count-- > 0 && PackageCatalogShimmers.Any()) - { - PackageCatalogShimmers.Remove(PackageCatalogShimmers.Last()); - } - } -} +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using DevHome.SetupFlow.Behaviors; +using DevHome.SetupFlow.Common.Helpers; +using DevHome.SetupFlow.Services; + +namespace DevHome.SetupFlow.ViewModels; + +public partial class PackageCatalogListViewModel : ObservableObject +{ + private readonly IWindowsPackageManager _wpm; + private readonly CatalogDataSourceLoader _catalogDataSourceLoader; + private readonly PackageCatalogViewModelFactory _packageCatalogViewModelFactory; + private bool _initialized; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CatalogFullPath))] + private PackageCatalogViewModel _viewAllCatalog; + + public List CatalogFullPath => new () + { + AppManagementBehavior.Title, + ViewAllCatalog?.Name ?? string.Empty, + }; + + /// + /// Gets a list of package catalogs to display + /// + public ObservableCollection PackageCatalogs { get; } = new (); + + /// + /// Gets a list of shimmer indices. + /// This list is used to repeat the shimmer control {Count} times + /// + public ObservableCollection PackageCatalogShimmers { get; } = new (); + + public PackageCatalogListViewModel( + CatalogDataSourceLoader catalogDataSourceLoader, + IWindowsPackageManager wpm, + PackageCatalogViewModelFactory packageCatalogViewModelFactory) + { + _catalogDataSourceLoader = catalogDataSourceLoader; + _wpm = wpm; + _packageCatalogViewModelFactory = packageCatalogViewModelFactory; + } + + /// + /// Load the package catalogs to display + /// + public async Task LoadCatalogsAsync() + { + if (!_initialized) + { + _initialized = true; + AddShimmers(_catalogDataSourceLoader.CatalogCount); + try + { + await Task.Run(async () => await _wpm.WinGetCatalog.ConnectAsync()); + await foreach (var dataSourceCatalogs in _catalogDataSourceLoader.LoadCatalogsAsync()) + { + foreach (var catalog in dataSourceCatalogs) + { + var catalogVM = await Task.Run(() => _packageCatalogViewModelFactory(catalog)); + catalogVM.CanAddAllPackages = true; + PackageCatalogs.Add(catalogVM); + } + + RemoveShimmers(dataSourceCatalogs.Count); + } + } + catch (Exception e) + { + Log.Logger?.ReportError(Log.Component.AppManagement, $"Failed to connect to {nameof(_wpm.WinGetCatalog)}. Skipping catalogs loading operation.", e); + } + + // Remove any remaining shimmers: + // This can happen if for example a catalog was detected but not + // displayed (e.g. catalog with no packages to display) + RemoveShimmers(_catalogDataSourceLoader.CatalogCount); + } + } + + /// + /// Add package catalog shimmers + /// + /// Number of package catalog shimmers to add + private void AddShimmers(int count) + { + while (count-- > 0) + { + PackageCatalogShimmers.Add(PackageCatalogShimmers.Count); + } + } + + /// + /// Remove package catalog shimmers + /// + /// Number of package catalog shimmers to remove + private void RemoveShimmers(int count) + { + while (count-- > 0 && PackageCatalogShimmers.Any()) + { + PackageCatalogShimmers.Remove(PackageCatalogShimmers.Last()); + } + } + + [RelayCommand] + private void ViewAllPackages(PackageCatalogViewModel catalog) + { + AppManagementBehavior.SetHeaderVisibility(false); + ViewAllCatalog = catalog; + } + + [RelayCommand] + private void ExitViewAllPackages() + { + AppManagementBehavior.SetHeaderVisibility(true); + ViewAllCatalog = null; + } + + [RelayCommand] + private void OnLoaded() + { + // When the view is loaded, ensure we exit the view all packages mode + ExitViewAllPackages(); + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml index c6b3bdda15..b171fd8e29 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml @@ -10,6 +10,7 @@ xmlns:ctControls="using:CommunityToolkit.WinUI.Controls" xmlns:converters="using:CommunityToolkit.WinUI.Converters" xmlns:viewmodels="using:DevHome.SetupFlow.ViewModels" + xmlns:controls="using:DevHome.SetupFlow.Controls" mc:Ignorable="d"> @@ -38,41 +39,18 @@ - - - 0 - 0 - 0 - 0 - 0 - 0 - 18 - + - - - - - - - - - + - + + Style="{ThemeResource AppManagementPackageDescriptionTextBlock}" + Text="{x:Bind PackageDescription, Mode=OneWay}" /> - + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml index c0049432d8..79fc10efd5 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml @@ -12,6 +12,7 @@ xmlns:controls="using:DevHome.SetupFlow.Controls" xmlns:selectors="using:DevHome.SetupFlow.Selectors" xmlns:viewmodels="using:DevHome.SetupFlow.ViewModels" + xmlns:behaviors="using:DevHome.SetupFlow.Behaviors" xmlns:views="using:DevHome.SetupFlow.Views" xmlns:ic="using:Microsoft.Xaml.Interactions.Core" xmlns:i="using:Microsoft.Xaml.Interactivity" @@ -25,6 +26,10 @@ + + + + @@ -34,9 +39,10 @@ - + @@ -48,6 +54,7 @@ QueryIcon="Find" HorizontalAlignment="Left" Width="400" + Margin="0,0,0,20" IsEnabled="{x:Bind ViewModel.SearchBoxEnabled, Mode=OneWay}" x:Uid="ms-resource:///DevHome.SetupFlow/Resources/SearchBox"> @@ -58,7 +65,7 @@ - + - - - 0 - 0 - 0 - 0 - 0 - 0 - 18 - + - - - - - - - - - + - + - + @@ -171,12 +147,10 @@ - + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml.cs index 21e34e34d8..2558202ed3 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml.cs @@ -2,16 +2,25 @@ // Licensed under the MIT License. using DevHome.SetupFlow.ViewModels; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace DevHome.SetupFlow.Views; public sealed partial class AppManagementView : UserControl { + public string Title => SetupShell.Title; + public AppManagementView() { this.InitializeComponent(); } public AppManagementViewModel ViewModel => (AppManagementViewModel)this.DataContext; + + public void SetHeaderVisibility(Visibility visibility) + { + SetupShell.HeaderVisibility = visibility; + SearchBox.Visibility = visibility; + } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/PackageCatalogListView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/PackageCatalogListView.xaml index 951a70afb4..3de54ffdfc 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/PackageCatalogListView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/PackageCatalogListView.xaml @@ -6,38 +6,106 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewmodels="using:DevHome.SetupFlow.ViewModels" xmlns:views="using:DevHome.SetupFlow.Views" + xmlns:converters="using:CommunityToolkit.WinUI.Converters" + xmlns:i="using:Microsoft.Xaml.Interactivity" + xmlns:ic="using:Microsoft.Xaml.Interactions.Core" mc:Ignorable="d"> - - - 6 - + + 6 + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + - + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml index 7583b4fd5a..bde815d538 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml @@ -4,7 +4,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ctControls="using:CommunityToolkit.WinUI.Controls" xmlns:converters="using:CommunityToolkit.WinUI.Converters" xmlns:viewModels="using:DevHome.SetupFlow.ViewModels" xmlns:i="using:Microsoft.Xaml.Interactivity" @@ -12,6 +11,7 @@ xmlns:models="using:DevHome.SetupFlow.Models" xmlns:commonviews="using:DevHome.Common.Views" xmlns:setupFlowBehaviors="using:DevHome.SetupFlow.Behaviors" + xmlns:controls="using:DevHome.SetupFlow.Controls" setupFlowBehaviors:SetupFlowNavigationBehavior.PreviousVisibility="Collapsed" setupFlowBehaviors:SetupFlowNavigationBehavior.NextVisibility="Collapsed" mc:Ignorable="d"> @@ -369,26 +369,20 @@ - - - 0 - 0 - 0 - 0 - 0 - 0 - 18 - - - - - - - - - - - + + + + + + + + + + + + + + From 7f1dac91bedfead79b06d90e0429ca584a457489 Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:15:09 -0400 Subject: [PATCH 28/69] Clean up partially created widget on Esc (#1770) --- .../Views/DashboardView.xaml.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs index 08ef0e2dc3..88d2bedfd0 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs @@ -249,6 +249,17 @@ public async Task AddWidgetClickAsync() XamlRoot = this.XamlRoot, RequestedTheme = this.ActualTheme, }; + + // If the dialog was closed in a way we don't already handle (for example, pressing Esc), + // delete the partially created widget. + dialog.Closed += async (sender, args) => + { + if (dialog.AddedWidget == null && dialog.ViewModel.Widget != null) + { + await dialog.ViewModel.Widget.DeleteAsync(); + } + }; + _ = await dialog.ShowAsync(); var newWidget = dialog.AddedWidget; @@ -451,6 +462,16 @@ private async Task EditWidget(WidgetViewModel widgetViewModel) XamlRoot = this.XamlRoot, RequestedTheme = this.ActualTheme, }; + + // If the dialog was closed in a way we don't already handle (for example, pressing Esc), + // delete the partially created widget. + dialog.Closed += async (sender, args) => + { + if (dialog.EditedWidget == null && dialog.ViewModel.Widget != null) + { + await dialog.ViewModel.Widget.DeleteAsync(); + } + }; _ = await dialog.ShowAsync(); var newWidget = dialog.EditedWidget; From e605e29845e687b58b8dc3f29d9b1c8f9e2968df Mon Sep 17 00:00:00 2001 From: Nishit Mehta <80708599+nishitxmehta@users.noreply.github.com> Date: Tue, 31 Oct 2023 05:44:35 +0530 Subject: [PATCH 29/69] Fixed typo in GetReposEvent.cs (#1777) --- .../SetupFlow/RepoTool/RepoDialog/GetReposEvent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/TelemetryEvents/SetupFlow/RepoTool/RepoDialog/GetReposEvent.cs b/common/TelemetryEvents/SetupFlow/RepoTool/RepoDialog/GetReposEvent.cs index 75944ee05c..5fabd2ce8d 100644 --- a/common/TelemetryEvents/SetupFlow/RepoTool/RepoDialog/GetReposEvent.cs +++ b/common/TelemetryEvents/SetupFlow/RepoTool/RepoDialog/GetReposEvent.cs @@ -56,6 +56,6 @@ public GetReposEvent(string stageName, string providerName, IDeveloperId develop public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings) { - // The only sensitive strings is the developerID. GetHashedDeveloperId is used to hash the developerId. + // The only sensitive string is the developerID. GetHashedDeveloperId is used to hash the developerId. } } From 77a8fc1c768e9636638c7443e510e71c11520867 Mon Sep 17 00:00:00 2001 From: Shivam Singh <103785990+Shivam250702@users.noreply.github.com> Date: Tue, 31 Oct 2023 22:01:15 +0530 Subject: [PATCH 30/69] Update README.md (#1744) Co-authored-by: Kayla Cinnamon --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7eb83b8fd..fd8f5114b5 100644 --- a/README.md +++ b/README.md @@ -118,4 +118,4 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Trademarks -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos is subject to those third-parties' policies. From 6b5225870832f2d00a2dbb6df74bdad940a0500d Mon Sep 17 00:00:00 2001 From: Darren Hoehna Date: Tue, 31 Oct 2023 10:07:58 -0700 Subject: [PATCH 31/69] Making corners more rounded. (#1798) Co-authored-by: Darren Hoehna --- tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml index b7d504553a..2f96266f74 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml @@ -62,7 +62,7 @@ https://github.com/microsoft/devhome/issues/626--> + MinHeight="64" CornerRadius="4" Padding="24,0,24,7" > From d6ff316723872c0bcdc39108dcd4853e76a970aa Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:17:30 -0400 Subject: [PATCH 32/69] Change last occurrences of plugin to extension (#1718) * Remove plugin from tools.md * Update Microsoft.Windows.DevHome.SDK.nuspec --- docs/tools.md | 4 ++-- extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tools.md b/docs/tools.md index 7f4155fb20..76856982e5 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -67,5 +67,5 @@ The Widget UI consists of two main parts. At the top, there is a context menu an Widgets are rendered by Adaptive Cards, and there are a few ways Dev Home customizes the look and feel of the cards. Please note all of these are subject to change while Dev Home is in Preview. * There are [HostConfig](https://learn.microsoft.com/en-us/adaptive-cards/sdk/rendering-cards/uwp/host-config) files that define common styles (e.g., font family, font sizes, default spacing) and behaviors (e.g., max number of actions) for all the widgets. There is one for [light mode](../tools/Dashboard/DevHome.Dashboard/Assets/HostConfigLight.json) and one for [dark mode](../tools/Dashboard/DevHome.Dashboard/Assets/HostConfigDark.json). -* Dev Home supports a custom AdaptiveElement type called [`LabelGroup`](../common/Renderers/LabelGroup.cs). This allows a card author to render a set of labels, each with a specified background color. For an example of how to use this type, please see the [GitHub Issues widget](https://github.com/microsoft/devhomegithubextension/blob/main/src/GithubPlugin/Widgets/Templates/GithubIssuesTemplate.json). -* When a widget is added or customized, Dev Home will show the widget in a ContentDialog, allowing for more space in which to put configuration UI. To signify that your widget is in this "configuration" mode, Dev Home supports the JSON field `"configuring" = true`. For an example of this, see [GitHubWidget](https://github.com/microsoft/devhomegithubextension/blob/main/src/GithubPlugin/Widgets/GithubWidget.cs). +* Dev Home supports a custom AdaptiveElement type called [`LabelGroup`](../common/Renderers/LabelGroup.cs). This allows a card author to render a set of labels, each with a specified background color. For an example of how to use this type, please see the [GitHub Issues widget](https://github.com/microsoft/devhomegithubextension/blob/main/src/GitHubExtension/Widgets/Templates/GitHubIssuesTemplate.json). +* When a widget is added or customized, Dev Home will show the widget in a ContentDialog, allowing for more space in which to put configuration UI. To signify that your widget is in this "configuration" mode, Dev Home supports the JSON field `"configuring" = true`. For an example of this, see [GitHubWidget](https://github.com/microsoft/devhomegithubextension/blob/main/src/GitHubExtension/Widgets/GitHubWidget.cs). diff --git a/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec b/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec index 19af23af10..7ff3c8b0c7 100644 --- a/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec +++ b/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec @@ -7,9 +7,9 @@ Microsoft Microsoft false - Dev Home SDK provides support for creating Dev Home plugins on Windows. + Dev Home SDK provides support for creating Dev Home extensions on Windows. Release notes are available on the Dev Home repository. - Dev Home Windows App Plugin + Dev Home Windows Extension © Microsoft Corporation. All rights reserved. https://github.com/microsoft/devhome @@ -33,4 +33,4 @@ - \ No newline at end of file + From 87c933d2214a6ae95d1824c008730950f82d98fd Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:59:12 -0400 Subject: [PATCH 33/69] Mention AC schema version in tools.md (#1778) --- docs/tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tools.md b/docs/tools.md index 76856982e5..172f342daf 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -66,6 +66,7 @@ Each widget is represented by a [`WidgetViewModel`](../tools/Dashboard/DevHome.D The Widget UI consists of two main parts. At the top, there is a context menu and an attribution area. For more information on these components, please read [Built-in widget UI components](https://learn.microsoft.com/en-us/windows/apps/design/widgets/widgets-states-and-ui#built-in-widget-ui-components). The rest of the widget content is an [Adaptive Card](https://learn.microsoft.com/en-us/windows/apps/design/widgets/widgets-create-a-template) provided by the [Widget Provider](https://learn.microsoft.com/en-us/windows/apps/develop/widgets/widget-providers). Widgets are rendered by Adaptive Cards, and there are a few ways Dev Home customizes the look and feel of the cards. Please note all of these are subject to change while Dev Home is in Preview. +* Dev Home widgets use the [Adaptive Card schema](https://adaptivecards.io/explorer/) version 1.5, which is the most recent schema supported by the WinUI 3 Adaptive Card renderer. * There are [HostConfig](https://learn.microsoft.com/en-us/adaptive-cards/sdk/rendering-cards/uwp/host-config) files that define common styles (e.g., font family, font sizes, default spacing) and behaviors (e.g., max number of actions) for all the widgets. There is one for [light mode](../tools/Dashboard/DevHome.Dashboard/Assets/HostConfigLight.json) and one for [dark mode](../tools/Dashboard/DevHome.Dashboard/Assets/HostConfigDark.json). * Dev Home supports a custom AdaptiveElement type called [`LabelGroup`](../common/Renderers/LabelGroup.cs). This allows a card author to render a set of labels, each with a specified background color. For an example of how to use this type, please see the [GitHub Issues widget](https://github.com/microsoft/devhomegithubextension/blob/main/src/GitHubExtension/Widgets/Templates/GitHubIssuesTemplate.json). * When a widget is added or customized, Dev Home will show the widget in a ContentDialog, allowing for more space in which to put configuration UI. To signify that your widget is in this "configuration" mode, Dev Home supports the JSON field `"configuring" = true`. For an example of this, see [GitHubWidget](https://github.com/microsoft/devhomegithubextension/blob/main/src/GitHubExtension/Widgets/GitHubWidget.cs). From da1996b956e9cd16e43e8abb2982d1e69c81d52a Mon Sep 17 00:00:00 2001 From: Saksham Bhugra <85192629+sh4d0wy@users.noreply.github.com> Date: Wed, 1 Nov 2023 22:41:07 +0530 Subject: [PATCH 34/69] Update readme.md (#1761) --- docs/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index ac42503855..fe9db5461b 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -30,7 +30,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and you - We don't close issues automatically when referenced in a PR, so after the PR is merged: - mark the issue(s), that the PR solved, with the `Resolution-Fix-Committed` label, remove the `In progress` label and if the issue is assigned to a project, move the item to the `Done` status. - don't close the issue if it's a bug in the current released version since users tend to not search for closed issues, we will close the resolved issues when a new version is released. - - if it's not a code fix that effects the end user, the issue can be closed (for example a fix in the build or a code refactoring and so on). + - if it's not a code fix that affects the end user, the issue can be closed (for example a fix in the build or a code refactoring and so on). --> ## Compiling Dev Home @@ -59,4 +59,4 @@ It's responsible for: - [Dev Home Architecture](./architecture.md) - [Dev Home Tools](./tools.md) -- [Dev Home Extensions](./extensions.md) \ No newline at end of file +- [Dev Home Extensions](./extensions.md) From ec6dbabfe2435a56a6e4fb6487d8c1f075c84c3f Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:12:10 -0700 Subject: [PATCH 35/69] Update window title bar text foreground (#1759) * Update foreground from code behind * Repaint * Remove unused code --- common/Helpers/TitleBarHelper.cs | 31 +----- common/Windows/WindowTitleBar.xaml | 6 +- common/Windows/WindowTitleBar.xaml.cs | 144 ++++++++++++++------------ src/Views/ShellPage.xaml.cs | 1 + 4 files changed, 84 insertions(+), 98 deletions(-) diff --git a/common/Helpers/TitleBarHelper.cs b/common/Helpers/TitleBarHelper.cs index 7f3575b476..c45cc521fe 100644 --- a/common/Helpers/TitleBarHelper.cs +++ b/common/Helpers/TitleBarHelper.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. -using System; -using System.Runtime.InteropServices; - using Microsoft.UI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; @@ -17,16 +14,6 @@ namespace DevHome.Common.Helpers; // https://github.com/microsoft/TemplateStudio/issues/4516 public class TitleBarHelper { - private const int WAINACTIVE = 0x00; - private const int WAACTIVE = 0x01; - private const int WMACTIVATE = 0x0006; - - [DllImport("user32.dll")] - private static extern IntPtr GetActiveWindow(); - - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam); - public static void UpdateTitleBar(Window window, ElementTheme theme) { if (window.ExtendsContentIntoTitleBar) @@ -78,29 +65,17 @@ public static void UpdateTitleBar(Window window, ElementTheme theme) Application.Current.Resources["WindowCaptionBackground"] = new SolidColorBrush(Colors.Transparent); Application.Current.Resources["WindowCaptionBackgroundDisabled"] = new SolidColorBrush(Colors.Transparent); - - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window); - if (hwnd == GetActiveWindow()) - { - SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero); - SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero); - } - else - { - SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero); - SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero); - } } } /// /// Gets the title bar text color brush based on the window activation state. /// - /// Window activation state + /// Window activation state /// Corresponding color brush for the title bar text - public static SolidColorBrush GetTitleBarTextColorBrush(WindowActivationState state) + public static SolidColorBrush GetTitleBarTextColorBrush(bool isWindowActive) { - var resource = state == WindowActivationState.Deactivated ? "WindowCaptionForegroundDisabled" : "WindowCaptionForeground"; + var resource = isWindowActive ? "WindowCaptionForeground" : "WindowCaptionForegroundDisabled"; return (SolidColorBrush)Application.Current.Resources[resource]; } } diff --git a/common/Windows/WindowTitleBar.xaml b/common/Windows/WindowTitleBar.xaml index 6b3ef2677b..bf7e13a019 100644 --- a/common/Windows/WindowTitleBar.xaml +++ b/common/Windows/WindowTitleBar.xaml @@ -20,10 +20,6 @@ x:Key="IconContainerConverter" FalseValue="auto" TrueValue="0"/> - @@ -56,8 +52,8 @@ -/// A title bar that can be used in place of the default title bar when the -/// host window set to true. -/// -public sealed partial class WindowTitleBar : UserControl -{ - public event EventHandler? TitleChanged; - - public string Title - { - get => (string)GetValue(TitleProperty); - set => SetValue(TitleProperty, value); - } - - public IconElement Icon - { - get => (IconElement)GetValue(IconProperty) ?? DefaultIconContent; - set => SetValue(IconProperty, value); - } - - public bool HideIcon - { - get => (bool)GetValue(HideIconProperty); - set => SetValue(HideIconProperty, value); - } - - /// - /// Gets or sets a value indicating whether the title bar is active. - /// - /// - /// Title bars help users differentiate when a window is active and - /// inactive. All title bar elements should be semi-transparent when the - /// window is inactive. - /// Reference: - /// - public bool IsActive - { - get => (bool)GetValue(IsActiveProperty); - set => SetValue(IsActiveProperty, value); - } - - public WindowTitleBar() - { - this.InitializeComponent(); - } - - private static void OnTitleChanged(WindowTitleBar windowTitleBar, string newValue) - { - windowTitleBar.TitleChanged?.Invoke(windowTitleBar, newValue); - } - - private static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(WindowTitleBar), new PropertyMetadata(null, (s, e) => OnTitleChanged((WindowTitleBar)s, (string)e.NewValue))); - private static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(WindowTitleBar), new PropertyMetadata(null)); - private static readonly DependencyProperty HideIconProperty = DependencyProperty.Register(nameof(HideIcon), typeof(bool), typeof(WindowTitleBar), new PropertyMetadata(false)); - private static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(nameof(IsActive), typeof(bool), typeof(WindowTitleBar), new PropertyMetadata(true)); -} +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using DevHome.Common.Helpers; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace DevHome.Common.Windows; + +/// +/// A title bar that can be used in place of the default title bar when the +/// host window set to true. +/// +public sealed partial class WindowTitleBar : UserControl +{ + public event EventHandler? TitleChanged; + + public string Title + { + get => (string)GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + public IconElement Icon + { + get => (IconElement)GetValue(IconProperty) ?? DefaultIconContent; + set => SetValue(IconProperty, value); + } + + public bool HideIcon + { + get => (bool)GetValue(HideIconProperty); + set => SetValue(HideIconProperty, value); + } + + /// + /// Gets or sets a value indicating whether the title bar is active. + /// + /// + /// Title bars help users differentiate when a window is active and + /// inactive. All title bar elements should be semi-transparent when the + /// window is inactive. + /// Reference: + /// + public bool IsActive + { + get => (bool)GetValue(IsActiveProperty); + set => SetValue(IsActiveProperty, value); + } + + public WindowTitleBar() + { + this.InitializeComponent(); + } + + public void Repaint() + { + // Update the title text block foreground from code behind after the + // window activation state or system theme has changed, and after the WindowCaption* + // brushes have been updated. More details in TitleBarHelper.UpdateTitleBar method. + TitleTextBlock.Foreground = TitleBarHelper.GetTitleBarTextColorBrush(IsActive); + } + + private static void OnTitleChanged(WindowTitleBar windowTitleBar, string newValue) + { + windowTitleBar.TitleChanged?.Invoke(windowTitleBar, newValue); + } + + private static void OnIsActiveChanged(WindowTitleBar windowTitleBar, bool newValue) + { + windowTitleBar.Repaint(); + } + + private static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(WindowTitleBar), new PropertyMetadata(null, (s, e) => OnTitleChanged((WindowTitleBar)s, (string)e.NewValue))); + private static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(WindowTitleBar), new PropertyMetadata(null)); + private static readonly DependencyProperty HideIconProperty = DependencyProperty.Register(nameof(HideIcon), typeof(bool), typeof(WindowTitleBar), new PropertyMetadata(false)); + private static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(nameof(IsActive), typeof(bool), typeof(WindowTitleBar), new PropertyMetadata(true, (s, e) => OnIsActiveChanged((WindowTitleBar)s, (bool)e.NewValue))); +} diff --git a/src/Views/ShellPage.xaml.cs b/src/Views/ShellPage.xaml.cs index b690455c71..84951a3dd9 100644 --- a/src/Views/ShellPage.xaml.cs +++ b/src/Views/ShellPage.xaml.cs @@ -54,6 +54,7 @@ private void OnActualThemeChanged(FrameworkElement sender, object args) { // Update the title bar if the system theme changes. TitleBarHelper.UpdateTitleBar(App.MainWindow, ActualTheme); + AppTitleBar.Repaint(); } private void MainWindow_Activated(object sender, WindowActivatedEventArgs args) From 1785b4cb8fcd6c3b204d519d45dcd6a2e3ebfc7c Mon Sep 17 00:00:00 2001 From: Hamna Rauf Date: Wed, 1 Nov 2023 22:15:12 +0500 Subject: [PATCH 36/69] Fix alignment of expander (#1746) --- settings/DevHome.Settings/Views/FeedbackPage.xaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml b/settings/DevHome.Settings/Views/FeedbackPage.xaml index 3cdc481719..8d6ec387a1 100644 --- a/settings/DevHome.Settings/Views/FeedbackPage.xaml +++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml @@ -37,7 +37,7 @@ - + @@ -49,7 +49,7 @@ - + @@ -59,7 +59,7 @@ - + From 5d94969581211d50baae49521af54f6b11a6efe0 Mon Sep 17 00:00:00 2001 From: Mohit Dhote <146939900+mohitd404@users.noreply.github.com> Date: Wed, 1 Nov 2023 22:51:32 +0530 Subject: [PATCH 37/69] Added Contributors avatars in readme.md (#1781) * Added Contributors avatars in readme.md Fixed Issue #1775 * Updated changes --------- Co-authored-by: Eric Johnson --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fd8f5114b5..c2673192bd 100644 --- a/README.md +++ b/README.md @@ -119,3 +119,9 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos is subject to those third-parties' policies. + +## Thanks to our contributors + + + + From 570e33e0a97e0e2fd5e6e6b85a7465a2ac1fec14 Mon Sep 17 00:00:00 2001 From: Hamna Rauf Date: Wed, 1 Nov 2023 22:53:49 +0500 Subject: [PATCH 38/69] Add General Feedback option (#1741) Co-authored-by: Eric Johnson --- .../Strings/en-us/Resources.resw | 37 +++++++++++++++++++ .../DevHome.Settings/Views/FeedbackPage.xaml | 17 +++++++++ .../Views/FeedbackPage.xaml.cs | 21 +++++++++++ 3 files changed, 75 insertions(+) diff --git a/settings/DevHome.Settings/Strings/en-us/Resources.resw b/settings/DevHome.Settings/Strings/en-us/Resources.resw index 5d2c3fd83d..f603f14d90 100644 --- a/settings/DevHome.Settings/Strings/en-us/Resources.resw +++ b/settings/DevHome.Settings/Strings/en-us/Resources.resw @@ -193,6 +193,43 @@ Related links + + General feedback + BlankIssue is for general feedback that does not categorize under given options + + + Give general comments and suggestions + Details about when this option should be used + + + Feedback + Begin to fill out the blank issue content dialog + + + Cancel + Close out of the content dialog + + + Preview on GitHub + Navigate to the GitHub issue to see the issue form + + + Give general feedback + + + Title + + + Enter title + General feedback title + + + Description + + + Share your thoughts with us. + General feedback description + Feedback diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml b/settings/DevHome.Settings/Views/FeedbackPage.xaml index 8d6ec387a1..db16bf1d97 100644 --- a/settings/DevHome.Settings/Views/FeedbackPage.xaml +++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml @@ -90,6 +90,16 @@ + + + + + + + + + + @@ -118,6 +128,13 @@ public partial class MainPageViewModel : SetupPageViewModelBase { - private const string _hideSetupFlowBannerKey = "HideSetupFlowBanner"; - private readonly IHost _host; private readonly IWindowsPackageManager _wpm; - [ObservableProperty] - private bool _showBanner = true; + public MainPageBannerViewModel BannerViewModel { get; } [ObservableProperty] private bool _showDevDriveItem; @@ -57,7 +53,8 @@ public MainPageViewModel( ISetupFlowStringResource stringResource, SetupFlowOrchestrator orchestrator, IWindowsPackageManager wpm, - IHost host) + IHost host, + MainPageBannerViewModel bannerViewModel) : base(stringResource, orchestrator) { _host = host; @@ -66,7 +63,8 @@ public MainPageViewModel( IsNavigationBarVisible = false; IsStepPage = false; ShowDevDriveItem = DevDriveUtil.IsDevDriveFeatureEnabled; - ShowBanner = ShouldShowSetupFlowBanner(); + + BannerViewModel = bannerViewModel; } protected async override Task OnFirstNavigateToAsync() @@ -83,15 +81,6 @@ protected async override Task OnFirstNavigateToAsync() } } - [RelayCommand] - private void HideBanner() - { - TelemetryFactory.Get().LogCritical("MainPage_HideLearnMoreBanner_Event", false, Orchestrator.ActivityId); - var roamingProperties = ApplicationData.Current.RoamingSettings.Values; - roamingProperties[_hideSetupFlowBannerKey] = bool.TrueString; - ShowBanner = false; - } - /// /// Starts the setup flow including the pages for the given task groups. /// @@ -186,12 +175,6 @@ private async Task StartConfigurationFileAsync() } } - [RelayCommand] - private async Task BannerButtonAsync() - { - await Launcher.LaunchUriAsync(new ("https://go.microsoft.com/fwlink/?linkid=2235076")); - } - [RelayCommand] private void HideAppInstallerUpdateNotification() { @@ -206,10 +189,4 @@ private async Task UpdateAppInstallerAsync() Log.Logger?.ReportInfo(Log.Component.MainPage, "Opening AppInstaller in the Store app"); await Launcher.LaunchUriAsync(new Uri($"ms-windows-store://pdp/?productid={WindowsPackageManager.AppInstallerProductId}")); } - - private bool ShouldShowSetupFlowBanner() - { - var roamingProperties = ApplicationData.Current.RoamingSettings.Values; - return !roamingProperties.ContainsKey(_hideSetupFlowBannerKey); - } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml index c93f16db8a..16add5ecaf 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml @@ -72,9 +72,9 @@ From 311fd117077f0627cf134b89436a7ed26da09fac Mon Sep 17 00:00:00 2001 From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:05:08 -0700 Subject: [PATCH 44/69] Accessibility enhancements in app instal (#1780) --- .../Styles/AppManagement_ThemeResources.xaml | 1 + .../Views/AppManagementReviewView.xaml | 2 +- .../Views/AppManagementView.xaml | 2 +- .../DevHome.SetupFlow/Views/PackageView.xaml | 2 - .../DevHome.SetupFlow/Views/ReviewView.xaml | 2 + .../Views/ReviewView.xaml.cs | 51 ++++++++++++------- .../DevHome.SetupFlow/Views/SearchView.xaml | 7 ++- .../Views/SearchView.xaml.cs | 48 +++++++++++------ 8 files changed, 77 insertions(+), 38 deletions(-) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml b/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml index bc29ab3a1d..9dd1311934 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml @@ -80,6 +80,7 @@