Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Avalonia #272

Merged
merged 35 commits into from
Mar 28, 2024
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 .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
-p:CSharpier_Bypass=true
--output LightBulb/bin/publish/
--configuration Release
--use-current-runtime

- name: Create installer
shell: pwsh
Expand Down
2 changes: 1 addition & 1 deletion Installer/Compile installer.bat
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
dotnet publish ../LightBulb/ -o Source/ --configuration Release
dotnet publish ../LightBulb/ -o Source/ --configuration Release --use-current-runtime
"c:\Program Files (x86)\Inno Setup 6\ISCC.exe" Installer.iss
13 changes: 7 additions & 6 deletions LightBulb.Core/SolarTimes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace LightBulb.Core;

// Times are presented in the current timezone, which is a flimsy convention
public readonly record struct SolarTimes(TimeOnly Sunrise, TimeOnly Sunset)
{
private static double DegreesToRadians(double degree) => degree * (Math.PI / 180);
Expand All @@ -11,7 +12,7 @@ public readonly record struct SolarTimes(TimeOnly Sunrise, TimeOnly Sunset)

private static TimeOnly CalculateSolarTime(
GeoLocation location,
DateTimeOffset date,
DateTimeOffset instant,
double zenith,
bool isSunrise
)
Expand All @@ -21,7 +22,7 @@ bool isSunrise
// Convert longitude to hour value and calculate an approximate time
var lngHours = location.Longitude / 15;
var timeApproxHours = isSunrise ? 6 : 18;
var timeApproxDays = date.DayOfYear + (timeApproxHours - lngHours) / 24;
var timeApproxDays = instant.DayOfYear + (timeApproxHours - lngHours) / 24;

// Calculate Sun's mean anomaly
var sunMeanAnomaly = 0.9856 * timeApproxDays - 3.289;
Expand Down Expand Up @@ -75,14 +76,14 @@ bool isSunrise

// Adjust UTC time to local time
// (we use the provided offset because it's impossible to calculate timezone from coordinates)
var localHours = (utcHours + date.Offset.TotalHours).Wrap(0, 24);
var localHours = (utcHours + instant.Offset.TotalHours).Wrap(0, 24);

return TimeOnly.FromTimeSpan(TimeSpan.FromHours(localHours));
}

public static SolarTimes Calculate(GeoLocation location, DateTimeOffset date) =>
public static SolarTimes Calculate(GeoLocation location, DateTimeOffset instant) =>
new(
CalculateSolarTime(location, date, 90.83, true),
CalculateSolarTime(location, date, 90.83, false)
CalculateSolarTime(location, instant, 90.83, true),
CalculateSolarTime(location, instant, 90.83, false)
);
}
2 changes: 1 addition & 1 deletion LightBulb.WindowsApi/DeviceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public partial class DeviceContext(nint handle) : NativeResource(handle)
private void SetGammaRamp(GammaRamp ramp)
{
if (!NativeMethods.SetDeviceGammaRamp(Handle, ref ramp))
Debug.WriteLine($"Failed to set gamma ramp on device context #${Handle}).");
Debug.WriteLine($"Failed to set gamma ramp on device context #{Handle}).");
}

public void SetGamma(double redMultiplier, double greenMultiplier, double blueMultiplier)
Expand Down
3 changes: 2 additions & 1 deletion LightBulb.WindowsApi/LightBulb.WindowsApi.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<!-- TODO: remove this and replace with P/Invoke bindings -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
Expand Down
136 changes: 136 additions & 0 deletions LightBulb/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<Application
x:Class="LightBulb.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
xmlns:framework="clr-namespace:LightBulb.Framework"
xmlns:materialAssists="clr-namespace:Material.Styles.Assists;assembly=Material.Styles"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles">
<Application.DataTemplates>
<framework:ViewManager />
</Application.DataTemplates>

<Application.Styles>
<materialStyles:MaterialTheme />
<materialIcons:MaterialIconStyles />
<dialogHostAvalonia:DialogHostStyles />

<!-- Dialog host -->
<Style Selector="dialogHostAvalonia|DialogHost">
<Setter Property="DialogMargin" Value="0" />
</Style>

