UI 복잡 버전 234 RED→GREEN 완료(docs/UI_USECASES.md §A~§J): 라이브 테마 전환(DynamicResource 전환+ThemeResolver+HighContrast 테마, 재시작 없음), 디자인 토큰 체계·WCAG 대비 결함 5건 수정, 설정창 카테고리 내비/검색 재구성+AppConfig 40여 속성, 다운로드 관리자(속도/ETA/세그먼트맵/전체 일시정지-이어받기/CSV 내보내기/예약 게이트/중복 확인), 라이브러리·아카이브·작업공간·재생목록·즐겨찾기 편집 뷰, 접근성(자동화 속성 30여 개·커스텀 피어·docs/ACCESSIBILITY.md), 인터랙션(휠 클릭 새 탭·XButton 탐색·F12·영역 캡처·프로필 전환), 상태/알림(토스트·알림센터·온보딩·CrashReportDialog). 보강 테스트 35건+실창 E2E 4건(StaDispatcher 기반 — 프록시 설정이 기시작 핸들러를 건드리는 크래시 실결함 수정 포함) — 453/453 GREEN. 이전 미커밋 작업(CI 워크플로·Mdns·QueueHub·UpdateChecker·AGENTS/CLAUDE·빌드 스크립트) 일괄 포함

This commit is contained in:
Yun Chan 2026-08-17 15:54:53 +09:00
parent 5a0ce730b2
commit 17f2f91152
88 changed files with 9463 additions and 1127 deletions

View file

@ -42,6 +42,19 @@ public class DownloadItemViewModel : INotifyPropertyChanged
public CancellationTokenSource? Cts { get; set; }
public bool Started { get; set; }
/// <summary>§E 일시정지 상태(재개 대기) — 임시 파일 보존·"일시정지" 상태 표시 판단에 사용.</summary>
public bool PausedForResume { get; set; }
/// <summary>UC-D30 사이트별 묶음 — 출처 페이지 호스트(없으면 미디어 URL 호스트).</summary>
public string SiteName
{
get
{
var source = PageUrl ?? Url ?? "";
return Uri.TryCreate(source, UriKind.Absolute, out var u) ? u.Host : "직접 링크";
}
}
public string Status { get => _status; set => SetField(ref _status, value); }
public int Progress { get => _progress; set => SetField(ref _progress, value); }
// #9 직링크 길이 미상 시 진행률 바를 부정확(애니메이션) 모드로
@ -50,6 +63,71 @@ public class DownloadItemViewModel : INotifyPropertyChanged
public string? OutputPath { get => _outputPath; set => SetField(ref _outputPath, value); }
public string? Error { get => _error; set => SetField(ref _error, value); }
// ---- §E 다운로드 관리자 확장 (docs/UI_USECASES.md) ----
/// <summary>UC-D05 현재 속도 표시("2.4 MB/s" 등). DownloadManager 진행 콜백에서 갱신.</summary>
public string SpeedText { get => _speedText; set => SetField(ref _speedText, value); }
private string _speedText = "";
/// <summary>UC-D04 남은 시간 추정("3분 남음" 등). 속도·진행률로 계산.</summary>
public string RemainingText { get => _remainingText; set => SetField(ref _remainingText, value); }
private string _remainingText = "";
/// <summary>UC-D19 파일 총 크기 표시("142.5 MB" 등). 길이 미상이면 "".</summary>
public string TotalSizeText { get => _totalSizeText; set => SetField(ref _totalSizeText, value); }
private string _totalSizeText = "";
/// <summary>UC-D09 재시도 횟수.</summary>
public int RetryCount { get => _retryCount; set => SetField(ref _retryCount, value); }
private int _retryCount;
/// <summary>UC-D10 세그먼트별 진행 맵 — 각 세그먼트 진행률(0~1) 목록을 순서대로 유지.</summary>
public System.Collections.ObjectModel.ObservableCollection<double> SegmentMap { get; } = new();
/// <summary>UC-D24 대기열 우선순위(클수록 먼저). 기본 0.</summary>
public int Priority { get => _priority; set => SetField(ref _priority, value); }
private int _priority;
/// <summary>UC-D26 충돌 회피 후 최종 저장 파일명(OutputPath 확정 시 갱신).</summary>
public string? FinalFileName { get => _finalFileName; set => SetField(ref _finalFileName, value); }
private string? _finalFileName;
/// <summary>수신 바이트 총량(속도·ETA 계산 원천). 진행 콜백에서 누적.</summary>
public long ReceivedBytes { get => _receivedBytes; set => SetField(ref _receivedBytes, value); }
private long _receivedBytes;
/// <summary>총 바이트(길이 미상 -1). TotalSizeText 와 ETA 계산에 사용.</summary>
public long TotalBytes { get => _totalBytes; set => SetField(ref _totalBytes, value); }
private long _totalBytes = -1;
/// <summary>UC-D08 완료 시각(자동 치움 지연 판정). 미완료 null.</summary>
public DateTime? CompletedAt { get => _completedAt; set => SetField(ref _completedAt, value); }
private DateTime? _completedAt;
/// <summary>UC-D05 부속: 진행률·수신량으로 속도/ETA 텍스트를 갱신한다. speedBps ≤ 0 이면 속도만 지운다.</summary>
public void UpdateSpeedEta(long receivedBytes, long totalBytes, double speedBps)
{
ReceivedBytes = receivedBytes;
if (totalBytes > 0) TotalBytes = totalBytes;
SpeedText = speedBps <= 0
? ""
: speedBps < 1024 ? $"{speedBps:F0} B/s"
: speedBps < 1024 * 1024 ? $"{speedBps / 1024:F1} KB/s"
: $"{speedBps / 1024 / 1024:F1} MB/s";
if (TotalBytes > 0 && receivedBytes >= 0)
TotalSizeText = $"{TotalBytes / 1048576.0:F1} MB";
RemainingText = speedBps > 0 && TotalBytes > receivedBytes && receivedBytes > 0
? FormatEta(TimeSpan.FromSeconds((TotalBytes - receivedBytes) / speedBps))
: "";
}
/// <summary>ETA 사람 표기(1시간 미만 분 단위, 이상 시 시간 단위).</summary>
public static string FormatEta(TimeSpan eta) => eta.TotalHours >= 1
? $"{(int)eta.TotalHours}시간 {eta.Minutes}분 남음"
: eta.TotalMinutes >= 1
? $"{(int)Math.Ceiling(eta.TotalMinutes)}분 남음"
: $"{(int)Math.Ceiling(eta.TotalSeconds)}초 남음";
public DownloadItemViewModel(string title) { _title = title; }
public event PropertyChangedEventHandler? PropertyChanged;