Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b745f61
Update LightSwitchPage.xaml
niels9001 Oct 28, 2025
b3e09e6
Adding manual lat lon UI
niels9001 Nov 4, 2025
be87a44
added in logic
Jaylyn-Barbee Nov 4, 2025
814eb78
XAML styling
Jaylyn-Barbee Nov 4, 2025
3b1dfeb
Disable input boxes if using location services to fetch location
Jaylyn-Barbee Nov 5, 2025
e8ee996
enable button on manual entry
Jaylyn-Barbee Nov 5, 2025
26b22b5
xaml styling
Jaylyn-Barbee Nov 5, 2025
fd99820
trying to fix infinite settings updates
Jaylyn-Barbee Nov 5, 2025
e749f21
fixed default state issue and circular dependency causing looping
Jaylyn-Barbee Nov 5, 2025
b71140e
Merge branch 'main' into feature/lightswitch-manual-location
Jaylyn-Barbee Nov 5, 2025
ac0b56a
Xaml styling
Jaylyn-Barbee Nov 5, 2025
eec7940
Copilot Suggestions
Jaylyn-Barbee Nov 5, 2025
d172c21
Merge branch 'main' into feature/lightswitch-manual-location
Jaylyn-Barbee Nov 6, 2025
da0fe7a
Resolve merge: keep ours for ShortcutConflictControl.xaml
niels9001 Nov 6, 2025
5cf0b4d
Merge branch 'main' into feature/lightswitch-manual-location
Jaylyn-Barbee Nov 7, 2025
55f28f8
Refactored to use MVC layout in service
Jaylyn-Barbee Nov 7, 2025
48ddffa
manual boxes should be working
Jaylyn-Barbee Nov 7, 2025
89d58aa
Copilot suggestions
Jaylyn-Barbee Nov 7, 2025
95b9236
xaml clean up
Jaylyn-Barbee Nov 7, 2025
bd73f4a
added locks to prevent race conditions
Jaylyn-Barbee Nov 7, 2025
62ea01e
Fixed initial theme desync
Jaylyn-Barbee Nov 7, 2025
29de283
Merge branch 'main' of https://github.com/microsoft/PowerToys
niels9001 Nov 9, 2025
64dafdc
Merge branch 'main' into feature/lightswitch-manual-location
niels9001 Nov 9, 2025
ae2f817
removed unused vars
Jaylyn-Barbee Nov 10, 2025
b01c819
Merge branch 'feature/lightswitch-manual-location' of https://github.…
Jaylyn-Barbee Nov 10, 2025
c0bc441
Made LightSwitchPage Navigable
Jaylyn-Barbee Nov 10, 2025
7bb7d92
UI Tests
Jaylyn-Barbee Nov 10, 2025
0d52787
better responsiveness from lat/lon boxes
Jaylyn-Barbee Nov 11, 2025
a19de59
Fixing lat/lon boxes
Jaylyn-Barbee Nov 11, 2025
eb41e17
Everything working as expected
Jaylyn-Barbee Nov 11, 2025
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
101 changes: 52 additions & 49 deletions src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,14 @@
x:Uid="LightSwitch_LocationSettingsCard"
Visibility="Collapsed">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
<controls:IsEnabledTextBlock
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.SyncButtonInformation, Mode=OneWay}" />
<Button
Padding="8"
AutomationProperties.AutomationId="SetLocationButton_LightSwitch"
Click="SyncLocationButton_Click"
Content="{ui:FontIcon Glyph=&#xECAF;,
FontSize=16}" />
Content="Set location" />
</StackPanel>
</tkcontrols:SettingsCard>

Expand Down Expand Up @@ -202,17 +200,20 @@
<ContentDialog
x:Name="LocationDialog"
x:Uid="LightSwitch_LocationDialog"
IsPrimaryButtonEnabled="True"
IsPrimaryButtonEnabled="False"
IsSecondaryButtonEnabled="True"
Opened="LocationDialog_Opened"
PrimaryButtonClick="LocationDialog_PrimaryButtonClick"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid RowSpacing="48">
<Grid RowSpacing="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="64" />
</Grid.RowDefinitions>
<TextBlock x:Uid="LightSwitch_LocationDialog_Description" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<TextBlock
x:Uid="LightSwitch_LocationDialog_Description"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<!--<AutoSuggestBox
x:Name="CityAutoSuggestBox"
Grid.Row="1"
Expand Down Expand Up @@ -240,93 +241,95 @@
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>-->
<StackPanel
Grid.Row="2"
Margin="0,24,0,0"
HorizontalAlignment="Center"
Orientation="Vertical"
Spacing="32">

