mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
launcher: welcome screen + guided setup wizard
First launch went straight to a Host/Join card picker with no framing. Replaced with a two-phase onboarding: WelcomeView — branded intro with the wordmark, three feature bullets (2 min setup, free, works with regular Rec Room), and one big "Get started" button. Dismissed via AppState.WelcomeSeen. SetupView — multi-step wizard with a STEP X OF N indicator: 1. Host vs Join card picker (self-advances on click) 2. Rec Room install (shows auto-detected path with a green "we found it" banner; Pick... overrides) 3. (Host only) Server name + WHO CAN JOIN radios 4. (Host only) Photon AppIds — "Open setup walkthrough" launches the existing PhotonWizard, or paste fields below for advanced users; live status line confirms when AppIds are set 5. Done card with Finish button Join mode hops 2 -> done since it doesn't need server config or Photon. AppState.SetupComplete gates entry; future launches skip the wizard and route directly to HostView / JoinView. Existing state files get auto-migrated on load (Mode set + RecRoomPath populated = WelcomeSeen + SetupComplete backfilled to true, then flushed). Settings -> "Re-run setup" clears the flag for users who want to walk through again.
This commit is contained in:
@@ -52,6 +52,19 @@ public sealed class AppState
|
||||
[JsonPropertyName("serverName")]
|
||||
public string ServerName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>True once the user has dismissed the branded welcome
|
||||
/// screen. Subsequent launches skip straight to the setup wizard
|
||||
/// (or the main host/join view if setup is also done).</summary>
|
||||
[JsonPropertyName("welcomeSeen")]
|
||||
public bool WelcomeSeen { get; set; }
|
||||
|
||||
/// <summary>True once the user has finished the multi-step setup
|
||||
/// wizard. Future launches skip the wizard and open the
|
||||
/// host/join view directly. Settings -> "Re-run setup" can clear
|
||||
/// this if the user wants to walk through again.</summary>
|
||||
[JsonPropertyName("setupComplete")]
|
||||
public bool SetupComplete { get; set; }
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
@@ -65,7 +78,26 @@ public sealed class AppState
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(AppPaths.StateFile);
|
||||
return JsonSerializer.Deserialize<AppState>(json, JsonOptions) ?? new AppState();
|
||||
var state = JsonSerializer.Deserialize<AppState>(json, JsonOptions) ?? new AppState();
|
||||
|
||||
// Backfill the welcome/setup flags for users upgrading from a
|
||||
// pre-wizard build. If they already had a working host setup
|
||||
// they've effectively passed those screens — don't drag them
|
||||
// back through the wizard on first launch of the new build.
|
||||
var migrated = false;
|
||||
if (!state.WelcomeSeen && state.Mode != AppMode.Unset)
|
||||
{ state.WelcomeSeen = true; migrated = true; }
|
||||
if (!state.SetupComplete && state.Mode != AppMode.Unset &&
|
||||
!string.IsNullOrEmpty(state.RecRoomPath) &&
|
||||
(state.Mode == AppMode.Join || !string.IsNullOrEmpty(state.PhotonAppId)))
|
||||
{ state.SetupComplete = true; migrated = true; }
|
||||
// Flush the migration so subsequent launches read the
|
||||
// backfilled values directly from disk — keeps the file as
|
||||
// the authoritative source instead of needing to re-derive
|
||||
// them every load.
|
||||
if (migrated) { try { state.Save(); } catch { } }
|
||||
|
||||
return state;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
+268
-71
@@ -27,85 +27,279 @@
|
||||
<Grid Margin="36,28">
|
||||
<!-- View slots — only one Visible at a time; code-behind toggles. -->
|
||||
|
||||
<!-- ── First-run wizard ───────────────────────────────────── -->
|
||||
<StackPanel x:Name="FirstRunView" Visibility="Visible"
|
||||
VerticalAlignment="Center">
|
||||
<StackPanel HorizontalAlignment="Center" Margin="0,0,0,40">
|
||||
<TextBlock Text="DorkNet"
|
||||
FontSize="56" FontWeight="Bold"
|
||||
Foreground="{StaticResource WordmarkBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Segoe UI Variable, Segoe UI" />
|
||||
<TextBlock Text="Play together on your own Rec Room server"
|
||||
Foreground="{StaticResource InkSoft}"
|
||||
FontSize="16"
|
||||
HorizontalAlignment="Center" Margin="0,6,0,0" />
|
||||
</StackPanel>
|
||||
<!-- ── Welcome screen ─────────────────────────────────────
|
||||
Only shown on truly first launch. After "Get started"
|
||||
the user enters the setup wizard; subsequent launches
|
||||
skip straight to Setup or HostView/JoinView. -->
|
||||
<StackPanel x:Name="WelcomeView" Visibility="Collapsed"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
MaxWidth="520">
|
||||
<TextBlock Text="DorkNet"
|
||||
FontSize="72" FontWeight="Bold"
|
||||
Foreground="{StaticResource WordmarkBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Segoe UI Variable, Segoe UI" />
|
||||
<TextBlock Text="Play together on your own Rec Room server."
|
||||
Foreground="{StaticResource Ink}"
|
||||
FontSize="18"
|
||||
HorizontalAlignment="Center" Margin="0,8,0,32"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<Grid Margin="40,0" MaxWidth="780" HorizontalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="20" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0" Style="{StaticResource ChoiceCardBtn}"
|
||||
Background="{StaticResource CardBgHost}"
|
||||
Click="OnPickHost">
|
||||
<StackPanel>
|
||||
<Border CornerRadius="14" Width="56" Height="56"
|
||||
Background="#3D2B57" Margin="0,0,0,18"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="🛠" FontSize="32"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<TextBlock Text="Host a server" FontSize="22"
|
||||
FontWeight="Bold" Margin="0,0,0,10" />
|
||||
<TextBlock Foreground="{StaticResource InkSoft}"
|
||||
FontSize="13" TextWrapping="Wrap" LineHeight="20"
|
||||
Text="Run a server for you and your friends. Pick a Rec Room version, the launcher downloads the matching server, opens a tunnel, and hands you a shareable join code." />
|
||||
<Border Margin="0,18,0,0" CornerRadius="8"
|
||||
Background="{StaticResource Brand}" Padding="14,8"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="Get started →" Foreground="White"
|
||||
FontWeight="SemiBold" FontSize="13" />
|
||||
</Border>
|
||||
<Border Style="{StaticResource Panel}"
|
||||
Background="{StaticResource CardBg}" Padding="28,24"
|
||||
Margin="0,0,0,28">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,14">
|
||||
<TextBlock Text="✨" FontSize="20" Margin="0,0,12,0" />
|
||||
<TextBlock Foreground="{StaticResource Ink}" FontSize="14"
|
||||
VerticalAlignment="Center" TextWrapping="Wrap"
|
||||
Text="Set up in about two minutes" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Grid.Column="2" Style="{StaticResource ChoiceCardBtn}"
|
||||
Background="{StaticResource CardBgJoin}"
|
||||
Click="OnPickJoin">
|
||||
<StackPanel>
|
||||
<Border CornerRadius="14" Width="56" Height="56"
|
||||
Background="#1C3C4A" Margin="0,0,0,18"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="🎮" FontSize="32"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<TextBlock Text="Join a friend" FontSize="22"
|
||||
FontWeight="Bold" Margin="0,0,0,10" />
|
||||
<TextBlock Foreground="{StaticResource InkSoft}"
|
||||
FontSize="13" TextWrapping="Wrap" LineHeight="20"
|
||||
Text="Paste a join code from your host, point at your Rec Room install. The launcher fetches the matching patcher and gets you connected." />
|
||||
<Border Margin="0,18,0,0" CornerRadius="8"
|
||||
Background="{StaticResource Teal}" Padding="14,8"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="Get started →" Foreground="#0E0A1F"
|
||||
FontWeight="SemiBold" FontSize="13" />
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,14">
|
||||
<TextBlock Text="💸" FontSize="20" Margin="0,0,12,0" />
|
||||
<TextBlock Foreground="{StaticResource Ink}" FontSize="14"
|
||||
VerticalAlignment="Center" TextWrapping="Wrap"
|
||||
Text="Host for free — no credit card, no subscription" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="🎮" FontSize="20" Margin="0,0,12,0" />
|
||||
<TextBlock Foreground="{StaticResource Ink}" FontSize="14"
|
||||
VerticalAlignment="Center" TextWrapping="Wrap"
|
||||
Text="Works with the regular Rec Room game — no rebuild needed" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="You can switch modes anytime under Settings."
|
||||
<Button Style="{StaticResource PrimaryBtn}"
|
||||
Content="Get started →"
|
||||
Click="OnWelcomeContinue"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="34,14" FontSize="15" />
|
||||
|
||||
<TextBlock Text="Not affiliated with Rec Room Inc. or Against Gravity."
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{StaticResource InkFaint}"
|
||||
FontSize="12" Margin="0,32,0,0" />
|
||||
FontSize="11" Margin="0,28,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- ── Setup wizard ────────────────────────────────────────
|
||||
Shown on first launch after Welcome. Walks through mode
|
||||
pick, install location, server config + Photon. Once
|
||||
the user finishes the last step, AppState.SetupComplete
|
||||
flips and future launches go straight to HostView /
|
||||
JoinView (which still hold the same fields for tweaks). -->
|
||||
<Grid x:Name="SetupView" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="0,0,0,24">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="DorkNet" FontSize="22" FontWeight="Bold"
|
||||
Foreground="{StaticResource WordmarkBrush}" />
|
||||
<Border Style="{StaticResource StatusChip}" Margin="14,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock x:Name="SetupStepIndicator"
|
||||
Text="STEP 1" FontSize="10"
|
||||
FontWeight="Bold" Foreground="{StaticResource Brand}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto"
|
||||
Padding="0,0,8,0">
|
||||
<Grid>
|
||||
<!-- Setup step 1: Host or Join? -->
|
||||
<StackPanel x:Name="SetupStep1" Visibility="Visible">
|
||||
<TextBlock Text="What do you want to do?" FontSize="26"
|
||||
FontWeight="Bold" Margin="0,0,0,8" />
|
||||
<TextBlock Text="Pick a mode. You can change this later under Settings."
|
||||
Foreground="{StaticResource InkSoft}" FontSize="14"
|
||||
Margin="0,0,0,28" />
|
||||
|
||||
<Grid MaxWidth="780" HorizontalAlignment="Left">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="20" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0" Style="{StaticResource ChoiceCardBtn}"
|
||||
Background="{StaticResource CardBgHost}"
|
||||
Click="OnSetupPickHost">
|
||||
<StackPanel>
|
||||
<Border CornerRadius="14" Width="56" Height="56"
|
||||
Background="#3D2B57" Margin="0,0,0,18"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="🛠" FontSize="32"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<TextBlock Text="Host a server" FontSize="22"
|
||||
FontWeight="Bold" Margin="0,0,0,10" />
|
||||
<TextBlock Foreground="{StaticResource InkSoft}"
|
||||
FontSize="13" TextWrapping="Wrap" LineHeight="20"
|
||||
Text="Run a server for you and your friends. The launcher handles the tunnel, gives you a shareable join code." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Grid.Column="2" Style="{StaticResource ChoiceCardBtn}"
|
||||
Background="{StaticResource CardBgJoin}"
|
||||
Click="OnSetupPickJoin">
|
||||
<StackPanel>
|
||||
<Border CornerRadius="14" Width="56" Height="56"
|
||||
Background="#1C3C4A" Margin="0,0,0,18"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock Text="🎮" FontSize="32"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<TextBlock Text="Join a friend" FontSize="22"
|
||||
FontWeight="Bold" Margin="0,0,0,10" />
|
||||
<TextBlock Foreground="{StaticResource InkSoft}"
|
||||
FontSize="13" TextWrapping="Wrap" LineHeight="20"
|
||||
Text="You have a join code from someone. Paste it and the launcher fetches the matching patcher." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Setup step 2: Rec Room install -->
|
||||
<StackPanel x:Name="SetupStep2" Visibility="Collapsed">
|
||||
<TextBlock Text="Where's Rec Room installed?" FontSize="26"
|
||||
FontWeight="Bold" Margin="0,0,0,8" />
|
||||
<TextBlock Text="The launcher needs to find your Rec Room install folder. Pick the *_Data folder inside the install (e.g. Recroom_Release_Data)."
|
||||
Foreground="{StaticResource InkSoft}" FontSize="14"
|
||||
TextWrapping="Wrap" Margin="0,0,0,24" />
|
||||
|
||||
<Border x:Name="SetupDetectedBanner" Visibility="Collapsed"
|
||||
Style="{StaticResource Panel}"
|
||||
Background="{StaticResource CardBgJoin}"
|
||||
Margin="0,0,0,16">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<TextBlock Text="✓" FontSize="18"
|
||||
Foreground="{StaticResource Ok}"
|
||||
Margin="0,0,10,0" />
|
||||
<TextBlock Text="We found a Rec Room install"
|
||||
FontSize="15" FontWeight="SemiBold"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="SetupDetectedPath"
|
||||
Foreground="{StaticResource InkSoft}"
|
||||
FontSize="12"
|
||||
FontFamily="Cascadia Mono, Consolas"
|
||||
TextWrapping="Wrap" Margin="28,0,0,0" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<DockPanel Margin="0,0,0,12">
|
||||
<Button DockPanel.Dock="Right" Style="{StaticResource Btn}"
|
||||
Content="Pick…" Click="OnPickRecRoom" Margin="8,0,0,0" />
|
||||
<Border Background="{StaticResource BgDeeper}"
|
||||
BorderBrush="{StaticResource BorderSoft}" BorderThickness="1"
|
||||
CornerRadius="10" Padding="12,10">
|
||||
<TextBlock x:Name="SetupRecRoomPath"
|
||||
Text="(not picked)"
|
||||
Foreground="{StaticResource InkSoft}"
|
||||
TextTrimming="CharacterEllipsis" FontSize="13" />
|
||||
</Border>
|
||||
</DockPanel>
|
||||
<TextBlock Foreground="{StaticResource InkFaint}" FontSize="11"
|
||||
TextWrapping="Wrap"
|
||||
Text="Don't have Rec Room installed yet? Grab it from the Rec Room website (or wherever you usually get it). DorkNet works with any official install." />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Setup step 3 (HOST only): Server name + WHO CAN JOIN -->
|
||||
<StackPanel x:Name="SetupStep3" Visibility="Collapsed">
|
||||
<TextBlock Text="Set up your server" FontSize="26"
|
||||
FontWeight="Bold" Margin="0,0,0,8" />
|
||||
<TextBlock Text="Name your server so friends know what they're joining."
|
||||
Foreground="{StaticResource InkSoft}" FontSize="14"
|
||||
Margin="0,0,0,24" />
|
||||
|
||||
<TextBlock Style="{StaticResource FieldLabel}" Text="SERVER NAME" />
|
||||
<TextBox x:Name="SetupServerName" Margin="0,0,0,20"
|
||||
LostFocus="OnSetupServerNameChanged" />
|
||||
|
||||
<TextBlock Style="{StaticResource FieldLabel}" Text="WHO CAN JOIN?" />
|
||||
<StackPanel Margin="0,0,0,16">
|
||||
<RadioButton x:Name="SetupHostInternet" GroupName="SetupHostingMode"
|
||||
Content="Friends anywhere (internet via Cloudflare)"
|
||||
IsChecked="True" Margin="0,0,0,6"
|
||||
Checked="OnSetupHostingModeChanged"
|
||||
Foreground="{StaticResource Ink}" />
|
||||
<RadioButton x:Name="SetupHostLocal" GroupName="SetupHostingMode"
|
||||
Content="Same WiFi only (no internet needed)"
|
||||
Checked="OnSetupHostingModeChanged"
|
||||
Foreground="{StaticResource Ink}" />
|
||||
</StackPanel>
|
||||
<TextBlock Foreground="{StaticResource InkFaint}" FontSize="11"
|
||||
TextWrapping="Wrap"
|
||||
Text="Cloudflare mode works for friends anywhere. Same-WiFi mode is fastest but only works for people on your local network." />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Setup step 4 (HOST only): Photon AppIds -->
|
||||
<StackPanel x:Name="SetupStep4" Visibility="Collapsed">
|
||||
<TextBlock Text="Connect your Photon account" FontSize="26"
|
||||
FontWeight="Bold" Margin="0,0,0,8" />
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Foreground="{StaticResource InkSoft}" FontSize="14"
|
||||
Margin="0,0,0,24"
|
||||
Text="DorkNet needs your own Photon account (free, takes ~2 min) to handle in-room sync. Click the walkthrough below — it'll open the Photon website, tell you what to click, and bring you back here." />
|
||||
|
||||
<Button Style="{StaticResource PrimaryBtn}"
|
||||
Content="Open setup walkthrough"
|
||||
Click="OnOpenPhotonWizard"
|
||||
HorizontalAlignment="Left" Margin="0,0,0,20" />
|
||||
|
||||
<Border Style="{StaticResource Panel}"
|
||||
Background="{StaticResource BgDeeper}"
|
||||
Margin="0,0,0,12">
|
||||
<StackPanel>
|
||||
<TextBlock Foreground="{StaticResource InkSoft}" FontSize="12"
|
||||
Margin="0,0,0,8"
|
||||
Text="Already have AppIds? Paste them here." />
|
||||
<TextBlock Style="{StaticResource FieldLabel}" Text="REALTIME APPID" />
|
||||
<TextBox x:Name="SetupPhotonAppId" Margin="0,0,0,12"
|
||||
FontFamily="Cascadia Mono, Consolas" FontSize="12"
|
||||
LostFocus="OnSetupPhotonChanged" />
|
||||
<TextBlock Style="{StaticResource FieldLabel}" Text="VOICE APPID (OPTIONAL)" />
|
||||
<TextBox x:Name="SetupPhotonVoiceAppId" Margin="0,0,0,0"
|
||||
FontFamily="Cascadia Mono, Consolas" FontSize="12"
|
||||
LostFocus="OnSetupPhotonChanged" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<TextBlock x:Name="SetupPhotonStatus"
|
||||
Foreground="{StaticResource InkFaint}" FontSize="11"
|
||||
Text="Realtime AppId required before you can host." />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Setup done: short confirmation card -->
|
||||
<StackPanel x:Name="SetupStepDone" Visibility="Collapsed">
|
||||
<TextBlock Text="You're all set! 🎉" FontSize="26"
|
||||
FontWeight="Bold" Margin="0,0,0,12" />
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Foreground="{StaticResource Ink}" FontSize="14"
|
||||
LineHeight="22"
|
||||
Text="Setup's done. Hit Finish to open the main view — everything you just configured is saved, so you can hit Start hosting straight away." />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- Footer nav -->
|
||||
<Grid Grid.Row="2" Margin="0,20,0,0">
|
||||
<Button x:Name="SetupBackBtn" HorizontalAlignment="Left"
|
||||
Style="{StaticResource GhostBtn}"
|
||||
Content="← Back" Click="OnSetupBack" Visibility="Collapsed" />
|
||||
<Button x:Name="SetupNextBtn" HorizontalAlignment="Right"
|
||||
Style="{StaticResource PrimaryBtn}"
|
||||
Content="Next →" Click="OnSetupNext" Visibility="Collapsed" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- ── Host view ──────────────────────────────────────────── -->
|
||||
<Grid x:Name="HostView" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -450,6 +644,9 @@
|
||||
<Button Style="{StaticResource Btn}"
|
||||
Content="Join" Click="OnPickJoin"
|
||||
Margin="10,0,0,0" />
|
||||
<Button Style="{StaticResource GhostBtn}"
|
||||
Content="Re-run setup" Click="OnRerunSetup"
|
||||
Margin="10,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
+210
-14
@@ -82,10 +82,12 @@ public partial class MainWindow : Window
|
||||
// ── View routing ────────────────────────────────────────────────────
|
||||
private void ShowView(string name)
|
||||
{
|
||||
FirstRunView.Visibility = name == "first-run" ? Visibility.Visible : Visibility.Collapsed;
|
||||
WelcomeView.Visibility = name == "welcome" ? Visibility.Visible : Visibility.Collapsed;
|
||||
SetupView.Visibility = name == "setup" ? Visibility.Visible : Visibility.Collapsed;
|
||||
HostView.Visibility = name == "host" ? Visibility.Visible : Visibility.Collapsed;
|
||||
JoinView.Visibility = name == "join" ? Visibility.Visible : Visibility.Collapsed;
|
||||
SettingsView.Visibility = name == "settings" ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (name == "setup") RenderSetupStep();
|
||||
}
|
||||
|
||||
private string CurrentViewName()
|
||||
@@ -93,25 +95,42 @@ public partial class MainWindow : Window
|
||||
if (SettingsView.Visibility == Visibility.Visible) return "settings";
|
||||
if (HostView.Visibility == Visibility.Visible) return "host";
|
||||
if (JoinView.Visibility == Visibility.Visible) return "join";
|
||||
return "first-run";
|
||||
if (SetupView.Visibility == Visibility.Visible) return "setup";
|
||||
return "welcome";
|
||||
}
|
||||
|
||||
private string? _previousView;
|
||||
|
||||
/// <summary>Pick the right initial view based on persisted state.
|
||||
/// True first launch: welcome -> setup -> host/join. Subsequent
|
||||
/// launches skip the wizard since SetupComplete is true.</summary>
|
||||
private string PickRoutingTarget()
|
||||
{
|
||||
if (!_state.WelcomeSeen) return "welcome";
|
||||
if (_state.Mode == AppMode.Unset || !_state.SetupComplete) return "setup";
|
||||
return _state.Mode == AppMode.Join ? "join" : "host";
|
||||
}
|
||||
|
||||
private void ReflectStateInUi()
|
||||
{
|
||||
// Mirror persisted values into the host/join input fields so the
|
||||
// user doesn't re-type them every launch.
|
||||
// Mirror persisted values into the host/join + setup input fields
|
||||
// so the user doesn't re-type them every launch (or every step
|
||||
// back/forward in the wizard).
|
||||
var path = _state.RecRoomPath ?? "(not picked)";
|
||||
HostRecRoomPath.Text = path;
|
||||
JoinRecRoomPath.Text = path;
|
||||
SetupRecRoomPath.Text = path;
|
||||
|
||||
PhotonAppIdInput.Text = _state.PhotonAppId;
|
||||
PhotonVoiceAppIdInput.Text = _state.PhotonVoiceAppId;
|
||||
SetupPhotonAppId.Text = _state.PhotonAppId;
|
||||
SetupPhotonVoiceAppId.Text = _state.PhotonVoiceAppId;
|
||||
SelectComboBoxByTag(PhotonRegionSelect, _state.PhotonRegion);
|
||||
ServerNameInput.Text = _state.ServerName;
|
||||
// Hosting mode radio — reflect persisted choice; defaults to Internet
|
||||
// since that's the install-and-play case most users want.
|
||||
SetupServerName.Text = _state.ServerName;
|
||||
|
||||
// Hosting mode radios — reflect persisted choice on both the
|
||||
// main HostView and the setup wizard's matching step.
|
||||
if (HostModeLocal is not null && HostModeInternet is not null)
|
||||
{
|
||||
if (_state.HostingMode == HostingMode.LocalNetwork)
|
||||
@@ -119,6 +138,27 @@ public partial class MainWindow : Window
|
||||
else
|
||||
HostModeInternet.IsChecked = true;
|
||||
}
|
||||
if (SetupHostLocal is not null && SetupHostInternet is not null)
|
||||
{
|
||||
if (_state.HostingMode == HostingMode.LocalNetwork)
|
||||
SetupHostLocal.IsChecked = true;
|
||||
else
|
||||
SetupHostInternet.IsChecked = true;
|
||||
}
|
||||
|
||||
// Setup step 2 banner — surface the auto-detected install so the
|
||||
// user knows the launcher already did the work.
|
||||
if (!string.IsNullOrEmpty(_state.RecRoomPath))
|
||||
{
|
||||
SetupDetectedBanner.Visibility = Visibility.Visible;
|
||||
SetupDetectedPath.Text = _state.RecRoomPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupDetectedBanner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
UpdateSetupPhotonStatus();
|
||||
|
||||
CachePathsText.Text =
|
||||
$"State: {AppPaths.StateFile}\n" +
|
||||
@@ -127,13 +167,7 @@ public partial class MainWindow : Window
|
||||
$"Logs: {AppPaths.LogsRoot}";
|
||||
|
||||
RefreshJoinReadiness();
|
||||
|
||||
ShowView(_state.Mode switch
|
||||
{
|
||||
AppMode.Host => "host",
|
||||
AppMode.Join => "join",
|
||||
_ => "first-run",
|
||||
});
|
||||
ShowView(PickRoutingTarget());
|
||||
}
|
||||
|
||||
private async Task RefreshManifestAsync()
|
||||
@@ -172,7 +206,169 @@ public partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
// ── First-run wizard / mode picks ───────────────────────────────────
|
||||
// ── Welcome + setup wizard ──────────────────────────────────────────
|
||||
private int _setupStep = 1; // 1=mode, 2=install, 3=server, 4=photon, 5=done
|
||||
|
||||
private void OnWelcomeContinue(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_state.WelcomeSeen = true;
|
||||
_state.Save();
|
||||
_setupStep = 1;
|
||||
ShowView("setup");
|
||||
}
|
||||
|
||||
private void OnSetupPickHost(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_state.Mode = AppMode.Host;
|
||||
_state.Save();
|
||||
_setupStep = 2;
|
||||
RenderSetupStep();
|
||||
}
|
||||
|
||||
private void OnSetupPickJoin(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_state.Mode = AppMode.Join;
|
||||
_state.Save();
|
||||
// Join mode only needs the install path — Photon/server config
|
||||
// come from the host's join code, not the joiner.
|
||||
_setupStep = 2;
|
||||
RenderSetupStep();
|
||||
}
|
||||
|
||||
private void OnSetupBack(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_setupStep <= 1) return;
|
||||
_setupStep--;
|
||||
// For Join mode, skip the host-only steps (3 + 4) when walking back
|
||||
// — there's nothing to show there.
|
||||
if (_state.Mode == AppMode.Join && _setupStep is 3 or 4)
|
||||
_setupStep = 2;
|
||||
RenderSetupStep();
|
||||
}
|
||||
|
||||
private void OnSetupNext(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Validate before advancing — surface a friendly nudge if the
|
||||
// step's prereq isn't met.
|
||||
switch (_setupStep)
|
||||
{
|
||||
case 2 when string.IsNullOrEmpty(_state.RecRoomPath):
|
||||
ShowError("Pick your Rec Room install folder first.");
|
||||
return;
|
||||
case 3 when string.IsNullOrWhiteSpace(_state.ServerName):
|
||||
ShowError("Give your server a name first.");
|
||||
return;
|
||||
case 4 when string.IsNullOrWhiteSpace(_state.PhotonAppId):
|
||||
ShowError("A Realtime Photon AppId is required. Use the walkthrough button if you don't have one yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
_setupStep++;
|
||||
// Join mode skips host-only steps (3 = server name, 4 = Photon).
|
||||
if (_state.Mode == AppMode.Join && _setupStep is 3 or 4)
|
||||
_setupStep = 5;
|
||||
|
||||
if (_setupStep > 5)
|
||||
{
|
||||
_state.SetupComplete = true;
|
||||
_state.Save();
|
||||
ShowView(_state.Mode == AppMode.Join ? "join" : "host");
|
||||
return;
|
||||
}
|
||||
RenderSetupStep();
|
||||
}
|
||||
|
||||
private void RenderSetupStep()
|
||||
{
|
||||
SetupStep1.Visibility = _setupStep == 1 ? Visibility.Visible : Visibility.Collapsed;
|
||||
SetupStep2.Visibility = _setupStep == 2 ? Visibility.Visible : Visibility.Collapsed;
|
||||
SetupStep3.Visibility = _setupStep == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
SetupStep4.Visibility = _setupStep == 4 ? Visibility.Visible : Visibility.Collapsed;
|
||||
SetupStepDone.Visibility = _setupStep == 5 ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
// Step indicator chip and totals — Join skips host-only steps so
|
||||
// the "of N" denominator changes between modes. Join's step 5
|
||||
// (done) is the third visible step, since 3 + 4 are skipped.
|
||||
var total = _state.Mode == AppMode.Join ? 3 : 5;
|
||||
var displayed = (_state.Mode == AppMode.Join && _setupStep == 5) ? 3 : _setupStep;
|
||||
SetupStepIndicator.Text = $"STEP {displayed} OF {total}";
|
||||
|
||||
// Step 1 (mode pick) has no Back / Next — the cards self-advance.
|
||||
SetupBackBtn.Visibility = _setupStep > 1 ? Visibility.Visible : Visibility.Collapsed;
|
||||
SetupNextBtn.Visibility = _setupStep == 1 ? Visibility.Collapsed : Visibility.Visible;
|
||||
SetupNextBtn.Content = _setupStep == 5 ? "Finish →" : "Next →";
|
||||
|
||||
// Refresh the install banner + Photon status each render — the
|
||||
// user may have hit "Pick…" mid-wizard and we want the new path
|
||||
// reflected immediately.
|
||||
var path = _state.RecRoomPath ?? "(not picked)";
|
||||
SetupRecRoomPath.Text = path;
|
||||
SetupDetectedBanner.Visibility = string.IsNullOrEmpty(_state.RecRoomPath)
|
||||
? Visibility.Collapsed : Visibility.Visible;
|
||||
if (!string.IsNullOrEmpty(_state.RecRoomPath)) SetupDetectedPath.Text = _state.RecRoomPath;
|
||||
UpdateSetupPhotonStatus();
|
||||
}
|
||||
|
||||
private void UpdateSetupPhotonStatus()
|
||||
{
|
||||
if (SetupPhotonStatus is null) return;
|
||||
if (string.IsNullOrWhiteSpace(_state.PhotonAppId))
|
||||
{
|
||||
SetupPhotonStatus.Text = "Realtime AppId required before you can host.";
|
||||
SetupPhotonStatus.Foreground = (System.Windows.Media.Brush)FindResource("InkFaint");
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupPhotonStatus.Text = string.IsNullOrWhiteSpace(_state.PhotonVoiceAppId)
|
||||
? "✓ Realtime AppId set. (No voice — players can use Discord.)"
|
||||
: "✓ Realtime + Voice AppIds set.";
|
||||
SetupPhotonStatus.Foreground = (System.Windows.Media.Brush)FindResource("Ok");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSetupServerNameChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_state.ServerName = SetupServerName.Text.Trim();
|
||||
ServerNameInput.Text = _state.ServerName;
|
||||
_state.Save();
|
||||
}
|
||||
|
||||
private void OnSetupHostingModeChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (SetupHostLocal is null || SetupHostInternet is null) return;
|
||||
_state.HostingMode = SetupHostLocal.IsChecked == true
|
||||
? HostingMode.LocalNetwork
|
||||
: HostingMode.Internet;
|
||||
_state.Save();
|
||||
// Mirror to main HostView radios.
|
||||
if (HostModeLocal is not null && HostModeInternet is not null)
|
||||
{
|
||||
if (_state.HostingMode == HostingMode.LocalNetwork) HostModeLocal.IsChecked = true;
|
||||
else HostModeInternet.IsChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSetupPhotonChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_state.PhotonAppId = SetupPhotonAppId.Text.Trim();
|
||||
_state.PhotonVoiceAppId = SetupPhotonVoiceAppId.Text.Trim();
|
||||
_state.Save();
|
||||
// Mirror into the canonical Photon section so re-opening HostView
|
||||
// doesn't show stale values.
|
||||
PhotonAppIdInput.Text = _state.PhotonAppId;
|
||||
PhotonVoiceAppIdInput.Text = _state.PhotonVoiceAppId;
|
||||
UpdateSetupPhotonStatus();
|
||||
}
|
||||
|
||||
private void OnRerunSetup(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_state.SetupComplete = false;
|
||||
_state.Save();
|
||||
_setupStep = 1;
|
||||
ShowView("setup");
|
||||
}
|
||||
|
||||
// ── Legacy mode-pick handlers used by Settings -> Switch mode ──────
|
||||
private void OnPickHost(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_state.Mode = AppMode.Host;
|
||||
|
||||
Reference in New Issue
Block a user