JJ_MKVMaker/lib/ui/home/encode_bar.dart

199 lines
8.4 KiB
Dart

/// 약속: 다운로드 상자와 인코딩 막대 (코덱 · 크기 · 화질 · 보정) - 바꾸기는 AppController 가.
library;
import 'package:flutter/material.dart';
import '../../app/app_controller.dart';
import '../../app/download_manager.dart';
import '../../core/encode_options.dart';
import '../app_actions.dart';
import '../downloads_page.dart';
import '../encode_notice.dart';
import '../theme.dart';
import '../video_adjust_dialog.dart';
import '../../l10n/tr.dart';
/// 상단 "다운로딩" 상자: 누르면 다운로드 목록
class HomeDownloadBox extends StatelessWidget {
final DownloadManager d;
const HomeDownloadBox({super.key, required this.d});
@override
Widget build(BuildContext context) => ListenableBuilder(
listenable: d,
builder: (context, _) {
final n = d.downloadingCount;
final p = d.overallProgress;
return InkWell(
borderRadius: BorderRadius.circular(6),
onTap: () => DownloadsPage.open(Navigator.of(context), d),
child: Container(
width: 150,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
border: Border.all(color: n > 0 ? JjColors.accent : JjColors.border),
borderRadius: BorderRadius.circular(6),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Icon(Icons.downloading, size: 16,
color: n > 0 ? JjColors.accent : JjColors.textDim),
const SizedBox(width: 6),
Text(
n > 0
? trf('다운로딩 {0}{1}', [n, p == null ? '' : ' · ${(p * 100).toStringAsFixed(0)}%'])
: trf('다운로드 {0}', [d.tasks.length]),
style: const TextStyle(fontSize: 12)),
]),
if (n > 0) ...[
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
value: p, minHeight: 4, backgroundColor: JjColors.panelHigh),
),
],
],
),
),
);
},
);
}
// ───────── 화면 크기 · 코덱 · 화질 ─────────
class EncodeBar extends StatelessWidget {
final AppController c;
const EncodeBar({super.key, required this.c});
@override
Widget build(BuildContext context) {
final s = c.encode;
// 32: MKV 를 만드는 동안만 (AI 자막 · 번역 중에는 다음 MKV 설정을 바꿀 수 있다)
final locked = c.buildingMkv;
final up = c.upscaleCount;
Widget label(String t) => Padding(
padding: const EdgeInsets.only(right: 6),
child: Text(t, style: TextStyle(fontSize: 12, color: JjColors.textDim)),
);
// 253: 글자가 크면 고르기 상자가 화면보다 넓어져 넘쳤다 - 제 폭 (가장 긴 항목) 을 쓰되 화면 폭을 넘지 않게 (글은 …)
final maxDrop = (MediaQuery.sizeOf(context).width - 40).clamp(120.0, double.infinity);
Widget drop<T>(T value, List<T> items, String Function(T) text,
ValueChanged<T> onChanged, {bool Function(T)? enabled}) =>
ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxDrop),
child: IntrinsicWidth(
child: DropdownButton<T>(
value: value,
isDense: true,
isExpanded: true,
underline: const SizedBox(),
style: TextStyle(fontSize: 13, color: JjColors.text),
items: [
for (final i in items)
DropdownMenuItem(
value: i,
enabled: enabled?.call(i) ?? true,
child: Text(
text(i) + ((enabled?.call(i) ?? true) ? '' : tr(' (사용 불가)')),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: (enabled?.call(i) ?? true) ? null : JjColors.textDim),
),
),
],
onChanged: locked ? null : (v) => onChanged(v as T),
),
),
);
final compact = isCompact(context);
final row = Row(children: [
label(tr('화면 크기')),
drop(s.resolution, ResolutionChoice.values, (r) => r.label, (r) {
c.setResolution(r);
showEncodeNotice(context, c);
}),
const SizedBox(width: 20),
label(tr('코덱')),
drop(s.codec, VideoCodecChoice.values, (v) => v.label, c.setCodec,
enabled: c.isCodecAvailable),
const SizedBox(width: 20),
if (s.reencode) ...[
label(tr('화질')),
drop(s.quality, QualityChoice.values, (q) => q.label, c.setQuality),
const SizedBox(width: 20),
],
// 화면 비율 (가로 · 세로) · 회전 · 색 보정: 미리보기를 보며 고르는 창
Tooltip(
message: s.adjusts ? '${tr('화면 · 색 보정')}: ${s.adjustSummary}' : tr('화면 비율 (가로 · 세로) · 회전 · 밝기 · 대비 · 채도 · 색온도'),
child: (s.adjusts ? FilledButton.tonalIcon : OutlinedButton.icon)(
onPressed: locked ? null : () => showVideoAdjust(context, c),
icon: const Icon(Icons.tune, size: 18),
label: Text(tr('화면 · 색 보정')),
),
),
const SizedBox(width: 12),
Expanded(
// 31: "⚠ 원본보다 커서" 경고는 화면 너비와 상관없이 늘 보이게 맨 앞에 (넓은 화면에서 한 줄이 넘치면 설명 · 보정 요약 쪽이 잘린다)
child: Text(
'${s.reencode && up > 0 ? '${trf('⚠ {0}개는 원본보다 커서 화질 향상 없이 용량만 늘어납니다', [up])} · ' : ''}'
'${s.adjusts ? s.adjustSummary : !s.reencode ? tr('영상·음성을 그대로 복사합니다 (빠름, 화질 손실 없음)') : tr('영상을 다시 인코딩합니다 (시간이 오래 걸림, 음성은 그대로)')}',
overflow: compact ? null : TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12, color: up > 0 && s.reencode ? JjColors.danger : JjColors.textDim),
),
),
]);
// 241 시험에서 찾음: 폭 720 ~ 840 (노트 10+ 가로 등) 에서 한 줄이 넘쳤다 - 960 보다 좁으면 폰처럼 줄을 바꿔 보인다 (내 판단)
// 256: 낮은 화면 (폰 가로) 은 여러 줄로 감기면 목록이 한 줄만 보였다 - 한 줄로 옆으로 밀게.
// 31: 설명 · "⚠ 원본보다 커서" 경고는 맨 앞에 (밀지 않아도 보이게)
if (!compact && MediaQuery.sizeOf(context).height < 500) {
final text = [for (final w in row.children) if (w is Expanded) w.child];
return Container(
width: double.infinity,
height: 44,
color: JjColors.panel,
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: [
for (final t in text) ...[ConstrainedBox(constraints: const BoxConstraints(maxWidth: 360), child: t), const SizedBox(width: 16)],
for (final w in row.children) if (w is! Expanded) w,
]),
),
);
}
if (compact || MediaQuery.sizeOf(context).width < 960) {
return Container(
width: double.infinity,
color: JjColors.panel,
padding: const EdgeInsets.fromLTRB(8, 4, 8, 6),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
runSpacing: 6,
// 31: 설명 · "⚠ 원본보다 커서 용량만 늘어납니다" 경고는 빼지 않고 한 줄을 다 써서 보여 준다
children: [
for (final w in row.children)
w is Expanded ? SizedBox(width: double.infinity, child: w.child) : w,
],
),
);
}
return Container(
height: 44,
color: JjColors.panel,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: row,
);
}
}
// ───────── 왼쪽: 동영상 목록 ─────────