feat(about): restore the integrations section with honorific status

This commit is contained in:
2026-06-15 14:40:21 +02:00
parent 72099c8871
commit f9ed487ae2
3 changed files with 202 additions and 20 deletions
@@ -0,0 +1,29 @@
namespace HellionChat.Integrations;
internal enum HonorificStatusKind
{
NotInstalled,
Incompatible,
Detected,
}
internal static class HonorificStatus
{
// Mirrors the 1.5.6 three-state discriminator (1d3b429:About.cs:171/183/196):
// it keys on IsAvailable + the *nullability* of DetectedApiVersion, never a
// recomputed major check. IsAvailable already encodes the compatibility
// result HonorificService set during the initial pull. Null-safe: an
// (isAvailable=true, detectedApiVersion=null) state a test seam can produce
// resolves to NotInstalled rather than dereferencing null.
internal static HonorificStatusKind Resolve(
bool isAvailable,
(uint Major, uint Minor)? detectedApiVersion
)
{
if (isAvailable && detectedApiVersion is not null)
return HonorificStatusKind.Detected;
if (detectedApiVersion is not null)
return HonorificStatusKind.Incompatible;
return HonorificStatusKind.NotInstalled;
}
}
+4 -1
View File
@@ -196,7 +196,10 @@ internal static class PluginHostFactory
));
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.AboutTab(
sp.GetRequiredService<FontManager>(),
sp.GetRequiredService<ILogger<Ui.Components.Settings.Tabs.AboutTab>>()
sp.GetRequiredService<Plugin>(),
sp.GetRequiredService<Integrations.HonorificService>(),
sp.GetRequiredService<ThemeRegistry>(),
sp.GetRequiredService<IPlatformUtil>()
));
services.AddSingleton(sp => new Ui.Components.StatusBar(
sp.GetRequiredService<ThemeRegistry>(),
@@ -1,30 +1,52 @@
using System.Diagnostics;
using System.Reflection;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using HellionChat.Branding;
using Microsoft.Extensions.Logging;
using HellionChat.Integrations;
using HellionChat.Resources;
using HellionChat.Themes;
using HellionChat.Util;
namespace HellionChat.Ui.Components.Settings.Tabs;
internal sealed class AboutTab
{
private readonly FontManager _fonts;
private readonly ILogger<AboutTab> _logger;
private readonly Plugin _plugin;
private readonly HonorificService _honorific;
private readonly ThemeRegistry _themes;
private readonly IPlatformUtil _platformUtil;
public AboutTab(FontManager fonts, ILogger<AboutTab> logger)
// SelfTest observable — the status key the real render path resolved.
internal string? LastHonorificStatusKey { get; private set; }
public AboutTab(
FontManager fonts,
Plugin plugin,
HonorificService honorific,
ThemeRegistry themes,
IPlatformUtil platformUtil
)
{
_fonts = fonts;
_logger = logger;
_plugin = plugin;
_honorific = honorific;
_themes = themes;
_platformUtil = platformUtil;
}
public void Draw()
{
// Reset the SelfTest observable each frame so a stale value from a prior
// real render can never let the integrations-status SelfTest pass falsely.
LastHonorificStatusKey = null;
DrawPluginInfo();
DrawSectionHeader("Brand");
DrawBrand();
DrawSectionHeader("Links");
DrawLinks();
DrawSectionHeader("Integrations");
DrawIntegrations();
DrawSectionHeader("Credits");
DrawCredits();
DrawSectionHeader("License");
@@ -74,24 +96,152 @@ internal sealed class AboutTab
DrawLinkButton("Custom repo manifest", BrandingLinks.HellionChatCustomRepoManifest);
}
// URLs in v1.7.0 are exclusively hardcoded BrandingLinks.* constants —
// Process.Start with UseShellExecute=true is safe under that constraint.
// If a future cycle ever feeds user-supplied URLs here, add an https/http
// allow-list filter via Uri.TryCreate before Process.Start; without it
// UseShellExecute would happily launch file:// or shell-protocol handlers.
private void DrawIntegrations()
{
ImGui.TextWrapped(HellionStrings.Settings_Integrations_Intro);
ImGui.Spacing();
ImGui.TextUnformatted(HellionStrings.Settings_Integrations_Honorific_SectionHeader);
DrawHonorificStatus();
DrawToggle(
HellionStrings.Settings_Integrations_Honorific_Toggle,
() => Plugin.Config.ShowHonorificTitleInHeader,
v => Plugin.Config.ShowHonorificTitleInHeader = v
);
ImGui.TextDisabled(HellionStrings.Settings_Integrations_Honorific_ToggleHint);
DrawLinkButton(
HellionStrings.Settings_Integrations_Honorific_LinkRepo,
IntegrationLinks.HonorificRepo
);
DrawLinkButton(
HellionStrings.Settings_Integrations_Honorific_LinkAuthor,
IntegrationLinks.HonorificAuthor
);
DrawComingSoon();
DrawGotAnIdea();
}
private void DrawHonorificStatus()
{
var kind = HonorificStatus.Resolve(_honorific.IsAvailable, _honorific.DetectedApiVersion);
LastHonorificStatusKey = kind.ToString();
var colors = _themes.Active.Colors;
// Null-safety via the `is { } v` pattern, never `.Value` raw (spec SEC-2):
// the version is bound only on the arms that have it; the impossible
// Detected/Incompatible-without-version state falls through to default.
switch (kind)
{
case HonorificStatusKind.Detected when _honorific.DetectedApiVersion is { } v:
DrawStatusGlyph('●', colors.StatusSuccess);
ImGui.SameLine();
ImGui.TextUnformatted(
string.Format(
HellionStrings.Settings_Integrations_Honorific_Status_Detected,
v.Major,
v.Minor
)
);
break;
case HonorificStatusKind.Incompatible when _honorific.DetectedApiVersion is { } iv:
DrawStatusGlyph('⚠', colors.StatusWarning);
ImGui.SameLine();
ImGui.TextUnformatted(
string.Format(
HellionStrings.Settings_Integrations_Honorific_Status_Incompatible,
HonorificService.ExpectedApiMajor,
iv.Major,
iv.Minor
)
);
break;
default:
DrawStatusGlyph('○', colors.TextMuted);
ImGui.SameLine();
ImGui.TextUnformatted(
HellionStrings.Settings_Integrations_Honorific_Status_NotInstalled
);
break;
}
}
private static void DrawStatusGlyph(char glyph, uint rgba)
{
ImGui.PushStyleColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(rgba));
ImGui.TextUnformatted(glyph.ToString());
ImGui.PopStyleColor();
}
private void DrawComingSoon()
{
ImGui.Spacing();
ImGui.TextUnformatted(HellionStrings.Settings_Integrations_ComingSoon_SectionHeader);
ImGui.TextDisabled(HellionStrings.Settings_Integrations_ComingSoon_Intro);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Title,
HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_Notifications_Title,
HellionStrings.Settings_Integrations_ComingSoon_Notifications_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Title,
HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Title,
HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Description
);
DrawComingSoonItem(
HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Title,
HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Description
);
}
private void DrawComingSoonItem(string title, string description)
{
using (_fonts.FontAwesome.Push())
{
ImGui.TextDisabled(FontAwesomeIcon.Hourglass.ToIconString());
}
ImGui.SameLine();
ImGui.TextUnformatted(title);
ImGui.TextDisabled(description);
}
private void DrawGotAnIdea()
{
ImGui.Spacing();
ImGui.TextUnformatted(HellionStrings.Settings_Integrations_GotAnIdea_SectionHeader);
ImGui.TextWrapped(HellionStrings.Settings_Integrations_GotAnIdea_Body);
if (ImGui.Button(HellionStrings.Settings_Integrations_GotAnIdea_LinkLabel))
{
_platformUtil.OpenLink(BrandingLinks.HellionForgeDiscordInvite);
}
}
private void DrawToggle(string label, Func<bool> get, Action<bool> set)
{
var current = get();
if (ImGui.Checkbox(label, ref current))
{
set(current);
_plugin.SaveConfig();
}
}
// URLs are exclusively hardcoded BrandingLinks/IntegrationLinks constants,
// validated to http/https at module-init. OpenLink centralises the browser
// open on an off-draw thread (it internally uses the same ShellExecute, so
// this is a consistency cleanup, not a security change). The standalone Copy
// button stays as the clipboard path.
private void DrawLinkButton(string label, string url)
{
if (ImGui.Button(label))
{
try
{
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not open {Url}, copying to clipboard instead", url);
ImGui.SetClipboardText(url);
}
_platformUtil.OpenLink(url);
}
ImGui.SameLine();
if (ImGui.SmallButton($"Copy##{url}"))