Browse Source

move dark/light mode toggle to ui settings page

pull/2809/head
Jason Dove 6 months ago
parent
commit
1eab8568eb
No known key found for this signature in database
  1. 3
      CHANGELOG.md
  2. 5
      ErsatzTV.Application/Configuration/Commands/UpdateUiSettings.cs
  3. 26
      ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs
  4. 3
      ErsatzTV.Application/Configuration/Queries/GetUiSettings.cs
  5. 20
      ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs
  6. 6
      ErsatzTV.Application/Configuration/UiSettingsViewModel.cs
  7. 5
      ErsatzTV.Core/Notifications/ThemeUpdatedNotification.cs
  8. 75
      ErsatzTV/Pages/Settings/UISettings.razor
  9. 33
      ErsatzTV/Shared/MainLayout.razor

3
CHANGELOG.md

@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add log warnings when actual transcoding speed is potentially insufficient to support smooth playback
- Log messages will include media item id, channel number and transcoding speed
### Changed
- Move dark/light mode toggle to **Settings** > **UI**
## [26.2.0] - 2026-02-02
### Added
- Channel stream selector: add zero-based culture-specific `day_of_week` to `content_condition`, for example:

5
ErsatzTV.Application/Configuration/Commands/UpdateUiSettings.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Configuration;
public record UpdateUiSettings(UiSettingsViewModel UiSettings) : IRequest<Either<BaseError, Unit>>;

26
ErsatzTV.Application/Configuration/Commands/UpdateUiSettingsHandler.cs

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class UpdateUiSettingsHandler(IConfigElementRepository configElementRepository)
: IRequestHandler<UpdateUiSettings, Either<BaseError, Unit>>
{
public async Task<Either<BaseError, Unit>> Handle(
UpdateUiSettings request,
CancellationToken cancellationToken)
{
return await ApplyUpdate(request.UiSettings, cancellationToken);
}
private async Task<Unit> ApplyUpdate(UiSettingsViewModel uiSettings, CancellationToken cancellationToken)
{
await configElementRepository.Upsert(
ConfigElementKey.PagesIsDarkMode,
uiSettings.PagesIsDarkMode,
cancellationToken);
return Unit.Default;
}
}

3
ErsatzTV.Application/Configuration/Queries/GetUiSettings.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Configuration;
public record GetUiSettings : IRequest<UiSettingsViewModel>;

20
ErsatzTV.Application/Configuration/Queries/GetUiSettingsHandler.cs

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class GetUiSettingsHandler(IConfigElementRepository configElementRepository)
: IRequestHandler<GetUiSettings, UiSettingsViewModel>
{
public async Task<UiSettingsViewModel> Handle(GetUiSettings request, CancellationToken cancellationToken)
{
Option<bool> pagesIsDarkMode = await configElementRepository.GetValue<bool>(
ConfigElementKey.PagesIsDarkMode,
cancellationToken);
return new UiSettingsViewModel
{
PagesIsDarkMode = await pagesIsDarkMode.IfNoneAsync(true)
};
}
}

6
ErsatzTV.Application/Configuration/UiSettingsViewModel.cs

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
namespace ErsatzTV.Application.Configuration;
public class UiSettingsViewModel
{
public bool PagesIsDarkMode { get; set; }
}

5
ErsatzTV.Core/Notifications/ThemeUpdatedNotification.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
using MediatR;
namespace ErsatzTV.Core.Notifications;
public record ThemeUpdatedNotification : INotification;

75
ErsatzTV/Pages/Settings/UISettings.razor

