Skip to content
Draft
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 JournalApp/Data/CommonServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static void AddCommonJournalAppServices(this IServiceCollection services)
services.AddSingleton<AppDbSeeder>();
services.AddSingleton<KeyEventService>();
services.AddSingleton<PreferenceService>();
services.AddSingleton<SystemColorService>();
services.AddSingleton<CalendarService>();
services.AddSingleton<DataPointService>();
}
Expand Down
14 changes: 8 additions & 6 deletions JournalApp/Data/PreferenceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ public sealed class PreferenceService : IPreferences, IDisposable
private readonly ILogger<PreferenceService> logger;
private readonly IPreferences _preferenceStore;
private readonly Application _application;
private readonly SystemColorService _systemColorService;
private Dictionary<string, string> _moodColors;
private AppTheme? _theme;

public PreferenceService(ILogger<PreferenceService> logger, IPreferences preferenceStore)
public PreferenceService(ILogger<PreferenceService> logger, IPreferences preferenceStore, SystemColorService systemColorService)
{
this.logger = logger;
_preferenceStore = preferenceStore;
_systemColorService = systemColorService;

// Not available in unit tests.
if (Application.Current != null)
Expand Down Expand Up @@ -119,13 +121,13 @@ private void UpdateStatusBar()
#pragma warning disable CS0618 // Type or member is obsolete
if (IsDarkMode)
{
StatusBar.SetColor(Color.FromHex("#EAB8D6"));
StatusBar.SetStyle(StatusBarStyle.DarkContent);
StatusBar.SetColor(Color.FromHex("#111111"));
StatusBar.SetStyle(StatusBarStyle.LightContent);
}
else
{
StatusBar.SetColor(Color.FromHex("#854C73"));
StatusBar.SetStyle(StatusBarStyle.LightContent);
StatusBar.SetColor(Color.FromHex("#FFFFFF"));
StatusBar.SetStyle(StatusBarStyle.DarkContent);
}
#pragma warning restore CS0618 // Type or member is obsolete
}
Expand All @@ -135,7 +137,7 @@ private void GenerateMoodColors()
{
var emojis = DataPoint.Moods.Where(x => x != "🤔").ToList();
#pragma warning disable CS0618 // Type or member is obsolete
var primary = Color.FromHex("#FF9FDF");
var primary = Color.FromHex(_systemColorService.GetSourceColorHex());
#pragma warning restore CS0618 // Type or member is obsolete
var complementary = primary.GetComplementary();

Expand Down
2 changes: 1 addition & 1 deletion JournalApp/Pages/Calendar/CalendarDay.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
}

.calendar-day-with-mood {
color: #3A2F36;
color: var(--mud-palette-text-primary);
}

