feat(socials): bar appearance — text colour, background plate, opacity, text shadow

In the Socials drawer, under the show/hide switch: text colour, an optional background plate
with its own colour and opacity, and a text drop shadow. The bar had been hard-wired
white-on-transparent since it shipped.

- `SocialsConfig` gains five additive properties (BarTextColor, BarBackgroundEnabled,
  BarBackgroundColor, BarBackgroundOpacity, BarTextShadow).
- Persistence: five new Socials columns, added by the per-column idempotent migration pattern
  (a half-migrated DB heals instead of failing), written in Save and read back in Load with
  defaults for pre-existing rows.
- `SocialBarRenderer.Render(entries, SocialBarStyle?)` — the look is a separate value type so the
  renderer stays a pure "entries + look" function and the preview can never read a half-applied
  config mid-edit. A malformed hex falls back to white/black instead of throwing inside a render
  tick.
- The plate hugs the entries (rounded 20px, padding) so it reads as a caption bar, not a band
  across the frame. Off by default, because transparent is what creators have today.
- The colour pickers reuse `BorderColorPalette` — one catalogue of the app's colours, per the
  TASK 60/63 rule.

Guard: SocialBarAppearanceTests paints the real strip and inspects pixels — default is white with
no plate, a red text colour reaches the glyphs, a 55% blue plate yields half-transparent blue
pixels, and the shadow adds dark pixels that were not there before.
This commit is contained in:
2026-10-30 08:47:50 -07:00
parent 9528128ca7
commit c82636e1a9
9 changed files with 329 additions and 16 deletions
+55 -1
View File
@@ -42,6 +42,11 @@ public sealed class SocialsDialogViewModel : ViewModelBase
_signInProvider = signInProvider;
_signOutAction = signOutAction;
_barEnabled = current?.BarEnabled ?? true;
_barTextColor = current?.BarTextColor ?? "#FFFFFF";
_barBackgroundEnabled = current?.BarBackgroundEnabled ?? false;
_barBackgroundColor = current?.BarBackgroundColor ?? "#000000";
_barBackgroundOpacity = current?.BarBackgroundOpacity ?? 0.55;
_barTextShadow = current?.BarTextShadow ?? false;
_account = account;
_isSignedIn = account != null;
@@ -64,6 +69,47 @@ public sealed class SocialsDialogViewModel : ViewModelBase
set => SetProperty(ref _barEnabled, value);
}
// ─── Bar appearance (creator request 2026-10-30) ───────────────────────────
private string _barTextColor = "#FFFFFF";
public string BarTextColor
{
get => _barTextColor;
set => SetProperty(ref _barTextColor, value);
}
private bool _barBackgroundEnabled;
public bool BarBackgroundEnabled
{
get => _barBackgroundEnabled;
set => SetProperty(ref _barBackgroundEnabled, value);
}
private string _barBackgroundColor = "#000000";
public string BarBackgroundColor
{
get => _barBackgroundColor;
set => SetProperty(ref _barBackgroundColor, value);
}
private double _barBackgroundOpacity = 0.55;
public double BarBackgroundOpacity
{
get => _barBackgroundOpacity;
set => SetProperty(ref _barBackgroundOpacity, value);
}
private bool _barTextShadow;
public bool BarTextShadow
{
get => _barTextShadow;
set => SetProperty(ref _barTextShadow, value);
}
/// <summary>The colours offered for the text and the plate — the same catalogue the border
/// picker uses, so there is one list of "the app's colours" (TASK 60/63).</summary>
public IReadOnlyList<NamedColor> ColorOptions => BorderColorPalette.All;
public bool IsSignedIn
{
get => _isSignedIn;
@@ -275,7 +321,15 @@ public sealed class SocialsDialogViewModel : ViewModelBase
{
var entries = CommittedEntries ?? throw new InvalidOperationException("Save was not completed.");
if (entries.Count == 0) return null;
var config = new SocialsConfig { BarEnabled = BarEnabled };
var config = new SocialsConfig
{
BarEnabled = BarEnabled,
BarTextColor = BarTextColor,
BarBackgroundEnabled = BarBackgroundEnabled,
BarBackgroundColor = BarBackgroundColor,
BarBackgroundOpacity = BarBackgroundOpacity,
BarTextShadow = BarTextShadow,
};
foreach (var entry in entries)
config.Entries.Add(entry);
return config;