From 25336a6b9cae25352e0400224dea1c3828bac851 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 28 Jul 2026 05:17:16 -0400 Subject: [PATCH] feat(maui): add font size setting to MAUI app (fixes #165) --- .../Domain/SettingsTests.cs | 20 +++++++++++- src/NoteBookmark.Domain/Settings.cs | 3 ++ .../FontSizeHelperTests.cs | 25 +++++++++++++++ .../LocalDataServiceTests.cs | 17 ++++++++++ .../Components/Layout/MainLayout.razor | 21 ++++++++++++ src/NoteBookmark.MauiApp/Data/LocalModels.cs | 5 ++- src/NoteBookmark.MauiApp/wwwroot/app.css | 6 ++++ .../Components/Pages/Settings.razor | 32 +++++++++++++++++++ src/NoteBookmark.SharedUI/FontSizeHelper.cs | 15 +++++++++ 9 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/NoteBookmark.MauiApp.Tests/FontSizeHelperTests.cs create mode 100644 src/NoteBookmark.SharedUI/FontSizeHelper.cs diff --git a/src/NoteBookmark.Api.Tests/Domain/SettingsTests.cs b/src/NoteBookmark.Api.Tests/Domain/SettingsTests.cs index 5bffb79..b547c11 100644 --- a/src/NoteBookmark.Api.Tests/Domain/SettingsTests.cs +++ b/src/NoteBookmark.Api.Tests/Domain/SettingsTests.cs @@ -194,7 +194,8 @@ public void Settings_WithFullData_PreservesAllValues() SearchPrompt = "Find {topic}", AiApiKey = "test-key", AiBaseUrl = "https://api.openai.com", - AiModelName = "gpt-4" + AiModelName = "gpt-4", + FontSize = "large" }; // Assert @@ -207,5 +208,22 @@ public void Settings_WithFullData_PreservesAllValues() settings.AiApiKey.Should().Be("test-key"); settings.AiBaseUrl.Should().Be("https://api.openai.com"); settings.AiModelName.Should().Be("gpt-4"); + settings.FontSize.Should().Be("large"); + } + + [Theory] + [InlineData("small")] + [InlineData("medium")] + [InlineData("large")] + public void FontSize_CanBeSetAndRetrieved(string fontSize) + { + var settings = new Settings + { + PartitionKey = "setting", + RowKey = "setting", + FontSize = fontSize + }; + + settings.FontSize.Should().Be(fontSize); } } diff --git a/src/NoteBookmark.Domain/Settings.cs b/src/NoteBookmark.Domain/Settings.cs index f37d026..7c029fd 100644 --- a/src/NoteBookmark.Domain/Settings.cs +++ b/src/NoteBookmark.Domain/Settings.cs @@ -44,6 +44,9 @@ public class Settings: ITableEntity [DataMember(Name="ai_model_name")] public string? AiModelName { get; set; } + + [DataMember(Name="font_size")] + public string? FontSize { get; set; } public required string PartitionKey { get ; set; } public required string RowKey { get ; set; } diff --git a/src/NoteBookmark.MauiApp.Tests/FontSizeHelperTests.cs b/src/NoteBookmark.MauiApp.Tests/FontSizeHelperTests.cs new file mode 100644 index 0000000..9540ec8 --- /dev/null +++ b/src/NoteBookmark.MauiApp.Tests/FontSizeHelperTests.cs @@ -0,0 +1,25 @@ +using FluentAssertions; +using NoteBookmark.SharedUI; +using Xunit; + +namespace NoteBookmark.MauiApp.Tests; + +public class FontSizeHelperTests +{ + [Theory] + [InlineData("small", "0.875rem")] + [InlineData("SMALL", "0.875rem")] + [InlineData("medium", "1rem")] + [InlineData("MEDIUM", "1rem")] + [InlineData("large", "1.25rem")] + [InlineData("LARGE", "1.25rem")] + [InlineData("18px", "18px")] + [InlineData(null, "1rem")] + [InlineData("", "1rem")] + [InlineData(" ", "1rem")] + public void ToCssValue_MapsFontSizeCorrectly(string? input, string expectedCssValue) + { + var result = FontSizeHelper.ToCssValue(input); + result.Should().Be(expectedCssValue); + } +} diff --git a/src/NoteBookmark.MauiApp.Tests/LocalDataServiceTests.cs b/src/NoteBookmark.MauiApp.Tests/LocalDataServiceTests.cs index d1e5b2d..c3311d0 100644 --- a/src/NoteBookmark.MauiApp.Tests/LocalDataServiceTests.cs +++ b/src/NoteBookmark.MauiApp.Tests/LocalDataServiceTests.cs @@ -144,4 +144,21 @@ public async Task DeletePost_ShouldSoftDeleteAndFlagPendingSync() var pending = await _sut.GetPendingSyncPostsAsync(); pending.Should().ContainSingle(p => p.Id == "post1", "because it needs to sync the deletion"); } + + [Fact] + public async Task SaveSettings_ShouldStoreAndRetrieveFontSize() + { + var settings = new Settings + { + PartitionKey = "setting", + RowKey = "setting", + FontSize = "large" + }; + + await _sut.SaveSettingsAsync(settings); + + var retrieved = await _sut.GetSettingsAsync(); + retrieved.Should().NotBeNull(); + retrieved!.FontSize.Should().Be("large"); + } } diff --git a/src/NoteBookmark.MauiApp/Components/Layout/MainLayout.razor b/src/NoteBookmark.MauiApp/Components/Layout/MainLayout.razor index 6a5435b..2d62a87 100644 --- a/src/NoteBookmark.MauiApp/Components/Layout/MainLayout.razor +++ b/src/NoteBookmark.MauiApp/Components/Layout/MainLayout.razor @@ -3,10 +3,13 @@ @using Microsoft.Maui.ApplicationModel @using Microsoft.FluentUI.AspNetCore.Components @using NoteBookmark.MauiApp.Data +@using NoteBookmark.SharedUI @inject IConnectivity Connectivity @inject NavigationManager Navigation @inject ISyncService SyncService @inject IToastService ToastService +@inject IDataService DataService +@inject IJSRuntime JSRuntime @implements IDisposable @@ -69,6 +72,24 @@ } } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + try + { + var settings = await DataService.GetSettings(); + var cssSize = FontSizeHelper.ToCssValue(settings?.FontSize); + await JSRuntime.InvokeVoidAsync("document.documentElement.style.setProperty", "--app-font-size", cssSize); + await JSRuntime.InvokeVoidAsync("document.body.style.setProperty", "--app-font-size", cssSize); + } + catch + { + // Fallback for headless environments or prerendering + } + } + } + private void OnSyncConflictDetected(object? sender, SyncConflictEventArgs e) { InvokeAsync(() => diff --git a/src/NoteBookmark.MauiApp/Data/LocalModels.cs b/src/NoteBookmark.MauiApp/Data/LocalModels.cs index 14ca4b3..681dd48 100644 --- a/src/NoteBookmark.MauiApp/Data/LocalModels.cs +++ b/src/NoteBookmark.MauiApp/Data/LocalModels.cs @@ -179,6 +179,7 @@ public class LocalSettings public string? AiApiKey { get; set; } public string? AiBaseUrl { get; set; } public string? AiModelName { get; set; } + public string? FontSize { get; set; } public bool IsPendingSync { get; set; } public Settings ToDomain() => new Settings @@ -193,7 +194,8 @@ public class LocalSettings SearchPrompt = SearchPrompt, AiApiKey = AiApiKey, AiBaseUrl = AiBaseUrl, - AiModelName = AiModelName + AiModelName = AiModelName, + FontSize = FontSize }; public static LocalSettings FromDomain(Settings s, bool isPendingSync = false) => new LocalSettings @@ -209,6 +211,7 @@ public class LocalSettings AiApiKey = s.AiApiKey, AiBaseUrl = s.AiBaseUrl, AiModelName = s.AiModelName, + FontSize = s.FontSize, IsPendingSync = isPendingSync }; } diff --git a/src/NoteBookmark.MauiApp/wwwroot/app.css b/src/NoteBookmark.MauiApp/wwwroot/app.css index 77e3472..49ca487 100644 --- a/src/NoteBookmark.MauiApp/wwwroot/app.css +++ b/src/NoteBookmark.MauiApp/wwwroot/app.css @@ -1,5 +1,11 @@ +:root { + --app-font-size: 1rem; +} + html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: var(--app-font-size, 1rem); + --type-ramp-base-font-size: var(--app-font-size, 1rem); } a, .btn-link { diff --git a/src/NoteBookmark.SharedUI/Components/Pages/Settings.razor b/src/NoteBookmark.SharedUI/Components/Pages/Settings.razor index eb66dcd..5b65bab 100644 --- a/src/NoteBookmark.SharedUI/Components/Pages/Settings.razor +++ b/src/NoteBookmark.SharedUI/Components/Pages/Settings.razor @@ -5,6 +5,7 @@ @using NoteBookmark.Domain @inject IDataService client @inject NavigationManager Navigation +@inject IJSRuntime JSRuntime + + + + @@ -103,9 +112,22 @@ private Domain.Settings? settings; + private record FontSizeOption(string Text, string Value); + + private static readonly List fontSizeOptions = new() + { + new("Small", "small"), + new("Medium", "medium"), + new("Large", "large") + }; + protected override async Task OnInitializedAsync() { settings = await client.GetSettings(); + if (settings != null && string.IsNullOrWhiteSpace(settings.FontSize)) + { + settings.FontSize = FontSizeHelper.DefaultFontSize; + } } private async Task SaveSettings() @@ -113,6 +135,16 @@ if (settings != null) { await client.SaveSettings(settings); + try + { + var cssSize = FontSizeHelper.ToCssValue(settings.FontSize); + await JSRuntime.InvokeVoidAsync("document.documentElement.style.setProperty", "--app-font-size", cssSize); + await JSRuntime.InvokeVoidAsync("document.body.style.setProperty", "--app-font-size", cssSize); + } + catch + { + // Prerender / non-JS environment safe + } Navigation.NavigateTo("/"); } } diff --git a/src/NoteBookmark.SharedUI/FontSizeHelper.cs b/src/NoteBookmark.SharedUI/FontSizeHelper.cs new file mode 100644 index 0000000..9138b7a --- /dev/null +++ b/src/NoteBookmark.SharedUI/FontSizeHelper.cs @@ -0,0 +1,15 @@ +namespace NoteBookmark.SharedUI; + +public static class FontSizeHelper +{ + public const string DefaultFontSize = "medium"; + + public static string ToCssValue(string? fontSize) => fontSize?.ToLowerInvariant() switch + { + "small" => "0.875rem", + "large" => "1.25rem", + "medium" => "1rem", + _ when !string.IsNullOrWhiteSpace(fontSize) => fontSize, + _ => "1rem" + }; +}