<Grid
Grid.Row="1"
Margin="0,12,0,0"
ColumnSpacing="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="124" />
<ColumnDefinition Width="124" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<NumberBox
x:Name="LatitudeBox"
x:Uid="LightSwitch_LatitudeBox"
Maximum="90"
Minimum="-90"
Text="{x:Bind ViewModel.Latitude, Mode=TwoWay}"
ValueChanged="LatLonBox_ValueChanged" />
<NumberBox
x:Name="LongitudeBox"
x:Uid="LightSwitch_LongitudeBox"
Grid.Column="1"
Maximum="180"
Minimum="-180"
Text="{x:Bind ViewModel.Longitude, Mode=TwoWay}"
ValueChanged="LatLonBox_ValueChanged" />
<Button
x:Name="SyncButton"
x:Uid="LightSwitch_FindLocationAutomation"
Grid.Column="2"
Margin="4,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
AutomationProperties.AutomationId="SyncLocationButton_LightSwitch"
Style="{StaticResource AccentButtonStyle}"
Visibility="Collapsed">
Click="GetGeoLocation_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon FontSize="14" Glyph="&#xECAF;" />
<TextBlock x:Uid="LightSwitch_GetCurrentLocation" />
<FontIcon FontSize="16" Glyph="&#xECAF;" />
<TextBlock x:Uid="LightSwitch_FindLocation" />
</StackPanel>
</Button>
</StackPanel>

</Grid>
<ProgressRing
x:Name="SyncLoader"
Grid.Row="1"
Grid.Row="2"
Width="40"
Height="40"
VerticalAlignment="Center"
IsActive="False"
Visibility="Collapsed" />

<Grid
x:Name="LocationResultPanel"
Grid.Row="1"
Grid.Row="2"
VerticalAlignment="Bottom"
ColumnSpacing="16"
RowSpacing="12"
Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<FontIcon FontSize="16" Glyph="&#xECAF;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="LightSwitch_LocationTooltip" />
</ToolTipService.ToolTip>
</FontIcon>
<TextBlock
Grid.Row="1"
AutomationProperties.AutomationId="LocationResultText_LightSwitch"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextAlignment="Center">
<Run Text="{x:Bind ViewModel.Latitude, Mode=OneWay}" /><Run Text="°, " />
<Run Text="{x:Bind ViewModel.Longitude, Mode=OneWay}" /><Run Text="°" />
</TextBlock>
<FontIcon
Grid.Column="1"
FontSize="20"
Glyph="&#xED39;">
<FontIcon FontSize="20" Glyph="&#xED39;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="LightSwitch_SunriseTooltip" />
</ToolTipService.ToolTip>
</FontIcon>
<TextBlock
Grid.Row="1"
Grid.Column="1"
AutomationProperties.AutomationId="SunriseText_LightSwitch"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.LightTimeTimeSpan, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
TextAlignment="Center" />
TextAlignment="Left" />
<FontIcon
Grid.Column="2"
Grid.Row="1"
FontSize="20"
Glyph="&#xED3A;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="LightSwitch_SunsetTooltip" />
</ToolTipService.ToolTip>
</FontIcon>
<TextBlock
Grid.Row="2"
Grid.Column="2"
Grid.Row="1"
Grid.Column="1"
AutomationProperties.AutomationId="SunsetText_LightSwitch"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.DarkTimeTimeSpan, Converter={StaticResource TimeSpanToFriendlyTimeConverter}, Mode=OneWay}"
TextAlignment="Center" />
TextAlignment="Left" />
</Grid>
</Grid>
</ContentDialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
Expand Down Expand Up @@ -37,6 +36,8 @@ public sealed partial class LightSwitchPage : Page
private readonly IFileSystem _fileSystem;
private readonly IFileSystemWatcher _fileSystemWatcher;
private readonly DispatcherQueue _dispatcherQueue;
private bool _suppressViewModelUpdates;
private bool _suppressLatLonChange;

private LightSwitchViewModel ViewModel { get; set; }

Expand Down Expand Up @@ -89,11 +90,14 @@ private void LightSwitchPage_Loaded(object sender, RoutedEventArgs e)
ViewModel.InitializeScheduleMode();
}

