feat(ypp): YPP journey tracker slice 1 — the YPP pull-out below Stream Settings (TASK 39, Good Dog ONE test)
Current-OAuth-scope data, no re-consent: subscriber bars for both tiers (500 fan-funding / 1,000 ad-revenue; the 2027-02-01 doubling is versioned date-aware data in YppThresholds), 3-uploads/90d, live standing from channels.list auditDetails, self-reported 2FA/AdSense checkboxes + deep links (Google 2-Step / youtube.com/earn), and the "what counts" education. Every refresh appends a YppSnapshot (SQLite v10) so the analytics slice has history from day one. - ChannelStatsService (channels.list statistics,auditDetails,contentDetails + playlistItems.list, virtual CaptureCurrentAsync = test seam); YppTrackerViewModel mirrors LiveBroadcastFormViewModel. - MainViewModel.Ypp.cs: Ypp property, ChannelStatsFactoryOverride seam, one-open-at-a-time drawer exclusivity (both directions); account sign-in/out/restore hooks in Account.cs. - PreviewPane.xaml: TextPullOutHost is now a drawer bank [broadcast][ypp][stacked white tabs]; outside-click collapse covers both drawers. MainWindow.xaml.cs updated. - Wait - watchers: the whole feature already reviewed against YouTube's own docs (YPP Earn tab + API availability) before building — see TASKS/task-39-ypp-journey-tracker.md. - ONE integration test: YppPullOutTests (snapshot capture/persist + checklist round-trip + drawer exclusivity). 313/313 pass, 0 warnings. - Memory in same commit: TASKS.md row, ai.md Journey-tracker bullet (roadmap→reality), HANDOFF.md. Slice 2 (Analytics yt-analytics.readonly re-consent → watch-hours/Shorts + velocity/ETA + sparkline) is queued behind this, not merged.
This commit is contained in:
+175
-20
@@ -413,13 +413,16 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- ═══ BROADCAST TEXT PULL-OUT (always visible) ═══
|
||||
White "Stream Settings" tab on the preview's right edge; clicking it
|
||||
toggles the broadcast-metadata form, and any click outside
|
||||
the form collapses it. Edits persist locally immediately;
|
||||
Update pushes liveBroadcasts.update. -->
|
||||
<!-- ═══ RIGHT-EDGE PULL-OUTS (always visible) ═══
|
||||
White "Stream Settings" + "YPP" tabs stacked on the preview's right
|
||||
edge; each toggles its own drawer (the same animated-width pattern).
|
||||
Only one drawer is open at a time — IsDrawerOpen exclusivity is
|
||||
VM-enforced (opening one collapses the other). Any click outside
|
||||
both collapses whichever is open (MainWindow code-behind). -->
|
||||
<Grid Grid.Row="0">
|
||||
<StackPanel x:Name="TextPullOutHost" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Stretch">
|
||||
<StackPanel x:Name="TextPullOutHost" Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Stretch">
|
||||
<!-- The broadcast-metadata drawer -->
|
||||
<Border Background="#16213e" ClipToBounds="True">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
@@ -509,21 +512,173 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- The white tab -->
|
||||
<Border Width="30" VerticalAlignment="Center" Background="#ffffff"
|
||||
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand"
|
||||
ToolTip="Broadcast text fields">
|
||||
<Border.InputBindings>
|
||||
<MouseBinding Gesture="LeftClick"
|
||||
Command="{Binding BroadcastForm.ToggleDrawerCommand}"/>
|
||||
</Border.InputBindings>
|
||||
<TextBlock Text="Stream Settings" Foreground="#e94560" FontWeight="Bold" FontSize="15"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock.LayoutTransform>
|
||||
<RotateTransform Angle="90"/>
|
||||
</TextBlock.LayoutTransform>
|
||||
</TextBlock>
|
||||
<!-- The YPP-progress drawer (below Stream Settings on the rail) -->
|
||||
<Border Background="#16213e" ClipToBounds="True">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Width" Value="0"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Ypp.IsDrawerOpen}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Width"
|
||||
To="320" Duration="0:0:0.2">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<CubicEase EasingMode="EaseOut"/>
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Width"
|
||||
To="0" Duration="0:0:0.2">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<CubicEase EasingMode="EaseOut"/>
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<Grid Width="320" Margin="14,10,14,12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" Margin="0,0,0,8">
|
||||
<TextBlock Text="YPP PROGRESS" Style="{StaticResource SectionHeader}"
|
||||
HorizontalAlignment="Left"/>
|
||||
<Button HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Background="Transparent" BorderThickness="0" Cursor="Hand"
|
||||
Command="{Binding Ypp.CloseDrawerCommand}"
|
||||
ToolTip="Close">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter/>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<TextBlock Text="✕" Foreground="#a0a0b0" FontSize="14" FontWeight="Bold"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Sign in to YouTube to see your Partner Program progress and eligibility checklist here."
|
||||
Foreground="#d0d0e0" FontSize="13" TextWrapping="Wrap"
|
||||
Visibility="{Binding Ypp.IsSignedOut, Converter={StaticResource BoolToVis}}"/>
|
||||
<TextBlock Text="Refreshing from YouTube…" Foreground="#a0a0b0" FontSize="12"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding Ypp.IsLoading, Converter={StaticResource BoolToVis}}"/>
|
||||
<TextBlock Text="{Binding Ypp.LoadError}" Foreground="#ef4444" FontSize="11"
|
||||
TextWrapping="Wrap" Margin="0,0,0,4"
|
||||
Visibility="{Binding Ypp.LoadError, Converter={StaticResource NotNullToVis}}"/>
|
||||
<StackPanel Visibility="{Binding Ypp.IsSignedOut, Converter={StaticResource InverseBoolToVis}}">
|
||||
<TextBlock Text="{Binding Ypp.DisplayName}" Foreground="#e0e0e0" FontSize="13"
|
||||
FontWeight="SemiBold" Margin="0,0,0,10"/>
|
||||
<TextBlock Text="SUBSCRIBERS — TIER 1 · FAN FUNDING" Style="{StaticResource YtLabel}" Margin="0,0,0,2"/>
|
||||
<TextBlock Text="{Binding Ypp.Tier1SubsText}" Foreground="#e0e0e0" FontSize="13"/>
|
||||
<Border Height="8" Background="#2a2a40" CornerRadius="4" Margin="0,4,0,10">
|
||||
<Border Height="8" Background="#e94560" CornerRadius="4" HorizontalAlignment="Left"
|
||||
Width="{Binding Ypp.Tier1SubsBarWidth}"/>
|
||||
</Border>
|
||||
<TextBlock Text="SUBSCRIBERS — TIER 2 · AD REVENUE" Style="{StaticResource YtLabel}" Margin="0,0,0,2"/>
|
||||
<TextBlock Text="{Binding Ypp.Tier2SubsText}" Foreground="#e0e0e0" FontSize="13"/>
|
||||
<Border Height="8" Background="#2a2a40" CornerRadius="4" Margin="0,4,0,10">
|
||||
<Border Height="8" Background="#e94560" CornerRadius="4" HorizontalAlignment="Left"
|
||||
Width="{Binding Ypp.Tier2SubsBarWidth}"/>
|
||||
</Border>
|
||||
<TextBlock Text="PUBLIC UPLOADS (90 DAYS)" Style="{StaticResource YtLabel}" Margin="0,0,0,2"/>
|
||||
<TextBlock Text="{Binding Ypp.UploadsText}" Foreground="#e0e0e0" FontSize="13"/>
|
||||
<Border Height="8" Background="#2a2a40" CornerRadius="4" Margin="0,4,0,6">
|
||||
<Border Height="8" Background="#e94560" CornerRadius="4" HorizontalAlignment="Left"
|
||||
Width="{Binding Ypp.UploadsBarWidth}"/>
|
||||
</Border>
|
||||
<TextBlock Text="LONG-FORM WATCH HOURS + SHORTS VIEWS" Style="{StaticResource YtLabel}" Margin="0,10,0,2"/>
|
||||
<TextBlock Text="These arrive with the extended analytics update — subscriber + upload + standing progress is live now."
|
||||
Foreground="#666677" FontSize="12" TextWrapping="Wrap"/>
|
||||
|
||||
<TextBlock Text="COMPLIANCE CHECKLIST" Style="{StaticResource YtLabel}" Margin="0,14,0,4"/>
|
||||
<CheckBox Content="Channel in good standing (live from YouTube)" Foreground="#e0e0e0" FontSize="12"
|
||||
IsEnabled="False" Margin="0,2,0,0"
|
||||
IsChecked="{Binding Ypp.OverallGoodStanding, Mode=OneWay}"/>
|
||||
<TextBlock Text="{Binding Ypp.StandingDetailText}" Foreground="#888899" FontSize="11"
|
||||
TextWrapping="Wrap" Margin="0,4,0,8"/>
|
||||
<CheckBox Content="Two-Step Verification switched on" Foreground="#e0e0e0" FontSize="12"
|
||||
Margin="0,2,0,0"
|
||||
IsChecked="{Binding Ypp.TwoFactorEnabled}"/>
|
||||
<Button Background="Transparent" BorderThickness="0" Cursor="Hand" HorizontalAlignment="Left"
|
||||
Command="{Binding Ypp.OpenTwoStepCommand}" ToolTip="Open Google's 2-Step Verification page">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter/>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<TextBlock Text="Set up on Google →" Foreground="#5aa7ff" FontSize="12" Margin="0,0,0,6"/>
|
||||
</Button>
|
||||
<CheckBox Content="AdSense account linked" Foreground="#e0e0e0" FontSize="12"
|
||||
Margin="0,2,0,0"
|
||||
IsChecked="{Binding Ypp.AdSenseLinked}"/>
|
||||
<Button Background="Transparent" BorderThickness="0" Cursor="Hand" HorizontalAlignment="Left"
|
||||
Command="{Binding Ypp.OpenEarnCommand}" ToolTip="Open the YouTube Earn page">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter/>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<TextBlock Text="How to link on YouTube →" Foreground="#5aa7ff" FontSize="12" Margin="0,0,0,6"/>
|
||||
</Button>
|
||||
|
||||
<TextBlock Text="WHAT COUNTS" Style="{StaticResource YtLabel}" Margin="0,10,0,4"/>
|
||||
<TextBlock Text="{Binding Ypp.WhatCountsText}" Foreground="#a0a0b0" FontSize="12"
|
||||
TextWrapping="Wrap" Margin="0,0,0,6"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<StackPanel Grid.Row="2" Margin="0,10,0,0">
|
||||
<Button Style="{StaticResource YtButton}" Content="Refresh from YouTube"
|
||||
Command="{Binding Ypp.RefreshCommand}"/>
|
||||
<TextBlock Text="{Binding Ypp.LastUpdatedText}" Foreground="#888899" FontSize="11" Margin="0,6,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- The stacked white tabs -->
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Stretch">
|
||||
<Border Width="30" VerticalAlignment="Center" Background="#ffffff"
|
||||
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
|
||||
ToolTip="Broadcast text fields">
|
||||
<Border.InputBindings>
|
||||
<MouseBinding Gesture="LeftClick"
|
||||
Command="{Binding BroadcastForm.ToggleDrawerCommand}"/>
|
||||
</Border.InputBindings>
|
||||
<TextBlock Text="Stream Settings" Foreground="#e94560" FontWeight="Bold" FontSize="15"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock.LayoutTransform>
|
||||
<RotateTransform Angle="90"/>
|
||||
</TextBlock.LayoutTransform>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<Border Width="30" VerticalAlignment="Center" Background="#ffffff"
|
||||
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
|
||||
ToolTip="YouTube Partner Program progress">
|
||||
<Border.InputBindings>
|
||||
<MouseBinding Gesture="LeftClick"
|
||||
Command="{Binding Ypp.ToggleDrawerCommand}"/>
|
||||
</Border.InputBindings>
|
||||
<TextBlock Text="YPP" Foreground="#e94560" FontWeight="Bold" FontSize="15"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock.LayoutTransform>
|
||||
<RotateTransform Angle="90"/>
|
||||
</TextBlock.LayoutTransform>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user