@ -0,0 +1,75 @@ @@ -0,0 +1,75 @@
@page "/settings/ui"
@using ErsatzTV.Application.Configuration
@using ErsatzTV.Core.Notifications
@implements IDisposable
@inject IMediator Mediator
@inject ISnackbar Snackbar
@inject ILogger<UISettings> Logger
<MudForm Style="max-height: 100%">
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SaveUiSettings())" Class="ml-6" StartIcon="@Icons.Material.Filled.Save">Save Settings</MudButton>
</MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">UI</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Default Theme</MudText>
</div>
<MudSelect @bind-Value="_uiSettings.PagesIsDarkMode">
<MudSelectItem Value="@true">Dark</MudSelectItem>
<MudSelectItem Value="@false">Light</MudSelectItem>
</MudSelect>
</MudStack>
</MudContainer>
</div>
</MudForm>
@code {
private CancellationTokenSource _cts;
private UiSettingsViewModel _uiSettings = new();
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
protected override async Task OnParametersSetAsync()
{
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
var token = _cts.Token;
try
{
_uiSettings = await Mediator.Send(new GetUiSettings(), token);
}
catch (OperationCanceledException)
{
// do nothing
}
}
private async Task SaveUiSettings()
{
Either<BaseError, Unit> result = await Mediator.Send(new UpdateUiSettings(_uiSettings), _cts.Token);
foreach (var error in result.LeftToSeq())
{
Snackbar.Add(error.Value, Severity.Error);
Logger.LogError("Unexpected error saving ui settings: {Error}", error.Value);
}
if (result.IsRight)
{
Snackbar.Add("Successfully saved UI settings", Severity.Success);
await Mediator.Publish(new ThemeUpdatedNotification(), _cts.Token);
}
}
}

33
ErsatzTV/Shared/MainLayout.razor

@ -103,7 +103,6 @@ @@ -103,7 +103,6 @@
<MudTooltip Text="GitHub">
<MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Primary" Href="https://github.com/ErsatzTV/ErsatzTV" Target="_blank"/>
</MudTooltip>
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Primary" OnClick="@DarkModeToggle"/>
<AuthorizeView>
<form action="/account/logout" method="post">
<MudTooltip Text="Logout">
@ -189,6 +188,7 @@ @@ -189,6 +188,7 @@
<MudNavLink Href="settings/hdhr">HDHomeRun</MudNavLink>
<MudNavLink Href="settings/scanner">Scanner</MudNavLink>
<MudNavLink Href="settings/playout">Playout</MudNavLink>
<MudNavLink Href="settings/ui">UI</MudNavLink>
<MudNavLink Href="settings/xmltv">XMLTV</MudNavLink>
</MudNavGroup>
<MudNavGroup Expanded="true">
@ -286,6 +286,7 @@ @@ -286,6 +286,7 @@
Courier.Subscribe<HealthCheckSummary>(HandleHealthCheckSummary);
Courier.Subscribe<PlayoutUpdatedNotification>(HandlePlayoutUpdated);
Courier.Subscribe<ThemeUpdatedNotification>(HandleThemeUpdated);
}
protected override async Task OnInitializedAsync()
@ -294,18 +295,6 @@ @@ -294,18 +295,6 @@
await HandleHealthCheckSummary(HealthCheckService.GetHealthCheckSummary(), CancellationToken.None);
}
private async Task DarkModeToggle()
{
_isDarkMode = !_isDarkMode;
await Mediator.Send(new SaveConfigElementByKey(ConfigElementKey.PagesIsDarkMode, _isDarkMode.ToString()));
}
public string DarkLightModeButtonIcon => _isDarkMode switch
{
true => Icons.Material.Rounded.DarkMode,
false => Icons.Material.Outlined.LightMode
};
public void Dispose()
{
SystemStartup.OnDatabaseReady -= OnStartupProgress;
@ -314,6 +303,8 @@ @@ -314,6 +303,8 @@
SearchTargets.OnSearchTargetsChanged -= OnSearchTargetsChanged;
Courier.UnSubscribe<HealthCheckSummary>(HandleHealthCheckSummary);
Courier.UnSubscribe<PlayoutUpdatedNotification>(HandlePlayoutUpdated);
Courier.UnSubscribe<ThemeUpdatedNotification>(HandleThemeUpdated);
_cts?.Cancel();
_cts?.Dispose();
@ -529,4 +520,20 @@ @@ -529,4 +520,20 @@
}
}
private async Task HandleThemeUpdated(ThemeUpdatedNotification _, CancellationToken cancellationToken)
{
try
{
_isDarkMode = await Mediator.Send(new GetConfigElementByKey(ConfigElementKey.PagesIsDarkMode), cancellationToken)
.MapT(result => !bool.TryParse(result.Value, out bool value) || value)
.IfNoneAsync(true);
await InvokeAsync(StateHasChanged);
}
catch (Exception)
{
// do nothing
}
}
}

Loading…
Cancel
Save