Baseline: WPF/Avalonia/Cli/Core + plans (BROWSER_FEATURES_PLAN v2, MOBILE_PLAN v1)

This commit is contained in:
Yun Chan 2026-08-15 11:59:25 +09:00
commit 21b7c4d8bd
60 changed files with 6581 additions and 0 deletions

View file

@ -0,0 +1,399 @@
<Window x:Class="VideoDownloader.App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:vm="clr-namespace:VideoDownloader.App.ViewModels"
mc:Ignorable="d"
FontFamily="{StaticResource UiFont}"
Title="Video Downloader" Height="820" Width="1300"
Background="{StaticResource WindowBgGradient}"
WindowStartupLocation="CenterScreen"
WindowStyle="None" ResizeMode="CanResize">
<!-- 보더리스: WindowChrome 이 실제 엣지 리사이즈 + 올바른 최대화(작업표시줄 존중) + DWM 그림자 담당 -->
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0"
ResizeBorderThickness="6"
GlassFrameThickness="1"
CornerRadius="0"
UseAeroCaptionButtons="False"
NonClientFrameEdges="None"/>
</WindowChrome.WindowChrome>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<DataTemplate DataType="{x:Type vm:DownloadItemViewModel}">
<Border Background="{StaticResource CardBg}" BorderBrush="{StaticResource BorderSubtle}"
BorderThickness="1" CornerRadius="12" Padding="13" Margin="0,0,0,10">
<StackPanel>
<DockPanel>
<Border DockPanel.Dock="Right" Background="{StaticResource GlassBg}"
BorderBrush="{StaticResource BorderBrush}" BorderThickness="1"
CornerRadius="6" Padding="8,2" VerticalAlignment="Center">
<TextBlock Text="{Binding FormatTag}" FontSize="10" FontWeight="Bold"
Foreground="{StaticResource AccentBrush}"/>
</Border>
<TextBlock Text="{Binding Title}" Foreground="{StaticResource TextPrimary}"
FontSize="13" FontWeight="SemiBold" TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"/>
</DockPanel>
<!-- 소셜(YouTube/Instagram/X) 카드: 화질 선택 -->
<DockPanel Margin="0,9,0,2" Visibility="{Binding IsSocial, Converter={StaticResource BoolToVis}}">
<Button DockPanel.Dock="Right" Click="CardFetchFormats_Click" Margin="7,0,0,0"
VerticalAlignment="Center" ToolTip="이 영상에서 사용 가능한 화질 조회">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource CardActionButton}">
<Setter Property="Content" Value="불러오기"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsFetchingFormats}" Value="True">
<Setter Property="Content" Value="…"/>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="다운로드 중"><Setter Property="IsEnabled" Value="False"/></DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="병합 중"><Setter Property="IsEnabled" Value="False"/></DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="리먹스 중"><Setter Property="IsEnabled" Value="False"/></DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="완료"><Setter Property="IsEnabled" Value="False"/></DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<ComboBox Style="{StaticResource DarkComboBox}"
ItemsSource="{Binding Formats}" DisplayMemberPath="Label"
SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center"/>
</DockPanel>
<DockPanel Margin="0,11,0,6">
<TextBlock DockPanel.Dock="Right" Text="{Binding Segments}"
Foreground="{StaticResource TextMuted}" FontSize="11"
VerticalAlignment="Center" Margin="8,0,0,0"/>
<Grid VerticalAlignment="Center">
<ProgressBar Value="{Binding Progress}" Maximum="100">
<ProgressBar.Style>
<Style TargetType="ProgressBar" BasedOn="{StaticResource AccentProgressBar}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsIndeterminate}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
<ProgressBar>
<ProgressBar.Style>
<Style TargetType="ProgressBar" BasedOn="{StaticResource IndeterminateBar}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsIndeterminate}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
</Grid>
</DockPanel>
<DockPanel Margin="0,4,0,0">
<TextBlock Text="{Binding Status}" FontSize="11" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextSecondary}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="오류">
<Setter Property="Foreground" Value="{StaticResource ErrorBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="완료">
<Setter Property="Foreground" Value="{StaticResource SuccessBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Style="{StaticResource CardActionButton}" Content="받기" Click="CardDownload_Click" Margin="0,0,6,0"/>
<Button Content="취소" Click="CardCancel_Click" Margin="0,0,6,0">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource CardActionButton}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="다운로드 중"><Setter Property="IsEnabled" Value="True"/></DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="병합 중"><Setter Property="IsEnabled" Value="True"/></DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="리먹스 중"><Setter Property="IsEnabled" Value="True"/></DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Style="{StaticResource CardActionButton}" Content="재시도" Click="CardRetry_Click" Margin="0,0,6,0"/>
<Button Style="{StaticResource CardActionButton}" Content="폴더" Click="CardFolder_Click"/>
</StackPanel>
</DockPanel>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="오류">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="&#xE7BA;" Style="{StaticResource FluentIcon}" Foreground="{StaticResource ErrorBrush}" FontSize="13" Margin="0,0,6,0"/>
<TextBlock Text="{Binding Error}" Foreground="{StaticResource ErrorBrush}" FontSize="11" TextWrapping="Wrap" MaxWidth="300"/>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ===== 상단 툴바 (슬림, 전체가 드래그 핸들) ===== -->
<Border Grid.Row="0" Background="{StaticResource PanelBg}"
BorderBrush="{StaticResource AccentFadeBrush}" BorderThickness="0,0,0,2" Padding="20,9,0,9"
PreviewMouseLeftButtonDown="TitleBar_PreviewMouseLeftButtonDown">
<DockPanel>
<!-- 브랜드 -->
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal"
VerticalAlignment="Center" Margin="0,0,12,0">
<Border Width="26" Height="26" CornerRadius="7" Background="{StaticResource AccentGradient}">
<TextBlock Text="&#xE896;" Style="{StaticResource FluentIcon}" Foreground="White" FontSize="14"/>
</Border>
<TextBlock Text="Video Downloader" Foreground="{StaticResource TextPrimary}"
FontWeight="SemiBold" FontSize="13.5" VerticalAlignment="Center" Margin="9,0,0,0"/>
</StackPanel>
<!-- 네비게이션 (Fluent 아이콘) -->
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Center">
<Button Name="NavBack" Style="{StaticResource IconButton}" Content="&#xE76B;" ToolTip="뒤로 (Alt+←)" Margin="0,0,3,0" Click="NavBack_Click"/>
<Button Name="NavForward" Style="{StaticResource IconButton}" Content="&#xE76C;" ToolTip="앞으로 (Alt+→)" Margin="0,0,3,0" Click="NavForward_Click"/>
<Button Name="NavRefresh" Style="{StaticResource IconButton}" Content="&#xE72C;" ToolTip="새로고침 (F5)" Margin="0,0,3,0" Click="NavRefresh_Click"/>
<Button Name="NavHome" Style="{StaticResource IconButton}" Content="&#xE80F;" ToolTip="홈" Click="NavHome_Click"/>
</StackPanel>
<!-- 우측 액션 + 캡션 (Fluent 아이콘 + 툴팁) -->
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,0">
<Button Name="BookmarksBtn" Style="{StaticResource IconButton}" Content="&#xE734;" ToolTip="즐겨찾기" Margin="0,0,2,0" Click="ToggleBookmarks"/>
<Button Name="HistoryBtn" Style="{StaticResource IconButton}" Content="&#xE823;" ToolTip="방문 기록" Margin="0,0,2,0" Click="ToggleHistory"/>
<Button Style="{StaticResource IconButton}" Content="&#xE8B7;" ToolTip="저장 폴더 열기" Margin="0,0,2,0" Click="OpenFolder_Click"/>
<Button Name="LinkBtn" Style="{StaticResource IconButton}" Content="&#xE71B;" ToolTip="링크로 받기 (YouTube·Instagram·X)" Margin="0,0,2,0" Click="ToggleLink"/>
<Button x:Name="PrivateBtn" Style="{StaticResource IconButton}" Content="&#xE72E;" ToolTip="개인정보 보호 모드(InPrivate)" Margin="0,0,2,0" Click="TogglePrivate_Click"/>
<Button Style="{StaticResource IconButton}" Content="&#xE713;" ToolTip="설정" Margin="0,0,8,0" Click="Settings_Click"/>
<Button Style="{StaticResource PrimaryButton}" Content="이동" ToolTip="주소로 이동 (Enter)" Height="32" Click="Go_Click"/>
<!-- 구분선 (좌우 대칭 여백) -->
<Border Width="1" Background="{StaticResource BorderSubtle}" Margin="10,8"/>
<!-- 윈도우 캡션 컨트롤 (고정폭, 서로 밀착) -->
<Button Style="{StaticResource CaptionButton}" Content="&#xE921;" ToolTip="최소화" Click="Minimize_Click"/>
<Button Name="MaxRestoreBtn" Style="{StaticResource CaptionButton}" Content="&#xE922;" ToolTip="최대화" Click="MaxRestore_Click"/>
<Button Style="{StaticResource CloseButton}" Content="&#xE8BB;" ToolTip="닫기" Click="Close_Click"/>
</StackPanel>
<!-- 주소창 -->
<Border Background="{StaticResource GlassBg}" BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" CornerRadius="8" Height="32" VerticalAlignment="Center">
<TextBox Name="AddressBox" Style="{StaticResource DarkTextBox}"
PreviewKeyDown="AddressBox_PreviewKeyDown" TextChanged="AddressBox_TextChanged"
BorderThickness="0" Background="Transparent"
Padding="14,0" FontSize="13" VerticalContentAlignment="Center"/>
</Border>
</DockPanel>
</Border>
<!-- ===== 본문 ===== -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition x:Name="RightCol" Width="420" MinWidth="56" MaxWidth="900"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" ClipToBounds="True" Background="Black">
<wv2:WebView2 Name="Browser"/>
</Border>
<!-- 리사이즈 핸들 (펼침 상태에서만 노출). 드래그로 우측 패널 폭 변경. -->
<GridSplitter x:Name="RightSplitter" Grid.Column="1" Width="7"
HorizontalAlignment="Center" VerticalAlignment="Stretch"
ResizeBehavior="PreviousAndNext" ResizeDirection="Columns"
Background="Transparent" Cursor="SizeWE"
DragCompleted="RightSplitter_DragCompleted">
<GridSplitter.Template>
<ControlTemplate TargetType="GridSplitter">
<Border x:Name="handle" Background="Transparent">
<Rectangle Width="1" HorizontalAlignment="Center" Fill="{StaticResource BorderSubtle}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="handle" Property="Background" Value="{StaticResource AccentFadeBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</GridSplitter.Template>
</GridSplitter>
<!-- 범용 우측 패널: 헤더(토글 버튼, 항상 노출) + 본문(펼침 시). 접기/펼치기·폭·상태 저장. -->
<Border Grid.Column="2" Background="{StaticResource PanelBg}"
BorderBrush="{StaticResource BorderSubtle}" BorderThickness="1,0,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="10,12,10,8" LastChildFill="True">
<Button x:Name="PanelToggle" DockPanel.Dock="Right" Style="{StaticResource IconButton}"
Click="PanelToggle_Click" ToolTip="패널 접기"
VerticalAlignment="Center" Margin="4,0,0,0" Content="&#xE76C;"/>
<Border x:Name="PanelCountBadge" DockPanel.Dock="Right" Background="{StaticResource GlassBg}"
CornerRadius="9" Padding="9,2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,8,0">
<TextBlock Text="{Binding Items.Count}" Foreground="{StaticResource TextSecondary}" FontSize="11"/>
</Border>
<TextBlock x:Name="PanelTitle" Text="다운로드" Foreground="{StaticResource TextPrimary}"
FontWeight="SemiBold" FontSize="13.5" VerticalAlignment="Center"/>
</DockPanel>
<Grid Grid.Row="1" x:Name="PanelBody">
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0,60,0,0"
Visibility="{Binding Items.Count, Converter={StaticResource InverseCountToVisibility}}">
<Border Width="54" Height="54" CornerRadius="14" Background="{StaticResource GlassBg}"
BorderBrush="{StaticResource BorderBrush}" BorderThickness="1" HorizontalAlignment="Center">
<TextBlock Text="&#xE8B2;" Style="{StaticResource FluentIcon}" FontSize="24" Foreground="{StaticResource TextMuted}"/>
</Border>
<TextBlock Text="영상을 재생하면 여기에 나타납니다" Foreground="{StaticResource TextSecondary}"
FontSize="13" FontWeight="Medium" HorizontalAlignment="Center" Margin="0,14,0,2"/>
<TextBlock Text="탐지 즉시 자동으로 목록에 추가돼요" Foreground="{StaticResource TextMuted}"
FontSize="11.5" HorizontalAlignment="Center"/>
</StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="16,2,16,16">
<ItemsControl ItemsSource="{Binding Items}"/>
</ScrollViewer>
</Grid>
</Grid>
</Border>
</Grid>
<!-- ===== 상태 표시줄 ===== -->
<Border Grid.Row="2" Background="{StaticResource PanelBg}"
BorderBrush="{StaticResource BorderSubtle}" BorderThickness="0,1,0,0" Padding="16,5">
<TextBlock Name="StatusText" Foreground="{StaticResource TextMuted}" FontSize="11"
TextTrimming="CharacterEllipsis" Text="준비"/>
</Border>
<!-- ===== 즐겨찾기 플라이아웃 ===== -->
<Popup Name="BookmarksPopup" PlacementTarget="{Binding ElementName=BookmarksBtn}"
Placement="Bottom" HorizontalOffset="-10" VerticalOffset="6"
StaysOpen="False" AllowsTransparency="True" PopupAnimation="Fade">
<Border Background="{StaticResource CardBg}" BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" CornerRadius="12" Padding="8" Width="320" MaxHeight="460">
<StackPanel>
<DockPanel Margin="6,4,6,6">
<TextBlock Text="즐겨찾기" Foreground="{StaticResource TextPrimary}" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button DockPanel.Dock="Right" Style="{StaticResource CardActionButton}" Content="+ 현재 페이지 추가" Click="AddBookmark_Click" HorizontalAlignment="Right"/>
</DockPanel>
<Border Background="{StaticResource BorderSubtle}" Height="1" Margin="0,0,0,4"/>
<ItemsControl Name="BookmarksList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel Margin="0,1">
<Button DockPanel.Dock="Right" Style="{StaticResource CardActionButton}" Content="✕"
Click="RemoveBookmark_Click" Padding="8,5" Margin="6,0,0,0" VerticalAlignment="Center"/>
<Button Style="{StaticResource FlatButton}" Click="OpenBookmark_Click" HorizontalAlignment="Stretch">
<StackPanel>
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" FontSize="12.5" FontWeight="Medium"/>
<TextBlock Text="{Binding Url}" Foreground="{StaticResource TextMuted}" FontSize="10.5" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Button>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</Popup>
<!-- ===== 히스토리 플라이아웃 ===== -->
<Popup Name="HistoryPopup" PlacementTarget="{Binding ElementName=HistoryBtn}"
Placement="Bottom" HorizontalOffset="-10" VerticalOffset="6"
StaysOpen="False" AllowsTransparency="True" PopupAnimation="Fade">
<Border Background="{StaticResource CardBg}" BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" CornerRadius="12" Padding="8" Width="340" MaxHeight="480">
<StackPanel>
<DockPanel Margin="6,4,6,6">
<TextBlock Text="방문 기록" Foreground="{StaticResource TextPrimary}" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button DockPanel.Dock="Right" Style="{StaticResource CardActionButton}" Content="🗑 전체 삭제" Click="ClearHistory_Click" HorizontalAlignment="Right"/>
</DockPanel>
<Border Background="{StaticResource BorderSubtle}" Height="1" Margin="0,0,0,4"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="400">
<ItemsControl Name="HistoryList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource FlatButton}" Click="OpenHistory_Click" HorizontalAlignment="Stretch" Margin="0,1">
<StackPanel>
<DockPanel>
<TextBlock DockPanel.Dock="Right" Text="{Binding VisitedAt, StringFormat=HH:mm}" Foreground="{StaticResource TextMuted}" FontSize="10.5" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" FontSize="12.5" FontWeight="Medium"/>
</DockPanel>
<TextBlock Text="{Binding Url}" Foreground="{StaticResource TextMuted}" FontSize="10.5" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Border>
</Popup>
<!-- ===== 주소창 자동완성 제안 ===== -->
<Popup Name="AddressPopup" PlacementTarget="{Binding ElementName=AddressBox}"
Placement="Bottom" StaysOpen="False" AllowsTransparency="True" PopupAnimation="Fade"
Width="560" MaxHeight="380">
<Border Background="{StaticResource CardBg}" BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" CornerRadius="10" Padding="4">
<ListBox Name="AddressSuggestions" Style="{StaticResource SuggestList}"
ItemContainerStyle="{StaticResource SuggestItem}"
PreviewMouseLeftButtonUp="AddressSuggestion_Click">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" FontSize="12.5" FontWeight="Medium"/>
<TextBlock Text="{Binding Url}" Foreground="{StaticResource TextMuted}" FontSize="10.5" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Popup>
<!-- ===== 링크로 받기 플라이아웃 (YouTube/Instagram/X URL 붙여넣기) ===== -->
<Popup Name="LinkPopup" PlacementTarget="{Binding ElementName=LinkBtn}"
Placement="Bottom" HorizontalOffset="-10" VerticalOffset="6"
StaysOpen="False" AllowsTransparency="True" PopupAnimation="Fade">
<Border Background="{StaticResource CardBg}" BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" CornerRadius="12" Padding="10" Width="380">
<StackPanel>
<TextBlock Text="링크로 받기" Foreground="{StaticResource TextPrimary}" FontWeight="SemiBold" Margin="6,2,6,4"/>
<TextBlock Text="YouTube·Instagram·X 등 영상 링크를 붙여넣고 받기를 누르세요" Foreground="{StaticResource TextMuted}" FontSize="11" Margin="6,0,6,10" TextWrapping="Wrap"/>
<DockPanel>
<Button DockPanel.Dock="Right" Style="{StaticResource PrimaryButton}" Content="받기" Margin="8,0,0,0" Click="LinkDownload_Click"/>
<Border Background="{StaticResource GlassBg}" BorderBrush="{StaticResource BorderBrush}" BorderThickness="1" CornerRadius="8">
<TextBox Name="LinkBox" Style="{StaticResource DarkTextBox}" BorderThickness="0" Background="Transparent"
Padding="12,8" PreviewKeyDown="LinkBox_PreviewKeyDown"/>
</Border>
</DockPanel>
</StackPanel>
</Border>
</Popup>
</Grid>
</Window>