From e0175fc4e5ecd94676152cb283908b5d767ab529 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Tue, 29 Jul 2025 18:48:12 +0000 Subject: [PATCH] add light mode (#2223) --- CHANGELOG.md | 1 + ErsatzTV.Core/Domain/ConfigElementKey.cs | 1 + ErsatzTV/Pages/Artist.razor | 6 +-- ErsatzTV/Pages/Movie.razor | 6 +-- ErsatzTV/Pages/TelevisionEpisodeList.razor | 6 +-- ErsatzTV/Pages/TelevisionSeasonList.razor | 6 +-- ErsatzTV/Pages/_Host.cshtml | 2 +- ErsatzTV/Shared/MainLayout.razor | 50 ++++++++++++++++++++-- ErsatzTV/wwwroot/css/site.css | 31 +++++++++----- 9 files changed, 81 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 350f44dfd..b5c109383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Sidecar text subtitles (e.g. `srt` files) - Embedded image subtitles - Embedded text subtitles that have already been extracted by ETV +- Add light mode and light/dark mode toggle to app bar ### Fixed - Fix app startup with MySql/MariaDB diff --git a/ErsatzTV.Core/Domain/ConfigElementKey.cs b/ErsatzTV.Core/Domain/ConfigElementKey.cs index 495a48fdc..b24b241cc 100644 --- a/ErsatzTV.Core/Domain/ConfigElementKey.cs +++ b/ErsatzTV.Core/Domain/ConfigElementKey.cs @@ -28,6 +28,7 @@ public class ConfigElementKey public static ConfigElementKey SearchIndexVersion => new("search_index.version"); public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count"); public static ConfigElementKey HDHRUUID => new("hdhr.uuid"); + public static ConfigElementKey PagesIsDarkMode => new("pages.is_dark_mode"); public static ConfigElementKey ChannelsPageSize => new("pages.channels.page_size"); public static ConfigElementKey CollectionsPageSize => new("pages.collections.page_size"); public static ConfigElementKey MultiCollectionsPageSize => new("pages.multi_collections.page_size"); diff --git a/ErsatzTV/Pages/Artist.razor b/ErsatzTV/Pages/Artist.razor index 2a9a87569..a3b79f220 100644 --- a/ErsatzTV/Pages/Artist.razor +++ b/ErsatzTV/Pages/Artist.razor @@ -19,13 +19,13 @@ fan art } - + @if (!string.IsNullOrWhiteSpace(_artist.Thumbnail)) { - + } -
+
@_artist?.Name diff --git a/ErsatzTV/Pages/Movie.razor b/ErsatzTV/Pages/Movie.razor index 678ebd918..7c008cb76 100644 --- a/ErsatzTV/Pages/Movie.razor +++ b/ErsatzTV/Pages/Movie.razor @@ -27,11 +27,11 @@ } } - + @if (!string.IsNullOrWhiteSpace(_movie?.Poster)) { -
+
@if (_movie.MediaItemState == MediaItemState.FileNotFound) { @@ -47,7 +47,7 @@ }
} -
+
@_movie?.Title diff --git a/ErsatzTV/Pages/TelevisionEpisodeList.razor b/ErsatzTV/Pages/TelevisionEpisodeList.razor index d59653e43..94eff53ba 100644 --- a/ErsatzTV/Pages/TelevisionEpisodeList.razor +++ b/ErsatzTV/Pages/TelevisionEpisodeList.razor @@ -27,13 +27,13 @@ } } - + @if (!string.IsNullOrWhiteSpace(_season?.Poster)) { - + } - + diff --git a/ErsatzTV/Pages/TelevisionSeasonList.razor b/ErsatzTV/Pages/TelevisionSeasonList.razor index 98668aca5..f3acc524e 100644 --- a/ErsatzTV/Pages/TelevisionSeasonList.razor +++ b/ErsatzTV/Pages/TelevisionSeasonList.razor @@ -26,10 +26,10 @@ } } - + - - + + @_show?.Title diff --git a/ErsatzTV/Pages/_Host.cshtml b/ErsatzTV/Pages/_Host.cshtml index e6ca5b2c4..d601bb0b7 100644 --- a/ErsatzTV/Pages/_Host.cshtml +++ b/ErsatzTV/Pages/_Host.cshtml @@ -19,7 +19,7 @@ - + diff --git a/ErsatzTV/Shared/MainLayout.razor b/ErsatzTV/Shared/MainLayout.razor index ff4ab8e78..a008cba60 100644 --- a/ErsatzTV/Shared/MainLayout.razor +++ b/ErsatzTV/Shared/MainLayout.razor @@ -1,5 +1,6 @@ @inherits LayoutComponentBase @using System.Reflection +@using ErsatzTV.Application.Configuration @using ErsatzTV.Application.Search @using ErsatzTV.Core.Health @using ErsatzTV.Core.Interfaces.Search @@ -13,12 +14,12 @@ @inject ICourier Courier @inject IHealthCheckService HealthCheckService; - + - +
@@ -92,6 +93,7 @@ +
@@ -225,6 +227,7 @@ private List _searchTargets; private int _errors; private int _warnings; + private bool _isDarkMode = true; protected override void OnInitialized() { @@ -242,6 +245,18 @@ 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; @@ -257,7 +272,7 @@ private static MudTheme ErsatzTvTheme => new() { - PaletteLight = new PaletteLight + PaletteDark = new PaletteDark { ActionDefault = "rgba(255,255,255, 0.80)", Primary = "#009000", @@ -269,7 +284,8 @@ DrawerText = "rgba(255,255,255, 0.80)", Divider = "rgba(255,255,255, 0.40)", Background = "#272727", - TextPrimary = "rgba(255,255,255, 0.80)", + BackgroundGray = "#272727", + TextPrimary = "rgba(255,255,255, 0.90)", TextSecondary = "rgba(255,255,255, 0.80)", TextDisabled = "rgba(255,255,255, 0.40)", ActionDisabled = "rgba(255,255,255, 0.40)", @@ -278,6 +294,28 @@ Info = "#00c0c0", Tertiary = "#00c000", White = Colors.Shades.White + }, + + PaletteLight = new PaletteLight + { + ActionDefault = "#546E7A", + Primary = "#546E7A", + Secondary = "#EC407A", + AppbarBackground = "#ECEFF1", + AppbarText = "#424242", + DrawerBackground = "#FFFFFF", + DrawerText = "#424242", + Surface = "#FFFFFF", + Background = "#F5F5F5", + TextPrimary = "#212121", + TextSecondary = "rgba(0,0,0, 0.7)", + TextDisabled = "rgba(0,0,0, 0.5)", + ActionDisabled = "rgba(0,0,0, 0.3)", + Divider = "rgba(0,0,0, 0.12)", + TableHover = "rgba(0,0,0, 0.02)", + TableLines = "rgba(0,0,0,0.08)", + Info = "#00c0c0", + Tertiary = "#00c000" } }; @@ -318,6 +356,10 @@ { _searchTargets = await Mediator.Send(new QuerySearchTargets(), _cts.Token); } + + _isDarkMode = await Mediator.Send(new GetConfigElementByKey(ConfigElementKey.PagesIsDarkMode), _cts.Token) + .MapT(result => !bool.TryParse(result.Value, out bool value) || value) + .IfNoneAsync(true); } protected async void OnSearchTargetsChanged(object sender, EventArgs e) => _searchTargets = await Mediator.Send(new QuerySearchTargets(), _cts.Token); diff --git a/ErsatzTV/wwwroot/css/site.css b/ErsatzTV/wwwroot/css/site.css index f9b306040..ae6838c9b 100644 --- a/ErsatzTV/wwwroot/css/site.css +++ b/ErsatzTV/wwwroot/css/site.css @@ -1,4 +1,18 @@ -.media-card-grid { +div.ersatztv-dark { + --fanart-background-rgb: 39, 39, 39; + --search-bar-background: rgba(255, 255, 255, .15); + --search-bar-text-color: #fafafa; + --selected-row-color: #009000; +} + +div.ersatztv-light { + --fanart-background-rgb: 245, 245, 245; + --search-bar-background: #fff; + --search-bar-text-color: #424242; + --selected-row-color: #546E7A77; +} + +.media-card-grid { display: flex; flex-direction: row; flex-wrap: wrap; @@ -52,13 +66,13 @@ .search-bar .mud-input { height: 42px; } .search-bar { - background-color: rgba(255, 255, 255, .15); + background-color: var(--search-bar-background); border-radius: 4px; height: 42px; margin-bottom: 5px; } -.search-bar div .mud-input-root { color: #fafafa; } +.search-bar div .mud-input-root { color: var(--search-bar-text-color); } .search-bar .mud-input.mud-input-outlined .mud-input-outlined-border { border: none; @@ -70,7 +84,7 @@ .fanart-container { position: relative; width: 100%; - z-index: -1; + z-index: 0; } .fanart-container img { @@ -83,7 +97,7 @@ } .fanart-container > .fanart-tint { - background: linear-gradient(rgba(39, 39, 39, 0.47) 0%, rgb(39, 39, 39) 100%); + background: linear-gradient(rgba(var(--fanart-background-rgb), 0.47) 0%, rgb(var(--fanart-background-rgb)) 100%); height: 400px; position: absolute; width: 100%; @@ -148,11 +162,6 @@ font-weight: unset; } -.form-field-stack .mud-typography, -.mud-form .mud-typography-h5 { - color: rgba(255, 255, 255, 0.90); -} - .form-field-stack > div.d-flex { width: 300px; justify-content: flex-start; @@ -168,5 +177,5 @@ } .selected { - background-color: #009000 !important; + background-color: var(--selected-row-color) !important; }