Files
PowerToys/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs
Copilot e7de5c7b8d Make update notification InfoBar clickable in Flyout (#42064)
## Summary

Resolves #38854 by making the "Update available" InfoBar in the tray
icon flyout clickable, providing direct navigation to the General
settings page where users can manage updates.

## Problem

When PowerToys has an available update, users experience unnecessary
navigation friction:

1. Click PowerToys tray icon → flyout opens with "Update available"
InfoBar
2. InfoBar is not clickable, only the settings wheel is interactive
3. Click settings wheel → opens Dashboard page (not where updates are
managed)
4. Navigate to General page → finally access update controls

Users requested making the "Update available" text itself a hyperlink
for direct access to update functionality.

## Solution

Made the InfoBar interactive by adding a `Tapped` event handler that:
- Hides the flyout (consistent with other flyout actions)
- Opens Settings window directly to the General page using
`App.OpenSettingsWindow(typeof(GeneralPage))`

## Changes

**Files Modified:**
- `src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml`:
Added `Tapped="UpdateInfoBar_Tapped"` event
- `src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs`:
Added event handler and navigation logic

## User Experience

**Before:** Tray Icon → Flyout → Settings Wheel → Dashboard → Navigate
to General → Update Controls
**After:** Tray Icon → Flyout → **Click "Update available"** → General
Page → Update Controls

The InfoBar now behaves as a clickable hyperlink as requested, while
preserving all existing functionality. Users can still use the settings
wheel if preferred, but now have a more direct path to update
management.

## Testing

The implementation follows existing patterns in the codebase and uses
established APIs. The InfoBar only appears when updates are in
`ReadyToInstall` or `ReadyToDownload` states, ensuring the navigation is
contextually appropriate.

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `i1qvsblobprodcus353.vsblob.vsassets.io`
>   - Triggering command: `dotnet build` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/microsoft/PowerToys/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Taskbar icon should provide link directly to update
page</issue_title>
> <issue_description>### Description of the new feature / enhancement
> 
> Right now when there is an update the following message appears when
you click on the PT icon in the taskbar:
> 
>
![Image](https://github.com/user-attachments/assets/18641e1c-d80b-4f7e-b206-8c9a36f2de36)
> 
> When you click on the settings wheel you go to the dashboard page:
> 
>
![Image](https://github.com/user-attachments/assets/e951b988-79ab-48b3-8714-a26999223153)
> 
> Then you need to click on the Learn more button before you finally get
to the page where you can install the update:
> 
>
![Image](https://github.com/user-attachments/assets/914c9c00-a642-40da-bfb5-439c1fd930c0)
> 
> Can't you make the **_Update available_** a hyperlink or add another
button that would link directly to the General page (or wherever the
update button is) so we can quickly install the update instead of going
a roundabout way every time?
> 
>
![Image](https://github.com/user-attachments/assets/5c065eed-84ad-47be-9432-607155065a17)
> 
> ### Scenario when this would be used?
> 
> When there is an update. It's a usability annoyance more than
anything.
> 
> ### Supporting information
> 
> _No response_</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>
Fixes microsoft/PowerToys#38854

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for
you](https://github.com/microsoft/PowerToys/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-11-06 16:53:32 +08:00

198 lines
7.6 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using global::Windows.System;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Controls;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using PowerToys.Interop;
using WinUIEx;
namespace Microsoft.PowerToys.Settings.UI.Flyout
{
public sealed partial class LaunchPage : Page
{
private LauncherViewModel ViewModel { get; set; }
public LaunchPage()
{
this.InitializeComponent();
var settingsUtils = new SettingsUtils();
ViewModel = new LauncherViewModel(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), Views.ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
}
private void ModuleButton_Click(object sender, RoutedEventArgs e)
{
FlyoutMenuButton selectedModuleBtn = sender as FlyoutMenuButton;
bool moduleRun = true;
// Closing manually the flyout to workaround focus gain problems
App.GetFlyoutWindow()?.Hide();
switch ((ModuleType)selectedModuleBtn.Tag)
{
case ModuleType.ColorPicker: // Launch ColorPicker
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowColorPickerSharedEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.EnvironmentVariables: // Launch Environment Variables
{
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !App.IsElevated && launchAdmin
? Constants.ShowEnvironmentVariablesAdminSharedEvent()
: Constants.ShowEnvironmentVariablesSharedEvent();
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
{
eventHandle.Set();
}
}
break;
case ModuleType.FancyZones: // Launch FancyZones Editor
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.Hosts: // Launch Hosts
{
bool launchAdmin = SettingsRepository<HostsSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
string eventName = !App.IsElevated && launchAdmin
? Constants.ShowHostsAdminSharedEvent()
: Constants.ShowHostsSharedEvent();
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
{
eventHandle.Set();
}
}
break;
case ModuleType.RegistryPreview: // Launch Registry Preview
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.MeasureTool: // Launch Screen Ruler
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.PowerLauncher: // Launch Run
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerLauncherSharedEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.PowerOCR: // Launch Text Extractor
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.Workspaces: // Launch Workspaces Editor
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.WorkspacesLaunchEditorEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.ShortcutGuide: // Launch Shortcut Guide
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShortcutGuideTriggerEvent()))
{
eventHandle.Set();
}
break;
case ModuleType.CmdPal: // Show CmdPal
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowCmdPalEvent()))
{
eventHandle.Set();
}
break;
default:
moduleRun = false;
break;
}
if (moduleRun)
{
PowerToysTelemetry.Log.WriteEvent(new TrayFlyoutModuleRunEvent() { ModuleName = ((ModuleType)selectedModuleBtn.Tag).ToString() });
}
}
private void SettingsBtn_Click(object sender, RoutedEventArgs e)
{
App.OpenSettingsWindow(null, true);
}
private async void DocsBtn_Click(object sender, RoutedEventArgs e)
{
await Launcher.LaunchUriAsync(new Uri("https://aka.ms/PowerToysOverview"));
}
private void AllAppButton_Click(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(AppsListPage), null, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight });
}
private void QuitButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.KillRunner();
this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
{
Application.Current.Exit();
});
}
internal void ReportBugBtn_Click(object sender, RoutedEventArgs e)
{
ViewModel.StartBugReport();
// Closing manually the flyout since no window will steal the focus
App.GetFlyoutWindow()?.Hide();
}
private void UpdateInfoBar_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
{
// Hide the flyout before opening settings window
App.GetFlyoutWindow()?.Hide();
// Open Settings window directly to General page where update controls are located
App.OpenSettingsWindow(typeof(GeneralPage));
}
}
}