import 'dart:math' as math; import 'package:flutter/material.dart'; import '../app/app_controller.dart'; import '../app/bookmarks_controller.dart'; import '../app/download_manager.dart'; import 'app_actions.dart'; import 'exit_guard.dart'; import 'network_settings.dart' show DnsFailureLine; import 'theme.dart'; import '../l10n/tr.dart'; import 'home/log_panel.dart'; import 'home/video_detail.dart'; import 'home/video_list_view.dart'; import 'home/encode_bar.dart'; import 'home/home_top_bar.dart'; class HomePage extends StatelessWidget { final AppController c; /// 다운로드 (없으면 "다운로딩" 상자 숨김) final DownloadManager? downloads; /// 종료 버튼 (없으면 숨김) final VoidCallback? onExit; /// 즐겨찾기 (없으면 브라우저 버튼 숨김) final BookmarksController? bookmarks; const HomePage({super.key, required this.c, this.downloads, this.onExit, this.bookmarks}); @override Widget build(BuildContext context) { return ListenableBuilder( listenable: c, builder: (context, _) => ExitGuard( c: c, downloads: downloads, child: Scaffold( // 끌어다 놓기는 앱 전체에서 받는다 (ui/app_drop.dart 의 AppDropArea - 어느 화면에서 놓아도 이 목록에 추가) body: SwipeNav( current: 'mkv', child: Column( children: [ HomeTopBar(c: c, downloads: downloads, onExit: onExit, bookmarks: bookmarks), const Divider(height: 1), EncodeBar(c: c), const Divider(height: 1), Expanded( // 좁은 화면 (접은 폴드 · 휴대폰 세로): 목록을 위에, 자세히 보기를 아래에 child: isCompact(context) && c.videos.isEmpty // 8: 좁은 화면에 동영상이 없으면 빈 목록 · 안내로 나누지 않고 한 화면에 안내 + [동영상 추가] ? _EmptyHint(onAdd: c.pickVideos) : isCompact(context) ? _CompactBody(c: c) : Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 13: 큰 화면 (태블릿 세로 등) 은 목록도 넓게 - 제목이 앞부분만 보여 구분이 안 되던 것 SizedBox( width: (MediaQuery.sizeOf(context).width * 0.4).clamp(320.0, 640.0), child: HomeVideoList(c: c)), const VerticalDivider(width: 1), Expanded( // 목록이 비었으면 [동영상 추가] 안내, 동영상은 있는데 고른 것이 없으면 "목록에서 누르세요" child: c.selected != null ? VideoDetailPane(c: c, v: c.selected!) : c.videos.isEmpty ? _EmptyHint(onAdd: c.pickVideos) : const _PickHint(), ), ], ), ), const Divider(height: 1), // 249: DNS 서버에 닿지 않는 동안 작업 기록 위에 (아래 안내를 놓쳐도 MKV 화면에서 보고 바꿀 수 있게) Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: DnsFailureLine(c: c, compact: isCompact(context))), if (c.settings.display.showLog) SizedBox( // 화면이 낮은 기기 (휴대폰 가로) 에서 목록 · 자세히 보기를 가리지 않도록 화면 높이에 맞춘다. // 256: 폰 가로 (높이 500 미만) 는 더 작게 - 목록이 한 줄만 보였다 // 관리자 판단 (10/17, 15 지적): 폰 세로에서 기록이 비었으면 안내 한 줄 높이만 (빈 자리만 없앰), 기록이 생기면 예전 높이 height: isCompact(context) && c.logs.isEmpty ? LogPanel.emptyHeight : MediaQuery.sizeOf(context).height < 500 ? 52 : (MediaQuery.sizeOf(context).height * 0.22).clamp(80.0, 140.0), child: LogPanel( logs: c.logs, onClose: () => c.updateSettings((s) => s.display.showLog = false), ), ) else LogStrip( last: c.logs.isEmpty ? '' : c.logs.last, onOpen: () => c.updateSettings((s) => s.display.showLog = true), ), ], ), ), ), ), ); } } // ───────── 상단 바 ───────── /// 좁은 화면 (폰 세로): 목록 위 · 자세히 아래. /// 15 지적 · 관리자 판단 (10/17): 목록을 높이 32% 로 고정하면 줄이 적을 때 빈 칸이 남고, 많을 때 4~5줄만 보였다. /// '자동' (처음 값) 은 목록이 내용만큼 차지하되 자세히 칸의 최소 높이를 남기고, 남는 공간은 목록에. /// 환경 설정 "폰 세로 목록 크기" 로 작게 (25%) · 크게 (55%) 를 고를 수 있다. class _CompactBody extends StatelessWidget { final AppController c; const _CompactBody({required this.c}); /// 줄 하나의 가장 작은 높이 (이보다 줄이 많으면 내용이 칸보다 길다고 보고 줄여 그리지 않는다) static const _rowMin = 48.0; @override Widget build(BuildContext context) => LayoutBuilder(builder: (context, box) { final h = box.maxHeight; final picked = c.selected != null; final cap = switch (c.settings.mkv.compactList) { 'small' => h * 0.25, 'large' => h * 0.55, // 자동: 자세히 칸 최소 - 고른 동영상이 있으면 62% (세부 정보 · 자막 버튼이 칸 밖으로 밀리지 않게, 261 · 266), // 없으면 안내 한 덩이만큼 _ => h - (picked ? h * 0.62 : math.min(120.0, h * 0.4)), }.clamp(math.min(120.0, h), h).toDouble(); // 줄이 많아 내용이 칸보다 확실히 길면 칸 높이 그대로 (shrinkWrap 은 줄을 모두 그려 느리다) final shrink = c.settings.mkv.compactList == 'auto' && c.videos.length * _rowMin < cap; return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (shrink) ConstrainedBox(constraints: BoxConstraints(maxHeight: cap), child: HomeVideoList(c: c, shrink: true)) else SizedBox(height: cap, child: HomeVideoList(c: c)), const Divider(height: 1), Expanded(child: picked ? VideoDetailPane(c: c, v: c.selected!) : const _PickHint()), ]); }); } /// 목록에 동영상이 있는데 고른 것이 없을 때 (15 지적: 빈 목록 안내 "동영상 추가로 파일을 고르세요" 가 나와 헷갈렸다) class _PickHint extends StatelessWidget { const _PickHint(); @override Widget build(BuildContext context) => Center( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column(mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.touch_app_outlined, size: 40, color: JjColors.textDim), const SizedBox(height: 8), Text(tr('고른 동영상이 없습니다'), textAlign: TextAlign.center, style: TextStyle(color: JjColors.textDim)), const SizedBox(height: 4), Text(tr('목록에서 동영상을 누르면 여기에 자세히 보입니다'), textAlign: TextAlign.center, style: TextStyle(color: JjColors.textDim, fontSize: 12)), ]), ), ); } class _EmptyHint extends StatelessWidget { final VoidCallback? onAdd; const _EmptyHint({this.onAdd}); @override Widget build(BuildContext context) => Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.video_library_outlined, size: 56, color: JjColors.textDim), const SizedBox(height: 12), Text(tr('"동영상 추가" 로 파일을 선택하세요'), textAlign: TextAlign.center, style: TextStyle(color: JjColors.textDim)), const SizedBox(height: 4), Text(tr('같은 폴더의 자막은 자동으로 추가됩니다'), textAlign: TextAlign.center, style: TextStyle(color: JjColors.textDim, fontSize: 12)), if (onAdd != null) ...[ const SizedBox(height: 16), FilledButton.icon(onPressed: onAdd, icon: const Icon(Icons.add), label: Text(tr('동영상 추가'))), ], ], ), ), ); }