199 lines
8.4 KiB
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,
|
|
);
|
|
}
|
|
}
|
|
|
|
// ───────── 왼쪽: 동영상 목록 ─────────
|