Files
PowerToys/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml.cs
Davide Giacometti db953bb325 [Settings] Move title bar shutdown button to navigation view (#40714)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Based on
https://github.com/microsoft/PowerToys/pull/40260#issuecomment-3085099815
feedback, this PR remove the title bar shutdown button in favor of a
menu item in the navigation view footer.

- Menu item is visible only when tray icon is hidden
- A confirm dialog has been added

<img width="848" height="448" alt="image"
src="https://github.com/user-attachments/assets/529bcfa9-94ed-48b1-b2bb-ca6993d12e0f"
/>

<img width="848" height="448" alt="image"
src="https://github.com/user-attachments/assets/febafbb4-3a5b-4b04-8065-28f0d269ab6c"
/>

- Close is used in tray icon menu for closing app

<img alt="image"
src="https://github.com/user-attachments/assets/3ac79a8c-961f-4f95-8967-adef00aba77b"
/>

<img alt="image"
src="https://github.com/user-attachments/assets/c2800a77-c733-41a9-aa4f-fa4c2afd30a3"
/>

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40346 #40577
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

- Open settings with tray icon visible: close menu is hidden
- Open settings with tray icon hidden: close menu is visible
- Tested close menu visibility change when tray icon option is changed
- Tested cancel button of close dialog
- Tested close button of dialog

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-08-21 16:40:37 +08:00

235 lines
8.2 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 System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Flyout;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Data.Json;
namespace Microsoft.PowerToys.Settings.UI.Views
{
/// <summary>
/// General Settings Page.
/// </summary>
public sealed partial class GeneralPage : Page, IRefreshablePage
{
private static DateTime OkToHideBackupAndRestoreMessageTime { get; set; }
/// <summary>
/// Gets or sets view model.
/// </summary>
public GeneralViewModel ViewModel { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GeneralPage"/> class.
/// General Settings page constructor.
/// </summary>
public GeneralPage()
{
InitializeComponent();
// Load string resources
var loader = Helpers.ResourceLoaderInstance.ResourceLoader;
var settingsUtils = new SettingsUtils();
Action stateUpdatingAction = () =>
{
this.DispatcherQueue.TryEnqueue(() =>
{
ViewModel.RefreshUpdatingState();
});
};
Action hideBackupAndRestoreMessageArea = () =>
{
this.DispatcherQueue.TryEnqueue(async () =>
{
const int messageShowTimeIs = 10000;
// in order to keep the message for about 5 seconds after the last call
// and not need any lock/thread-synch, use an OK-To-Hide time, and wait just a little longer than that.
OkToHideBackupAndRestoreMessageTime = DateTime.UtcNow.AddMilliseconds(messageShowTimeIs - 16);
await System.Threading.Tasks.Task.Delay(messageShowTimeIs);
if (DateTime.UtcNow > OkToHideBackupAndRestoreMessageTime)
{
ViewModel.HideBackupAndRestoreMessageArea();
}
});
};
var doRefreshBackupRestoreStatus = new Action<int>(RefreshBackupRestoreStatus);
ViewModel = new GeneralViewModel(
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
loader.GetString("GeneralSettings_RunningAsAdminText"),
loader.GetString("GeneralSettings_RunningAsUserText"),
ShellPage.IsElevated,
ShellPage.IsUserAnAdmin,
ShellPage.SendDefaultIPCMessage,
ShellPage.SendRestartAdminIPCMessage,
ShellPage.SendCheckForUpdatesIPCMessage,
string.Empty,
stateUpdatingAction,
hideBackupAndRestoreMessageArea,
doRefreshBackupRestoreStatus,
PickSingleFolderDialog,
loader);
DataContext = ViewModel;
ViewModel.InitializeReportBugLink();
// Register IPC handler for bug report status
ShellPage.ShellHandler.IPCResponseHandleList.Add(HandleBugReportStatusResponse); // Register cleanup on unload
this.Unloaded += GeneralPage_Unloaded;
CheckBugReportStatus();
doRefreshBackupRestoreStatus(100);
}
private void OpenColorsSettings_Click(object sender, RoutedEventArgs e)
{
try
{
Helpers.StartProcessHelper.Start(Helpers.StartProcessHelper.ColorsSettings);
}
catch (Exception ex)
{
Logger.LogError("Error while trying to open the system color settings", ex);
}
}
private void OpenDiagnosticsAndFeedbackSettings_Click(object sender, RoutedEventArgs e)
{
try
{
Helpers.StartProcessHelper.Start(Helpers.StartProcessHelper.DiagnosticsAndFeedback);
}
catch (Exception ex)
{
Logger.LogError("Error while trying to open the system Diagnostics & Feedback settings", ex);
}
}
private void RefreshBackupRestoreStatus(int delayMs = 0)
{
Task.Run(() =>
{
if (delayMs > 0)
{
Thread.Sleep(delayMs);
}
var settingsBackupAndRestoreUtils = SettingsBackupAndRestoreUtils.Instance;
var results = settingsBackupAndRestoreUtils.DryRunBackup();
this.DispatcherQueue.TryEnqueue(() =>
{
ViewModel.NotifyAllBackupAndRestoreProperties();
});
});
}
private void UpdateBackupAndRestoreStatusText(Microsoft.UI.Xaml.Documents.Hyperlink sender, Microsoft.UI.Xaml.Documents.HyperlinkClickEventArgs args)
{
RefreshBackupRestoreStatus();
}
private async Task<string> PickSingleFolderDialog()
{
// This function was changed to use the shell32 API to open folder dialog
// as the old one (PickSingleFolderAsync) can't work when the process is elevated
// TODO: go back PickSingleFolderAsync when it's fixed
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.GetSettingsWindow());
string r = await Task.FromResult<string>(ShellGetFolder.GetFolderDialog(hwnd));
return r;
}
private void Click_LanguageRestart(object sender, RoutedEventArgs e)
{
ViewModel.Restart();
}
private void Click_ViewDiagnosticDataViewerRestart(object sender, RoutedEventArgs e)
{
ViewModel.Restart();
}
public void RefreshEnabledState()
{
ViewModel.RefreshSettingsOnExternalChange();
}
private async void ViewDiagnosticData_Click(object sender, RoutedEventArgs e)
{
await Task.Run(ViewModel.ViewDiagnosticData);
}
private void BugReportToolClicked(object sender, RoutedEventArgs e)
{
// Start bug report
var launchPage = new LaunchPage();
launchPage.ReportBugBtn_Click(sender, e);
ViewModel.IsBugReportRunning = true;
// No need to start timer - the observer pattern will notify us when it finishes
}
private void CheckBugReportStatus()
{
// Send one-time request to check current bug report status
string ipcMessage = "{ \"bug_report_status\": { } }";
ShellPage.SendDefaultIPCMessage(ipcMessage);
}
private void HandleBugReportStatusResponse(JsonObject response)
{
if (response.ContainsKey("bug_report_running"))
{
var isRunning = response.GetNamedBoolean("bug_report_running");
// Update UI on the UI thread
this.DispatcherQueue.TryEnqueue(() =>
{
ViewModel.IsBugReportRunning = isRunning;
});
}
}
private void GeneralPage_Unloaded(object sender, RoutedEventArgs e)
{
CleanupBugReportHandlers();
}
private void CleanupBugReportHandlers()
{
// Remove IPC handler
if (ShellPage.ShellHandler?.IPCResponseHandleList != null)
{
ShellPage.ShellHandler.IPCResponseHandleList.Remove(HandleBugReportStatusResponse);
}
}
private void ShowSystemTrayIcon_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch)
{
var shellViewModel = ShellPage.ShellHandler?.ViewModel;
if (shellViewModel != null)
{
shellViewModel.ShowCloseMenu = !toggleSwitch.IsOn;
}
}
}
}
}