private async Task GetGeoLocation()
private async void GetGeoLocation_Click(object sender, RoutedEventArgs e)
{
LatitudeBox.IsEnabled = false;
LongitudeBox.IsEnabled = false;
SyncButton.IsEnabled = false;
SyncLoader.IsActive = true;
SyncLoader.Visibility = Visibility.Visible;
LocationResultPanel.Visibility = Visibility.Collapsed;

try
{
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When geolocation access is denied, the function returns early without re-enabling the UI controls or hiding the loader. This leaves LatitudeBox, LongitudeBox, and SyncButton disabled and SyncLoader visible, preventing users from manually entering coordinates or retrying. Add cleanup code (enable controls, hide loader, set _suppressLatLonChange = false) before the early return on line 109.

Copilot uses AI. Check for mistakes.
Expand All @@ -111,16 +115,6 @@ private async Task GetGeoLocation()

double latitude = Math.Round(pos.Coordinate.Point.Position.Latitude);
double longitude = Math.Round(pos.Coordinate.Point.Position.Longitude);
Comment on lines 121 to 122
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Math.Round on latitude and longitude coordinates reduces precision to whole degrees, which can cause location inaccuracy of up to ~111 km (69 miles). For sunrise/sunset calculations, this level of precision loss is significant. Consider keeping decimal precision (e.g., 2-4 decimal places) or removing rounding entirely to maintain accurate location data.

Copilot uses AI. Check for mistakes.

SunTimes result = SunCalc.CalculateSunriseSunset(
latitude,
longitude,
DateTime.Now.Year,
DateTime.Now.Month,
DateTime.Now.Day);

ViewModel.LightTime = (result.SunriseHour * 60) + result.SunriseMinute;
ViewModel.DarkTime = (result.SunsetHour * 60) + result.SunsetMinute;
ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);

Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed lines that calculated and set ViewModel.LightTime and ViewModel.DarkTime from sun times are not replaced with equivalent logic. This means when the user detects their location, the sunrise/sunset times won't be calculated and stored in the ViewModel. This could break the display of sunrise/sunset information in the LocationResultPanel that shows ViewModel.LightTimeTimeSpan and ViewModel.DarkTimeTimeSpan.

Suggested change
// Calculate and set sunrise/sunset times for the detected location
var sunTimes = SunTimeHelper.GetSunTimes(latitude, longitude, DateTime.Now);
ViewModel.LightTime = sunTimes.Sunrise;
ViewModel.DarkTime = sunTimes.Sunset;

Copilot uses AI. Check for mistakes.
Expand All @@ -130,21 +124,63 @@ private async Task GetGeoLocation()
// CityAutoSuggestBox.Text = string.Empty;
ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}°, {ViewModel.Longitude}°";

_suppressLatLonChange = false;

// ViewModel.CityTimesText = $"Sunrise: {result.SunriseHour}:{result.SunriseMinute:D2}\n" + $"Sunset: {result.SunsetHour}:{result.SunsetMinute:D2}";
SyncButton.IsEnabled = true;
SyncLoader.IsActive = false;
SyncLoader.Visibility = Visibility.Collapsed;
LocationDialog.IsPrimaryButtonEnabled = true;
LatitudeBox.IsEnabled = true;
LongitudeBox.IsEnabled = true;
LocationResultPanel.Visibility = Visibility.Visible;
}
catch (Exception ex)
{
SyncButton.IsEnabled = true;
SyncLoader.IsActive = false;
SyncLoader.Visibility = Visibility.Collapsed;
LocationResultPanel.Visibility = Visibility.Collapsed;
LatitudeBox.IsEnabled = true;
LongitudeBox.IsEnabled = true;
System.Diagnostics.Debug.WriteLine("Location error: " + ex.Message);
}
}

private void LatLonBox_ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
{
if (_suppressLatLonChange)
{
return;
}

double latitude = LatitudeBox.Value;
double longitude = LongitudeBox.Value;

if (double.IsNaN(latitude) || double.IsNaN(longitude))
{
return;
}

double viewModelLatitude = double.TryParse(ViewModel.Latitude, out var lat) ? lat : 0.0;
double viewModelLongitude = double.TryParse(ViewModel.Longitude, out var lon) ? lon : 0.0;

if (latitude == viewModelLatitude && longitude == viewModelLongitude)
{
return;
}

ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);
ViewModel.SyncButtonInformation = $"{ViewModel.Latitude}°, {ViewModel.Longitude}°";

LocationResultPanel.Visibility = Visibility.Visible;
if (LocationDialog != null)
{
LocationDialog.IsPrimaryButtonEnabled = true;
}
}

private void LocationDialog_PrimaryButtonClick(object sender, ContentDialogButtonClickEventArgs args)
{
if (ViewModel.ScheduleMode == "SunriseToSunsetUser")
Expand All @@ -161,6 +197,11 @@ private void LocationDialog_PrimaryButtonClick(object sender, ContentDialogButto

private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (_suppressViewModelUpdates)
{
return;
}

if (e.PropertyName == "IsEnabled")
{
if (ViewModel.IsEnabled != _generalSettingsRepository.SettingsConfig.Enabled.LightSwitch)
Expand Down Expand Up @@ -241,8 +282,12 @@ private void Settings_Changed(object sender, FileSystemEventArgs e)
{
_dispatcherQueue.TryEnqueue(() =>
{
_suppressViewModelUpdates = true;

_moduleSettingsRepository.ReloadSettings();
LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);

_suppressViewModelUpdates = false;
});
}

Expand Down Expand Up @@ -317,11 +362,6 @@ private void ModeSelector_SelectionChanged(object sender, SelectionChangedEventA
}
}

private async void LocationDialog_Opened(ContentDialog sender, ContentDialogOpenedEventArgs args)
{
await GetGeoLocation();
}

private void SunriseModeChartState()
{
if (ViewModel.Latitude != "0.0" && ViewModel.Longitude != "0.0")
Expand Down
Loading
Loading