<!-- Radio button -->
<Style Selector="RadioButton">
<Setter Property="FontSize" Value="14" />
<Setter Property="materialAssists:SelectionControlAssist.Size" Value="18" />

<Style Selector="^:checked, ^:unchecked">
<Style Selector="^ Ellipse#PART_HoverEffect">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
</Style>
</Style>
</Style>

<!-- Slider -->
<Style Selector="Slider">
<Style Selector="^ ProgressBar#PART_ProgressLayer">
<Style Selector="^:horizontal">
<Style Selector="^ Panel#PART_InnerPanel">
<Setter Property="Height" Value="2" />

<Style Selector="^ Border#PART_InactiveState">
<Setter Property="Margin" Value="0" />
<Setter Property="Height" Value="2" />
</Style>

<Style Selector="^ Border#PART_Indicator">
<Setter Property="Margin" Value="0" />
</Style>
</Style>
</Style>
</Style>

<Style Selector="^ Track#PART_Track">
<Style Selector="^:horizontal">
<Setter Property="Margin" Value="4,0" />
</Style>

<Style Selector="^ Border#PART_HoverEffect">
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
</Style>

<Style Selector="^ Border#PART_ThumbGrip">
<Setter Property="Width" Value="12" />
<Setter Property="Height" Value="12" />
</Style>
</Style>
</Style>

<!-- Text box -->
<Style Selector="TextBox">
<Setter Property="Height" Value="20" />
<Setter Property="FontSize" Value="14" />

<Style Selector="^ Panel#PART_TextFieldPanel">
<Setter Property="MinHeight" Value="0" />
</Style>

<Style Selector="^ Panel#PART_TextContainer">
<Setter Property="Margin" Value="0" />
</Style>
</Style>

<!-- Tooltip -->
<Style Selector="ToolTip">
<Setter Property="TextElement.FontSize" Value="14" />
<Setter Property="TextElement.FontWeight" Value="Normal" />
<Setter Property="TextElement.FontStyle" Value="Normal" />
<Setter Property="TextElement.FontStretch" Value="Normal" />
</Style>

<!-- Toggle switch -->
<Style Selector="ToggleSwitch">
<Setter Property="materialAssists:ToggleSwitchAssist.SwitchThumbOffBackground" Value="{DynamicResource MaterialBackgroundBrush}" />
</Style>
</Application.Styles>

<!-- Tray icon -->
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon
Clicked="TrayIcon_OnClicked"
Icon="/favicon.ico"
ToolTipText="LightBulb">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Click="TrayIcon_OnClicked" Header="Open" />
<NativeMenuItem Click="ShowSettingsMenuItem_OnClick" Header="Settings" />
<NativeMenuItemSeparator />

<NativeMenuItem Click="ToggleMenuItem_OnClick" Header="Toggle" />
<NativeMenuItem Header="Disable...">
<NativeMenu>
<NativeMenuItem Click="DisableUntilSunriseMenuItem_OnClick" Header="Until sunrise" />
<NativeMenuItem Click="DisableTemporarily1DayMenuItem_OnClick" Header="For 1 day" />
<NativeMenuItem Click="DisableTemporarily12HoursMenuItem_OnClick" Header="For 12 hours" />
<NativeMenuItem Click="DisableTemporarily6HoursMenuItem_OnClick" Header="For 6 hours" />
<NativeMenuItem Click="DisableTemporarily3HoursMenuItem_OnClick" Header="For 3 hours" />
<NativeMenuItem Click="DisableTemporarily1HourMenuItem_OnClick" Header="For 1 hour" />
<NativeMenuItem Click="DisableTemporarily30MinutesMenuItem_OnClick" Header="For 30 minutes" />
<NativeMenuItem Click="DisableTemporarily5MinutesMenuItem_OnClick" Header="For 5 minutes" />
<NativeMenuItem Click="DisableTemporarily1MinuteMenuItem_OnClick" Header="For 1 minute" />
</NativeMenu>
</NativeMenuItem>
<NativeMenuItemSeparator />

<NativeMenuItem Click="ExitMenuItem_OnClick" Header="Exit" />
</NativeMenu>
</TrayIcon.Menu>
</TrayIcon>
</TrayIcons>
</TrayIcon.Icons>
</Application>
132 changes: 132 additions & 0 deletions LightBulb/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using LightBulb.Framework;
using LightBulb.Services;
using LightBulb.Utils.Extensions;
using LightBulb.ViewModels;
using LightBulb.ViewModels.Components;
using LightBulb.ViewModels.Components.Settings;
using LightBulb.ViewModels.Dialogs;
using LightBulb.Views;
using Material.Styles.Themes;
using Microsoft.Extensions.DependencyInjection;

namespace LightBulb;

public class App : Application, IDisposable
{
private readonly ServiceProvider _services;
private readonly MainViewModel _mainViewModel;

public App()
{
var services = new ServiceCollection();

// Services
services.AddSingleton<ExternalApplicationService>();
services.AddSingleton<GammaService>();
services.AddSingleton<HotKeyService>();
services.AddSingleton<SettingsService>();
services.AddSingleton<UpdateService>();

// View model framework
services.AddSingleton<DialogManager>();
services.AddSingleton<ViewModelManager>();

// View models
services.AddTransient<MainViewModel>();
services.AddTransient<DashboardViewModel>();
services.AddTransient<MessageBoxViewModel>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<SettingsTabViewModelBase, AdvancedSettingsTabViewModel>();
services.AddTransient<SettingsTabViewModelBase, ApplicationWhitelistSettingsTabViewModel>();
services.AddTransient<SettingsTabViewModelBase, GeneralSettingsTabViewModel>();
services.AddTransient<SettingsTabViewModelBase, HotKeySettingsTabViewModel>();
services.AddTransient<SettingsTabViewModelBase, LocationSettingsTabViewModel>();

// View framework
services.AddSingleton<ViewManager>();

_services = services.BuildServiceProvider(true);
_mainViewModel = _services.GetRequiredService<ViewModelManager>().CreateMainViewModel();
}

public override void Initialize() => AvaloniaXamlLoader.Load(this);

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
desktopLifetime.MainWindow = new MainView { DataContext = _mainViewModel };

base.OnFrameworkInitializationCompleted();

// Set custom theme colors
this.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme = Theme.Create(
Theme.Light,
Color.Parse("#343838"),
Color.Parse("#F9A825")
);

// Finalize pending updates (and restart) before launching the app
_services.GetRequiredService<UpdateService>().FinalizePendingUpdates();

// Load settings
_services.GetRequiredService<SettingsService>().Load();
}

private void ShowMainWindow()
{
if (ApplicationLifetime?.TryGetMainWindow() is { } window)
{
window.Show();
window.Activate();
window.Focus();
}
}

private void TrayIcon_OnClicked(object? sender, EventArgs args) => ShowMainWindow();

private void ShowSettingsMenuItem_OnClick(object? sender, EventArgs args)
{
ShowMainWindow();
_mainViewModel.ShowSettingsCommand.Execute(null);
}

private void ToggleMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.IsEnabled = !_mainViewModel.Dashboard.IsEnabled;

private void DisableUntilSunriseMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableUntilSunriseCommand.Execute(null);

private void DisableTemporarily1DayMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromDays(1));

private void DisableTemporarily12HoursMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromHours(12));

private void DisableTemporarily6HoursMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromHours(6));

private void DisableTemporarily3HoursMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromHours(3));

private void DisableTemporarily1HourMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromHours(1));

private void DisableTemporarily30MinutesMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromMinutes(30));

private void DisableTemporarily5MinutesMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromMinutes(5));

private void DisableTemporarily1MinuteMenuItem_OnClick(object? sender, EventArgs args) =>
_mainViewModel.Dashboard.DisableTemporarilyCommand.Execute(TimeSpan.FromMinutes(1));

private void ExitMenuItem_OnClick(object? sender, EventArgs args) =>
ApplicationLifetime?.TryShutdown();

public void Dispose() => _services.Dispose();
}
Loading