[Light Switch] Add 10s timeout and pre-check for location detection (#45887)

<!-- 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)?
-->
- Add 10-second timeout to GetGeopositionAsync to prevent infinite
spinner
- Pre-check location services availability when dialog opens; disable
Detect Location button with message if unavailable
- Show user-friendly error messages for timeout and unavailable
scenarios
- Add LocationErrorText UI element and localized string resources

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

- [x] Closes: #45860
- [x] Closes: #42852

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Jaylyn Barbee
2026-03-02 15:26:31 -05:00
committed by GitHub
parent fd399045f7
commit 22b4dda3aa
3 changed files with 52 additions and 9 deletions

View File

@@ -376,6 +376,12 @@
VerticalAlignment="Center" VerticalAlignment="Center"
IsActive="False" IsActive="False"
Visibility="Collapsed" /> Visibility="Collapsed" />
<TextBlock
x:Name="LocationErrorText"
Grid.Row="2"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
TextWrapping="Wrap"
Visibility="Collapsed" />
<Grid <Grid
x:Name="LocationResultPanel" x:Name="LocationResultPanel"
Grid.Row="2" Grid.Row="2"

View File

@@ -7,6 +7,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.IO.Abstractions; using System.IO.Abstractions;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Common.UI; using Common.UI;
using ManagedCommon; using ManagedCommon;
@@ -25,6 +26,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{ {
public sealed partial class LightSwitchPage : NavigablePage, IRefreshablePage public sealed partial class LightSwitchPage : NavigablePage, IRefreshablePage
{ {
private static readonly TimeSpan GeoLocationTimeout = TimeSpan.FromSeconds(10);
private readonly string appName = "LightSwitch"; private readonly string appName = "LightSwitch";
private readonly SettingsUtils settingsUtils; private readonly SettingsUtils settingsUtils;
private readonly Func<string, int> sendConfigMsg = ShellPage.SendDefaultIPCMessage; private readonly Func<string, int> sendConfigMsg = ShellPage.SendDefaultIPCMessage;
@@ -101,6 +104,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
this.SyncLoader.IsActive = true; this.SyncLoader.IsActive = true;
this.SyncLoader.Visibility = Visibility.Visible; this.SyncLoader.Visibility = Visibility.Visible;
this.LocationResultPanel.Visibility = Visibility.Collapsed; this.LocationResultPanel.Visibility = Visibility.Collapsed;
this.LocationErrorText.Visibility = Visibility.Collapsed;
try try
{ {
@@ -108,13 +112,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views
var accessStatus = await Geolocator.RequestAccessAsync(); var accessStatus = await Geolocator.RequestAccessAsync();
if (accessStatus != GeolocationAccessStatus.Allowed) if (accessStatus != GeolocationAccessStatus.Allowed)
{ {
// User denied location or it's not available ShowLocationError(ResourceLoaderInstance.ResourceLoader.GetString("LightSwitch_LocationError_Unavailable"));
return; return;
} }
var geolocator = new Geolocator { DesiredAccuracy = PositionAccuracy.Default }; var geolocator = new Geolocator { DesiredAccuracy = PositionAccuracy.Default };
Geoposition pos = await geolocator.GetGeopositionAsync(); using var cts = new CancellationTokenSource(GeoLocationTimeout);
var positionTask = geolocator.GetGeopositionAsync().AsTask(cts.Token);
Geoposition pos = await positionTask;
double latitude = Math.Round(pos.Coordinate.Point.Position.Latitude); double latitude = Math.Round(pos.Coordinate.Point.Position.Latitude);
double longitude = Math.Round(pos.Coordinate.Point.Position.Longitude); double longitude = Math.Round(pos.Coordinate.Point.Position.Longitude);
@@ -130,7 +136,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
// Since we use this mode, we can remove the selected city data. // Since we use this mode, we can remove the selected city data.
this.ViewModel.SelectedCity = null; this.ViewModel.SelectedCity = null;
// ViewModel.CityTimesText = $"Sunrise: {result.SunriseHour}:{result.SunriseMinute:D2}\n" + $"Sunset: {result.SunsetHour}:{result.SunsetMinute:D2}";
this.SyncButton.IsEnabled = true; this.SyncButton.IsEnabled = true;
this.SyncLoader.IsActive = false; this.SyncLoader.IsActive = false;
this.SyncLoader.Visibility = Visibility.Collapsed; this.SyncLoader.Visibility = Visibility.Collapsed;
@@ -139,7 +144,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
this.LongitudeBox.IsEnabled = true; this.LongitudeBox.IsEnabled = true;
this.LocationResultPanel.Visibility = Visibility.Visible; this.LocationResultPanel.Visibility = Visibility.Visible;
} }
catch (OperationCanceledException)
{
ShowLocationError(ResourceLoaderInstance.ResourceLoader.GetString("LightSwitch_LocationError_Timeout"));
}
catch (Exception ex) catch (Exception ex)
{
ShowLocationError(ResourceLoaderInstance.ResourceLoader.GetString("LightSwitch_LocationError_Timeout"));
Logger.LogInfo($"Location error: " + ex.Message);
}
}
private void ShowLocationError(string message)
{ {
this.SyncButton.IsEnabled = true; this.SyncButton.IsEnabled = true;
this.SyncLoader.IsActive = false; this.SyncLoader.IsActive = false;
@@ -147,8 +163,8 @@ namespace Microsoft.PowerToys.Settings.UI.Views
this.LocationResultPanel.Visibility = Visibility.Collapsed; this.LocationResultPanel.Visibility = Visibility.Collapsed;
this.LatitudeBox.IsEnabled = true; this.LatitudeBox.IsEnabled = true;
this.LongitudeBox.IsEnabled = true; this.LongitudeBox.IsEnabled = true;
Logger.LogInfo($"Location error: " + ex.Message); this.LocationErrorText.Text = message;
} this.LocationErrorText.Visibility = Visibility.Visible;
} }
private void LatLonBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) private void LatLonBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
@@ -301,6 +317,21 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{ {
this.LocationDialog.IsPrimaryButtonEnabled = false; this.LocationDialog.IsPrimaryButtonEnabled = false;
this.LocationResultPanel.Visibility = Visibility.Collapsed; this.LocationResultPanel.Visibility = Visibility.Collapsed;
this.LocationErrorText.Visibility = Visibility.Collapsed;
// Pre-check location services availability
var accessStatus = await Geolocator.RequestAccessAsync();
if (accessStatus != GeolocationAccessStatus.Allowed)
{
this.SyncButton.IsEnabled = false;
this.LocationErrorText.Text = ResourceLoaderInstance.ResourceLoader.GetString("LightSwitch_LocationError_Unavailable");
this.LocationErrorText.Visibility = Visibility.Visible;
}
else
{
this.SyncButton.IsEnabled = true;
}
await this.LocationDialog.ShowAsync(); await this.LocationDialog.ShowAsync();
} }

View File

@@ -5150,6 +5150,12 @@ The break timer font matches the text font.</value>
<data name="LightSwitch_SunsetTooltip.Text" xml:space="preserve"> <data name="LightSwitch_SunsetTooltip.Text" xml:space="preserve">
<value>Sunset</value> <value>Sunset</value>
</data> </data>
<data name="LightSwitch_LocationError_Timeout" xml:space="preserve">
<value>Unable to connect to location services. Please try again or enter your coordinates manually.</value>
</data>
<data name="LightSwitch_LocationError_Unavailable" xml:space="preserve">
<value>Location services unavailable. Please enter your coordinates manually.</value>
</data>
<data name="Close_NavViewItem.Content" xml:space="preserve"> <data name="Close_NavViewItem.Content" xml:space="preserve">
<value>Close PowerToys</value> <value>Close PowerToys</value>
<comment>Don't loc "PowerToys"</comment> <comment>Don't loc "PowerToys"</comment>