Skip to content
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
6 changes: 6 additions & 0 deletions PointerStar/Client/Cookies/WellKnownCookieValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public static class WellKnownCookieValues
private const string NameKey = "Name";
private const string RoleKey = "RoleId";
private const string RoomKey = "RoomId";
private const string ThemePreferenceKey = "ThemePreference";

public static ValueTask<string> GetNameAsync(this ICookie cookie)
=> cookie.GetValueAsync(NameKey);
Expand All @@ -26,5 +27,10 @@ public static ValueTask SetRoleAsync(this ICookie cookie, Guid? value)
=> cookie.SetValueAsync(RoleKey, value?.ToString("D") ?? "");
public static ValueTask SetRoomAsync(this ICookie cookie, string value)
=> cookie.SetValueAsync(RoomKey, value);

public static ValueTask<string> GetThemePreferenceAsync(this ICookie cookie)
=> cookie.GetValueAsync(ThemePreferenceKey);
public static ValueTask SetThemePreferenceAsync(this ICookie cookie, string value)
=> cookie.SetValueAsync(ThemePreferenceKey, value);
}

2 changes: 2 additions & 0 deletions PointerStar/Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using MudBlazor.Services;
using PointerStar.Client;
using PointerStar.Client.Cookies;
using PointerStar.Client.Services;
using PointerStar.Client.ViewModels;
using PointerStar.Shared;
using Toolbelt.Blazor.Extensions.DependencyInjection;
Expand Down Expand Up @@ -34,5 +35,6 @@
builder.Services.AddScoped<UserDialogViewModel>();
builder.Services.AddScoped<ICookie, Cookie>();
builder.Services.AddScoped<IClipboardService, ClipboardService>();
builder.Services.AddScoped<IThemeService, ThemeService>();

await builder.Build().RunAsync();
36 changes: 4 additions & 32 deletions PointerStar/Client/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,12 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:13580",
"sslPort": 44352
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5225",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"PointerStar.Client": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7234;http://localhost:5225",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"applicationUrl": "https://localhost:58850;http://localhost:58851"
}
}
}
}
87 changes: 87 additions & 0 deletions PointerStar/Client/Services/ThemeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
namespace PointerStar.Client.Services;

using PointerStar.Client.Cookies;

public enum ThemePreference
{
System,
Light,
Dark
}

public interface IThemeService
{
ThemePreference CurrentPreference { get; }
bool IsDarkMode { get; }
event EventHandler? ThemeChanged;
Task InitializeAsync(Func<Task<bool>> getSystemPreference);
Task SetPreferenceAsync(ThemePreference preference);
Task CycleThemeAsync();
}

public class ThemeService(ICookie cookie) : IThemeService
{
private readonly ICookie _cookie = cookie ?? throw new ArgumentNullException(nameof(cookie));
private bool _systemIsDark;

public ThemePreference CurrentPreference { get; private set; }

public bool IsDarkMode => CurrentPreference switch
{
ThemePreference.Dark => true,
ThemePreference.Light => false,
_ => _systemIsDark
};

public event EventHandler? ThemeChanged;

public async Task InitializeAsync(Func<Task<bool>> getSystemPreference)
{
// Get system preference
_systemIsDark = await getSystemPreference();

// Load saved preference from cookie
string preferenceValue = await _cookie.GetThemePreferenceAsync();
if (!string.IsNullOrEmpty(preferenceValue) && Enum.TryParse<ThemePreference>(preferenceValue, out var preference))
{
CurrentPreference = preference;
}
else
{
CurrentPreference = ThemePreference.System;
}
}

public async Task SetPreferenceAsync(ThemePreference preference)
{
if (CurrentPreference != preference)
{
CurrentPreference = preference;
await _cookie.SetThemePreferenceAsync(preference.ToString());
ThemeChanged?.Invoke(this, EventArgs.Empty);
}
}

public async Task CycleThemeAsync()
{
var nextPreference = IsDarkMode switch
{
true => ThemePreference.Light,
false => ThemePreference.Dark,
};
await SetPreferenceAsync(nextPreference);
}

public async Task UpdateSystemPreferenceAsync(bool isDark)
{
if (_systemIsDark != isDark)
{
_systemIsDark = isDark;
if (CurrentPreference == ThemePreference.System)
{
ThemeChanged?.Invoke(this, EventArgs.Empty);
}
}
await Task.CompletedTask;
}
}
53 changes: 49 additions & 4 deletions PointerStar/Client/Shared/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@using System.Reflection;
@using PointerStar.Client.Services
@inherits LayoutComponentBase
@inject IThemeService ThemeService
@implements IDisposable

<Toolbelt.Blazor.PWA.Updater.PWAUpdater />
<MudThemeProvider @ref="@_mudThemeProvider" @bind-IsDarkMode="@_isDarkMode" />
Expand All @@ -11,6 +14,9 @@
<MudAppBar Elevation="1">
<MudText Typo="Typo.h5" Class="ml-3">Pointer*</MudText>
<MudSpacer />
<MudTooltip Text="@GetThemeTooltip()">
<MudIconButton Icon="@GetThemeIcon()" Color="Color.Inherit" OnClick="@CycleThemeAsync" />
</MudTooltip>
<MudText Typo="Typo.subtitle1" Align="Align.End">@Assembly.GetExecutingAssembly().GetName().Version?.ToString(3)</MudText>
</MudAppBar>
<MudMainContent>
Expand All @@ -26,16 +32,55 @@
{
if (firstRender && _mudThemeProvider is { } themeProvider)
{
_isDarkMode = await themeProvider.GetSystemDarkModeAsync();
await ThemeService.InitializeAsync(() => themeProvider.GetSystemDarkModeAsync());
_isDarkMode = ThemeService.IsDarkMode;
await themeProvider.WatchSystemDarkModeAsync(OnSystemPreferenceChanged);
ThemeService.ThemeChanged += OnThemeChanged;
StateHasChanged();
}
}

private Task OnSystemPreferenceChanged(bool newValue)
private async Task OnSystemPreferenceChanged(bool newValue)
{
_isDarkMode = newValue;
if (ThemeService is ThemeService themeService)
{
await themeService.UpdateSystemPreferenceAsync(newValue);
}
}

private void OnThemeChanged(object? sender, EventArgs e)
{
_isDarkMode = ThemeService.IsDarkMode;
StateHasChanged();
return Task.CompletedTask;
}

private async Task CycleThemeAsync()
{
await ThemeService.CycleThemeAsync();
}

private string GetThemeIcon()
{
return ThemeService.CurrentPreference switch
{
ThemePreference.Light => Icons.Material.Filled.LightMode,
ThemePreference.Dark => Icons.Material.Filled.DarkMode,
_ => Icons.Material.Filled.Brightness4
};
}

private string GetThemeTooltip()
{
return ThemeService.CurrentPreference switch
{
ThemePreference.Light => "Theme: Light (click to switch to Dark)",
ThemePreference.Dark => "Theme: Dark (click to switch to Light)",
_ => "Toggle theme"
};
}

public void Dispose()
{
ThemeService.ThemeChanged -= OnThemeChanged;
}
}
Loading
Loading