.calendar-day-number {
Expand Down
157 changes: 105 additions & 52 deletions JournalApp/Pages/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
@inject KeyEventService KeyEventService
@inject NavigationManager NavigationManager
@inject PreferenceService PreferenceService
@inject SystemColorService SystemColorService
@inject IJSRuntime JSRuntime

<MudThemeProvider Theme="_theme" IsDarkMode="PreferenceService.IsDarkMode" DefaultScrollbar />

Expand All @@ -25,58 +27,9 @@

@code {
bool _hasInitiallyRendered;
IJSObjectReference _themeModule;

MudTheme _theme = new()
{
PaletteLight = new PaletteLight()
{
// https://materialkolor.com Seed FFFE73D8, Spec 2025.
Primary = "#854C73",
TextPrimary = "#3A2F36",
PrimaryContrastText = "#FFF7F8",
Secondary = "#715867",
TextSecondary = "#FFF7F8",
SecondaryContrastText = "#FFF7F8",
Error = "#A8364B",
Background = "#FEF0F6",
Surface = "#FAEAF0",

HoverOpacity = 0.1,
},

PaletteDark = new PaletteDark()
{
// https://materialkolor.com Seed FFFE73D8, Spec 2025.
Primary = "#EAB8D6",
TextPrimary = "#DECCD4",
PrimaryContrastText = "#59344D",
Secondary = "#DDBECF",
TextSecondary = "#503A47",
Error = "#F97386",
Background = "#1F171C",
Surface = "#120D10",

HoverOpacity = 0.1,
},

LayoutProperties = new()
{
DefaultBorderRadius = "4px",
},

Typography = new()
{
Button = new ButtonTypography()
{
TextTransform = "none",
},

Caption = new CaptionTypography()
{
LineHeight = "1",
},
},
};
MudTheme _theme = CreateBaseTheme();

protected override void OnInitialized()
{
Expand Down Expand Up @@ -107,13 +60,14 @@
if (firstRender)
{
_hasInitiallyRendered = true;
_ = ApplyDynamicThemeAsync();
}
}

public void OnThemeChanged(object sender, bool isDarkMode)
{
if (_hasInitiallyRendered)
StateHasChanged();
_ = InvokeAsync(ApplyDynamicThemeAsync);
}

void OnNewIntent(object sender, EventArgs e)
Expand All @@ -126,5 +80,104 @@
{
PreferenceService.ThemeChanged -= OnThemeChanged;
App.NewIntent -= OnNewIntent;
if (_themeModule != null)
_ = _themeModule.DisposeAsync();
}

static MudTheme CreateBaseTheme()
{
return new MudTheme
{
PaletteLight = new PaletteLight
{
Primary = "#5B5B5B",
PrimaryContrastText = "#FFFFFF",
Secondary = "#6B6B6B",
SecondaryContrastText = "#FFFFFF",
TextPrimary = "#1F1F1F",
TextSecondary = "#3B3B3B",
Error = "#B3261E",
Background = "#FFFFFF",
Surface = "#F5F5F5",
HoverOpacity = 0.1,
},
PaletteDark = new PaletteDark
{
Primary = "#C7C7C7",
PrimaryContrastText = "#1F1F1F",
Secondary = "#B0B0B0",
SecondaryContrastText = "#1F1F1F",
TextPrimary = "#F1F1F1",
TextSecondary = "#CFCFCF",
Error = "#F2B8B5",
Background = "#0F0F0F",
Surface = "#1A1A1A",
HoverOpacity = 0.1,
},
LayoutProperties = new LayoutProperties
{
DefaultBorderRadius = "4px",
},
Typography = new Typography
{
Button = new ButtonTypography
{
TextTransform = "none",
},
Caption = new CaptionTypography
{
LineHeight = "1",
},
},
};
}

async Task ApplyDynamicThemeAsync()
{
try
{
_themeModule ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./theme/material-theme.js");
var sourceColor = SystemColorService.GetSourceColorHex();

var lightTokens = await _themeModule.InvokeAsync<MaterialThemeTokens>("getThemeTokens", sourceColor, false);
var darkTokens = await _themeModule.InvokeAsync<MaterialThemeTokens>("getThemeTokens", sourceColor, true);

ApplyTokens(_theme.PaletteLight, lightTokens);
ApplyTokens(_theme.PaletteDark, darkTokens);

StateHasChanged();
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to apply dynamic theme. Using fallback palette.");
}
}

static void ApplyTokens(PaletteLight palette, MaterialThemeTokens tokens)
{
palette.Primary = tokens.Primary;
palette.PrimaryContrastText = tokens.OnPrimary;
palette.Secondary = tokens.Secondary;
palette.SecondaryContrastText = tokens.OnSecondary;
palette.TextPrimary = tokens.OnBackground;
palette.TextSecondary = tokens.OnSurfaceVariant;
palette.Error = tokens.Error;
palette.Background = tokens.Background;
palette.Surface = tokens.Surface;
palette.HoverOpacity = 0.1;
}

static void ApplyTokens(PaletteDark palette, MaterialThemeTokens tokens)
{
palette.Primary = tokens.Primary;
palette.PrimaryContrastText = tokens.OnPrimary;
palette.Secondary = tokens.Secondary;
palette.SecondaryContrastText = tokens.OnSecondary;
palette.TextPrimary = tokens.OnBackground;
palette.TextSecondary = tokens.OnSurfaceVariant;
palette.Error = tokens.Error;
palette.Background = tokens.Background;
palette.Surface = tokens.Surface;
palette.HoverOpacity = 0.1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#111111</color>
<color name="colorPrimaryDark">#111111</color>
</resources>
6 changes: 3 additions & 3 deletions JournalApp/Platforms/Android/Resources/values/colors.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#854C73</color>
<color name="colorPrimaryDark">#854C73</color>
</resources>
<color name="colorPrimary">#FFFFFF</color>
<color name="colorPrimaryDark">#FFFFFF</color>
</resources>
2 changes: 1 addition & 1 deletion JournalApp/Resources/AppIcon/appicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion JournalApp/Resources/AppIcon/appiconfg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion JournalApp/Resources/AppIcon/appiconfg_debug.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading