Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public App()
// Other Activation Handlers
services.AddTransient<IActivationHandler, ProtocolActivationHandler>();
services.AddTransient<IActivationHandler, DSCFileActivationHandler>();
services.AddTransient<IActivationHandler, AppInstallActivationHandler>();

// Services
services.AddSingleton<ILocalSettingsService, LocalSettingsService>();
Expand Down
113 changes: 113 additions & 0 deletions src/Services/AppInstallActivationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics;
using System.Web;
using DevHome.Activation;
using DevHome.Common.Extensions;
using DevHome.Common.Services;
using DevHome.Settings.ViewModels;
using DevHome.SetupFlow.Services;
using DevHome.SetupFlow.ViewModels;
using Microsoft.UI.Xaml;
using Serilog;
using Windows.ApplicationModel.Activation;
using Windows.Storage;

namespace DevHome.Services;

/// <summary>
/// Class that handles the activation of the application when an add-apps-to-cart URI protcol is used.
/// </summary>
public class AppInstallActivationHandler : ActivationHandler<ProtocolActivatedEventArgs>
{
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(AppInstallActivationHandler));
private const string AppSearchUri = "add-apps-to-cart";
private readonly INavigationService _navigationService;
private readonly SetupFlowViewModel _setupFlowViewModel;
private readonly IWindowsPackageManager _windowsPackageManager;
private readonly PackageProvider _packageProvider;
private readonly SetupFlowOrchestrator _setupFlowOrchestrator;
private static readonly char[] Separator = [','];

public AppInstallActivationHandler(
INavigationService navigationService,
SetupFlowViewModel setupFlowViewModel,
PackageProvider packageProvider,
IWindowsPackageManager wpm,
SetupFlowOrchestrator setupFlowOrchestrator)
{
_navigationService = navigationService;
_setupFlowViewModel = setupFlowViewModel;
_packageProvider = packageProvider;
_windowsPackageManager = wpm;
_setupFlowOrchestrator = setupFlowOrchestrator;
}

protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
{
return args.Uri != null && args.Uri.AbsolutePath.Equals(AppSearchUri, StringComparison.OrdinalIgnoreCase);
}

protected async override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
{
await AppActivationFlowAsync(args.Uri.Query);
}

private async Task AppActivationFlowAsync(string query)
{
try
{
// Don't interrupt the user if the machine configuration is in progress
if (_setupFlowOrchestrator.IsMachineConfigurationInProgress)
{
_log.Warning("Cannot activate the add-apps-to-cart flow because the machine configuration is in progress");
return;
}
else
{
_log.Information("Starting add-apps-to-cart activation");
_navigationService.NavigateTo(typeof(SetupFlowViewModel).FullName!);
_setupFlowViewModel.StartAppManagementFlow(query);
await SearchAndSelectAsync(query);
}
}
catch (Exception ex)
{
_log.Error(ex, "Error executing the add-apps-to-cart activation flow");
}
}

private async Task SearchAndSelectAsync(string query)
{
var parameters = HttpUtility.ParseQueryString(query);
var searchParameter = parameters["search"];

if (string.IsNullOrEmpty(searchParameter))
{
_log.Warning("Search parameter is missing or empty in the query.");
return;
}

// Currently using the first search term only
var firstSearchTerm = searchParameter.Split(Separator, StringSplitOptions.RemoveEmptyEntries)
.Select(term => term.Trim(' ', '"'))
.FirstOrDefault();

if (string.IsNullOrEmpty(firstSearchTerm))
{
_log.Warning("No valid search term was extracted from the query.");
return;
}

var searchResults = await _windowsPackageManager.SearchAsync(firstSearchTerm, 1);
if (searchResults.Count == 0)
{
_log.Warning("No results found for the search term: {SearchTerm}", firstSearchTerm);
return;
}

var firstResult = _packageProvider.CreateOrGet(searchResults[0]);
firstResult.IsSelected = true;
}
}
12 changes: 6 additions & 6 deletions src/Services/ProtocolActivationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ public ProtocolActivationHandler(INavigationService navigationService)
this._navigationService = navigationService;
}

protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
{
if (args.Uri.AbsolutePath == SettingsAccountsUri)
{
_navigationService.DefaultPage = typeof(AccountsViewModel).FullName!;
_navigationService.NavigateTo(_navigationService.DefaultPage);
}
return args.Uri != null && args.Uri.AbsolutePath.Equals(SettingsAccountsUri, StringComparison.OrdinalIgnoreCase);
}

protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
{
_navigationService.NavigateTo(typeof(AccountsViewModel).FullName!);
return Task.CompletedTask;
}
}
2 changes: 2 additions & 0 deletions src/ViewModels/ShellViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public async Task OnLoaded()
case ExtendedActivationKind.File:
// Allow the file activation handler to navigate to the appropriate page.
break;
case ExtendedActivationKind.Protocol:
break;
case ExtendedActivationKind.Launch:
default:
var isNotFirstRun = await _localSettingsService.ReadSettingAsync<bool>(WellKnownSettingsKeys.IsNotFirstRun);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Linq;
using System.Web;
using DevHome.SetupFlow.Models;
using DevHome.SetupFlow.Services;
using DevHome.SetupFlow.ViewModels;
Expand Down Expand Up @@ -35,4 +36,14 @@ public AppManagementTaskGroup(
public SetupPageViewModelBase GetSetupPageViewModel() => _appManagementViewModel;

public ReviewTabViewModelBase GetReviewTabViewModel() => _appManagementReviewViewModel;

public void HandleSearchQuery(string query)
{
var searchParameter = HttpUtility.ParseQueryString(query)["search"];
if (!string.IsNullOrEmpty(searchParameter))
{
var trimmedSearchParameter = searchParameter.Trim('\"');
_appManagementViewModel.PerformSearch(trimmedSearchParameter);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public partial class AppManagementViewModel : SetupPageViewModelBase
private readonly PackageCatalogListViewModel _packageCatalogListViewModel;
private readonly PackageProvider _packageProvider;

[ObservableProperty]
private string _searchText;

/// <summary>
/// Current view to display in the main content control
/// </summary>
Expand Down Expand Up @@ -127,4 +130,9 @@ private void OnPackageSelectionChanged(object sender, EventArgs args)
// Show warning if any selected package is installed
ShowInstalledPackageWarning = SelectedPackages.Any(p => !p.CanInstall);
}

internal void PerformSearch(string searchParameter)
{
SearchText = searchParameter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ public async Task StartConfigurationFileAsync(StorageFile file)
}
}

internal void StartAppManagementFlow(string query)
{
_log.Information($"Launching app management flow for query:{query}");
var appManagementSetupFlow = _host.GetService<AppManagementTaskGroup>();
StartSetupFlowForTaskGroups(null, "App Search URI", appManagementSetupFlow);
appManagementSetupFlow.HandleSearchQuery(query);
}

protected async override Task OnFirstNavigateToAsync()
{
if (await ValidateAppInstallerAsync())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,10 @@ public void OnNavigatedTo(NavigationEventArgs args)
StartCreationFlowAsync();
}
}

public void StartAppManagementFlow(string query)
{
Orchestrator.FlowPages = [_mainPageViewModel];
_mainPageViewModel.StartAppManagementFlow(query);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<!-- Search bar -->
<AutoSuggestBox
x:Name="SearchBox"
Text="{x:Bind ViewModel.SearchText, Mode=TwoWay}"
QueryIcon="Find"
HorizontalAlignment="Left"
Width="400"
Expand Down