Add first-run wizard with three privacy profiles
Fresh installs now open a setup window on first plugin load that asks the user to pick one of three starting profiles. Existing ChatTwo users keep skipping the wizard because the v6→v7 migration sets Configuration.FirstRunCompleted = true on the same pass that seeds the Privacy-First defaults — they already saw the migration notification and can reopen the wizard from the Privacy tab if they want to choose differently. The three profiles map to concrete configuration sets: Privacy-First (recommended): own-conversation whitelist (30 channels), retention enabled with the spec defaults (Tells 365 days, own-conversation channels 90, fallback 30). Casual: Privacy-First plus public chat (Say/Shout/Yell, both emote types, Novice Network) with a 1-day retention window so RP players can scroll back the last scene without keeping third-party speech forever. Full History: filter off, retention off, GDPR warning shown inline. Behaves like upstream Chat 2. The wizard window is non-modal but covers a wide layout (three side-by-side cards) and closing it without picking anything is treated as accepting whatever defaults are already in place. The Privacy tab gains a "show wizard again" button at the top so the choice is reversible.
This commit is contained in:
@@ -62,6 +62,10 @@ public class Configuration : IPluginConfiguration
|
||||
public Dictionary<ChatType, int> RetentionPerChannelDays = [];
|
||||
public DateTimeOffset RetentionLastRunAt = DateTimeOffset.MinValue;
|
||||
|
||||
// Hellion Chat first-run wizard — opens once on a fresh install. Existing
|
||||
// ChatTwo users skip it because the v6→v7 migration sets the flag.
|
||||
public bool FirstRunCompleted;
|
||||
|
||||
public int GetRetentionDays(ChatType type)
|
||||
{
|
||||
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
|
||||
@@ -238,6 +242,8 @@ public class Configuration : IPluginConfiguration
|
||||
RetentionDefaultDays = other.RetentionDefaultDays;
|
||||
RetentionPerChannelDays = other.RetentionPerChannelDays.ToDictionary(p => p.Key, p => p.Value);
|
||||
RetentionLastRunAt = other.RetentionLastRunAt;
|
||||
|
||||
FirstRunCompleted = other.FirstRunCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
public InputPreview InputPreview { get; }
|
||||
public CommandHelpWindow CommandHelpWindow { get; }
|
||||
public SeStringDebugger SeStringDebugger { get; }
|
||||
public FirstRunWizard FirstRunWizard { get; }
|
||||
public DebuggerWindow DebuggerWindow { get; }
|
||||
|
||||
internal Commands Commands { get; }
|
||||
@@ -124,6 +125,11 @@ public sealed class Plugin : IDalamudPlugin
|
||||
Config.PrivacyFilterEnabled = true;
|
||||
Config.PrivacyPersistChannels = [..Privacy.PrivacyDefaults.PrivacyFirstWhitelist];
|
||||
Config.PrivacyPersistUnknownChannels = false;
|
||||
// Existing ChatTwo users skip the first-run wizard — the
|
||||
// migration toast already explains what changed and they
|
||||
// can reopen the wizard from Settings → Privacy if they
|
||||
// want to pick a different profile.
|
||||
Config.FirstRunCompleted = true;
|
||||
Config.Version = 7;
|
||||
SaveConfig();
|
||||
|
||||
@@ -168,6 +174,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
CommandHelpWindow = new CommandHelpWindow(ChatLogWindow);
|
||||
SeStringDebugger = new SeStringDebugger(this);
|
||||
DebuggerWindow = new DebuggerWindow(this);
|
||||
FirstRunWizard = new FirstRunWizard(this);
|
||||
|
||||
WindowSystem.AddWindow(ChatLogWindow);
|
||||
WindowSystem.AddWindow(SettingsWindow);
|
||||
@@ -176,6 +183,12 @@ public sealed class Plugin : IDalamudPlugin
|
||||
WindowSystem.AddWindow(CommandHelpWindow);
|
||||
WindowSystem.AddWindow(SeStringDebugger);
|
||||
WindowSystem.AddWindow(DebuggerWindow);
|
||||
WindowSystem.AddWindow(FirstRunWizard);
|
||||
|
||||
// Open the wizard on a fresh install. Existing ChatTwo users have
|
||||
// FirstRunCompleted set to true by the v6→v7 migration above.
|
||||
if (!Config.FirstRunCompleted)
|
||||
FirstRunWizard.IsOpen = true;
|
||||
|
||||
FontManager.BuildFonts();
|
||||
|
||||
|
||||
@@ -84,4 +84,28 @@ internal static class PrivacyDefaults
|
||||
[ChatType.ExtraChatLinkshell7] = 90,
|
||||
[ChatType.ExtraChatLinkshell8] = 90,
|
||||
};
|
||||
|
||||
// Casual profile = Privacy-First plus public chat (Say/Shout/Yell, both
|
||||
// emote types, Novice Network), kept for a short 24-hour window so the
|
||||
// last RP scene or shout trade is still searchable but third-party data
|
||||
// doesn't accumulate forever.
|
||||
internal static readonly IReadOnlySet<ChatType> CasualWhitelist = new HashSet<ChatType>(PrivacyFirstWhitelist)
|
||||
{
|
||||
ChatType.Say,
|
||||
ChatType.Shout,
|
||||
ChatType.Yell,
|
||||
ChatType.CustomEmote,
|
||||
ChatType.StandardEmote,
|
||||
ChatType.NoviceNetwork,
|
||||
};
|
||||
|
||||
internal static readonly IReadOnlyDictionary<ChatType, int> CasualRetentionOverrides = new Dictionary<ChatType, int>
|
||||
{
|
||||
[ChatType.Say] = 1,
|
||||
[ChatType.Shout] = 1,
|
||||
[ChatType.Yell] = 1,
|
||||
[ChatType.CustomEmote] = 1,
|
||||
[ChatType.StandardEmote] = 1,
|
||||
[ChatType.NoviceNetwork] = 1,
|
||||
};
|
||||
}
|
||||
|
||||
+14
@@ -99,4 +99,18 @@ internal class HellionStrings
|
||||
|
||||
internal static string Migration_Notification_Title => Get(nameof(Migration_Notification_Title));
|
||||
internal static string Migration_Notification_Content => Get(nameof(Migration_Notification_Content));
|
||||
|
||||
internal static string Wizard_Title => Get(nameof(Wizard_Title));
|
||||
internal static string Wizard_Intro => Get(nameof(Wizard_Intro));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Heading => Get(nameof(Wizard_Profile_PrivacyFirst_Heading));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Description => Get(nameof(Wizard_Profile_PrivacyFirst_Description));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Apply => Get(nameof(Wizard_Profile_PrivacyFirst_Apply));
|
||||
internal static string Wizard_Profile_Casual_Heading => Get(nameof(Wizard_Profile_Casual_Heading));
|
||||
internal static string Wizard_Profile_Casual_Description => Get(nameof(Wizard_Profile_Casual_Description));
|
||||
internal static string Wizard_Profile_Casual_Apply => Get(nameof(Wizard_Profile_Casual_Apply));
|
||||
internal static string Wizard_Profile_FullHistory_Heading => Get(nameof(Wizard_Profile_FullHistory_Heading));
|
||||
internal static string Wizard_Profile_FullHistory_Description => Get(nameof(Wizard_Profile_FullHistory_Description));
|
||||
internal static string Wizard_Profile_FullHistory_GdprWarning => Get(nameof(Wizard_Profile_FullHistory_GdprWarning));
|
||||
internal static string Wizard_Profile_FullHistory_Apply => Get(nameof(Wizard_Profile_FullHistory_Apply));
|
||||
internal static string Wizard_Reopen_Button => Get(nameof(Wizard_Reopen_Button));
|
||||
}
|
||||
|
||||
@@ -177,4 +177,43 @@
|
||||
<data name="Migration_Notification_Content" xml:space="preserve">
|
||||
<value>Datenschutz-Filter ist standardmäßig aktiviert. Einstellungen → Datenschutz zum Anpassen.</value>
|
||||
</data>
|
||||
<data name="Wizard_Title" xml:space="preserve">
|
||||
<value>Hellion Chat — Willkommen</value>
|
||||
</data>
|
||||
<data name="Wizard_Intro" xml:space="preserve">
|
||||
<value>Wähle ein Start-Profil. Du kannst später alles unter Einstellungen → Datenschutz anpassen.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
|
||||
<value>Datensparsamkeit (empfohlen)</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
|
||||
<value>Es werden nur deine eigenen Konversationen gespeichert: Tells, Gruppe, FC, Linkshells, Cross-World-Linkshells, Allianz und ExtraChat. Öffentlicher Chat, NPC-Dialoge und System-Spam werden auf der Storage-Ebene verworfen. Aufbewahrung nach Spec-Defaults (Tells 365 Tage, eigene Konversations-Kanäle 90 Tage).</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_PrivacyFirst_Apply" xml:space="preserve">
|
||||
<value>Datensparsamkeit übernehmen</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Casual_Heading" xml:space="preserve">
|
||||
<value>Locker</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Casual_Description" xml:space="preserve">
|
||||
<value>Datensparsamkeit plus ein 24-Stunden-Fenster für öffentlichen Chat (Sagen, Schreien, Rufen, beide Emote-Typen, Anfänger-Netzwerk). Für RP-Spieler, die die letzte Szene nochmal nachlesen wollen, ohne öffentlichen Chat ewig zu behalten.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Casual_Apply" xml:space="preserve">
|
||||
<value>Locker übernehmen</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Heading" xml:space="preserve">
|
||||
<value>Volle Historie</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Description" xml:space="preserve">
|
||||
<value>Deaktiviert den Datenschutz-Filter komplett. Speichert alles außer Battle-Logs, wie das Original-Chat 2. Aufbewahrung ist AUS, die Historie wächst dauerhaft.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_GdprWarning" xml:space="preserve">
|
||||
<value>DSGVO-Hinweis: Wenn du Nachrichten Dritter (Sagen/Schreien/Rufen fremder Spieler, NPC-Dialoge mit Spielernamen usw.) zeitlich unbegrenzt speicherst, kann das die Ausnahme für rein persönliche oder familiäre Tätigkeiten (Art. 2 Abs. 2 Buchst. c) sprengen. Nutze dieses Profil nur, wenn du einen klaren Grund hast, das volle Archiv zu behalten.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Apply" xml:space="preserve">
|
||||
<value>Volle Historie übernehmen</value>
|
||||
</data>
|
||||
<data name="Wizard_Reopen_Button" xml:space="preserve">
|
||||
<value>Wizard erneut zeigen</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -177,4 +177,43 @@
|
||||
<data name="Migration_Notification_Content" xml:space="preserve">
|
||||
<value>Privacy filter activated by default. Settings → Privacy to adjust.</value>
|
||||
</data>
|
||||
<data name="Wizard_Title" xml:space="preserve">
|
||||
<value>Hellion Chat — Welcome</value>
|
||||
</data>
|
||||
<data name="Wizard_Intro" xml:space="preserve">
|
||||
<value>Pick a starting profile. You can change anything later under Settings → Privacy.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
|
||||
<value>Privacy-First (recommended)</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
|
||||
<value>Only your own conversations are stored: Tells, Party, FC, Linkshells, Cross-World Linkshells, Alliance and ExtraChat. Public chat, NPC dialogue and system spam are dropped at the storage layer. Retention follows the spec defaults (Tells 365 days, own-conversation channels 90 days).</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_PrivacyFirst_Apply" xml:space="preserve">
|
||||
<value>Use Privacy-First</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Casual_Heading" xml:space="preserve">
|
||||
<value>Casual</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Casual_Description" xml:space="preserve">
|
||||
<value>Privacy-First plus a 24-hour window for public chat (Say, Shout, Yell, both emote types, Novice Network). For RP players who want to look up the last scene without keeping public chat forever.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Casual_Apply" xml:space="preserve">
|
||||
<value>Use Casual</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Heading" xml:space="preserve">
|
||||
<value>Full History</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Description" xml:space="preserve">
|
||||
<value>Disables the privacy filter entirely. Stores everything except battle logs, just like upstream Chat 2. Retention is OFF, history grows forever.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_GdprWarning" xml:space="preserve">
|
||||
<value>GDPR notice: storing third-party messages (Say/Shout/Yell of strangers, NPC dialogue with player names, etc.) for an unlimited time may exceed the personal/household exemption (Art. 2(2)(c)). Use this profile only if you have a clear reason to keep the full archive.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Apply" xml:space="preserve">
|
||||
<value>Use Full History</value>
|
||||
</data>
|
||||
<data name="Wizard_Reopen_Button" xml:space="preserve">
|
||||
<value>Show wizard again</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Privacy;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
public sealed class FirstRunWizard : Window
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
|
||||
internal FirstRunWizard(Plugin plugin) : base($"{HellionStrings.Wizard_Title}###hellion-firstrun")
|
||||
{
|
||||
Plugin = plugin;
|
||||
|
||||
Flags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking;
|
||||
SizeCondition = ImGuiCond.Appearing;
|
||||
Size = new Vector2(900, 560);
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
MinimumSize = new Vector2(720, 480),
|
||||
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
// Closing the wizard without picking anything = the user accepts
|
||||
// whatever defaults are already in place. Mark as complete so we
|
||||
// don't pester them again on the next launch.
|
||||
if (!Plugin.Config.FirstRunCompleted)
|
||||
{
|
||||
Plugin.Config.FirstRunCompleted = true;
|
||||
Plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
ImGui.TextWrapped(HellionStrings.Wizard_Intro);
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var cardWidth = (avail.X - ImGui.GetStyle().ItemSpacing.X * 2) / 3f;
|
||||
var cardHeight = avail.Y - ImGui.GetTextLineHeightWithSpacing();
|
||||
|
||||
DrawCard("privacy-first", cardWidth, cardHeight,
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Heading,
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Description,
|
||||
null,
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Apply,
|
||||
ApplyPrivacyFirst);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
DrawCard("casual", cardWidth, cardHeight,
|
||||
HellionStrings.Wizard_Profile_Casual_Heading,
|
||||
HellionStrings.Wizard_Profile_Casual_Description,
|
||||
null,
|
||||
HellionStrings.Wizard_Profile_Casual_Apply,
|
||||
ApplyCasual);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
DrawCard("full-history", cardWidth, cardHeight,
|
||||
HellionStrings.Wizard_Profile_FullHistory_Heading,
|
||||
HellionStrings.Wizard_Profile_FullHistory_Description,
|
||||
HellionStrings.Wizard_Profile_FullHistory_GdprWarning,
|
||||
HellionStrings.Wizard_Profile_FullHistory_Apply,
|
||||
ApplyFullHistory);
|
||||
}
|
||||
|
||||
private void DrawCard(string id, float width, float height, string heading, string description, string? warning, string buttonLabel, Action onApply)
|
||||
{
|
||||
using var child = ImRaii.Child($"##wizard-card-{id}", new Vector2(width, height), true);
|
||||
if (!child.Success)
|
||||
return;
|
||||
|
||||
ImGui.TextUnformatted(heading);
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextWrapped(description);
|
||||
|
||||
if (warning is not null)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.WarningText(warning);
|
||||
}
|
||||
|
||||
// Push the button to the bottom of the card.
|
||||
var lineHeight = ImGui.GetFrameHeightWithSpacing();
|
||||
var remaining = ImGui.GetContentRegionAvail().Y - lineHeight;
|
||||
if (remaining > 0)
|
||||
ImGui.Dummy(new Vector2(0, remaining));
|
||||
|
||||
if (ImGui.Button($"{buttonLabel}##{id}", new Vector2(-1, 0)))
|
||||
{
|
||||
onApply();
|
||||
Plugin.Config.FirstRunCompleted = true;
|
||||
Plugin.SaveConfig();
|
||||
IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyPrivacyFirst()
|
||||
{
|
||||
Plugin.Config.PrivacyFilterEnabled = true;
|
||||
Plugin.Config.PrivacyPersistChannels = [..PrivacyDefaults.PrivacyFirstWhitelist];
|
||||
Plugin.Config.PrivacyPersistUnknownChannels = false;
|
||||
|
||||
Plugin.Config.RetentionEnabled = true;
|
||||
Plugin.Config.RetentionDefaultDays = 30;
|
||||
Plugin.Config.RetentionPerChannelDays =
|
||||
PrivacyDefaults.DefaultRetentionDays.ToDictionary(p => p.Key, p => p.Value);
|
||||
}
|
||||
|
||||
private void ApplyCasual()
|
||||
{
|
||||
Plugin.Config.PrivacyFilterEnabled = true;
|
||||
Plugin.Config.PrivacyPersistChannels = [..PrivacyDefaults.CasualWhitelist];
|
||||
Plugin.Config.PrivacyPersistUnknownChannels = false;
|
||||
|
||||
Plugin.Config.RetentionEnabled = true;
|
||||
Plugin.Config.RetentionDefaultDays = 30;
|
||||
var policy = PrivacyDefaults.DefaultRetentionDays.ToDictionary(p => p.Key, p => p.Value);
|
||||
foreach (var (type, days) in PrivacyDefaults.CasualRetentionOverrides)
|
||||
policy[type] = days;
|
||||
Plugin.Config.RetentionPerChannelDays = policy;
|
||||
}
|
||||
|
||||
private void ApplyFullHistory()
|
||||
{
|
||||
// Full history = upstream Chat 2 behavior. Filter off, retention off,
|
||||
// everything (except battle messages, which Chat 2 itself controls)
|
||||
// accumulates indefinitely.
|
||||
Plugin.Config.PrivacyFilterEnabled = false;
|
||||
Plugin.Config.PrivacyPersistUnknownChannels = true;
|
||||
|
||||
Plugin.Config.RetentionEnabled = false;
|
||||
Plugin.Config.RetentionPerChannelDays.Clear();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,10 @@ internal sealed class Privacy : ISettingsTab
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
if (ImGui.Button(HellionStrings.Wizard_Reopen_Button))
|
||||
Plugin.FirstRunWizard.IsOpen = true;
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.PrivacyFilterEnabled,
|
||||
HellionStrings.Privacy_FilterEnabled_Name,
|
||||
|
||||
Reference in New Issue
Block a user