JJ_MKVMaker/lib/ui/explorer/job_view.dart

116 lines
5.4 KiB
Dart

import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../../app/transfer_job.dart';
import '../../core/file_ops.dart' show formatSize;
import '../../core/vfs.dart' show vBasename;
import '../../l10n/tr.dart';
import '../theme.dart';
/// 약속 (E5d-4): 복사 · 이동 작업 하나의 진행 - 제목 (복사 · 옮기는 중 · 완료 · 멈춤 + 방법) · 대상 · [취소] (도는 동안).
/// 파일 탐색기: 위 = 전체 항목 중 · 아래 = 지금 폴더의 파일 중. [rsync] (폴더 하나를 맞춤): 위 = 지금 파일 · 아래 = 폴더 전체.
/// 그리기만 한다 (작업이 바뀌면 부르는 쪽이 다시 그림)
class TransferJobView extends StatelessWidget {
const TransferJobView({super.key, required this.job, required this.rsync});
final TransferJob job;
final bool rsync;
@override
Widget build(BuildContext context) {
String pct(double v) => '${(v * 100).round()}%';
final cur = job.index < job.total ? job.index : job.total - 1;
final curFiles = job.total == 0 ? 0 : job.filesTotal[cur.clamp(0, job.total - 1)];
final curDone = job.total == 0 ? 0 : job.filesDone[cur.clamp(0, job.total - 1)];
final title = job.finished
? (job.error == null ? tr('완료') : tr('멈춤'))
: job.move
? tr('옮기는 중')
: tr('복사하는 중');
final labelMax = MediaQuery.sizeOf(context).width * 0.45;
Widget line(String label, double value, String right) => Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(children: [
SizedBox(
width: math.min(300, labelMax), // 좁은 화면 (폰 세로) 은 폭의 45% 까지
child: Text(label, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12.5)),
),
const SizedBox(width: 10),
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(3),
child: LinearProgressIndicator(
minHeight: 8,
value: job.counting ? null : value,
// 빈 곳과 찬 곳이 분명히 다르게
color: JjColors.accent,
backgroundColor: JjColors.border,
),
),
),
const SizedBox(width: 10),
SizedBox(
width: 150,
child: Text(right, textAlign: TextAlign.end, style: TextStyle(fontSize: 12, color: JjColors.textDim)),
),
]),
);
return Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
Icon(job.move ? Icons.drive_file_move_outline : Icons.copy_outlined, size: 18, color: JjColors.accent),
const SizedBox(width: 8),
Text('$title · ${tr(job.method.label)}', style: const TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(width: 12),
Expanded(
// Rsync 화면: 원본 → 대상 (⇄ 는 두 줄)
child: Text(rsync ? '${job.sources.join(', ')}/ → ${job.dest}/' : job.dest,
maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12, color: JjColors.textDim)),
),
if (!job.finished) TextButton(onPressed: job.cancel, child: Text(tr('취소'))),
]),
// Rsync 화면 (폴더 하나를 맞춤): 위 = 지금 복사하는 파일 (그 파일의 진행률 · 속도), 아래 = 폴더 전체 (파일 수)
if (rsync) ...[
line(
job.counting
? tr('파일 세는 중…')
: job.pruning
? (job.finished ? trf('빈 폴더 {0}개 지움', [job.pruned]) : tr('원본의 빈 폴더 정리 중…'))
: job.finished
? tr('끝')
: job.currentFile.isEmpty
? tr('시작하는 중…')
: job.currentFile.split('/').last,
job.finished ? 1.0 : (job.filePercent ?? 0),
[
if (job.filePercent != null && !job.finished) pct(job.filePercent!),
if (!job.finished && job.speed != null) '${formatSize(job.speed!.round())}/s',
].join(' · '),
),
line(
trf('{0} 전체', [job.currentName.isEmpty ? vBasename(job.sources.first) : job.currentName]),
job.folderProgress,
'${pct(job.folderProgress)} · ${job.checkTotal > 0 && !job.finished ? trf('항목 {0}/{1}', [job.checked, job.checkTotal]) : trf('파일 {0}/{1}', [job.allDone, job.allFiles])}',
),
] else ...[
// 파일 탐색기 (여러 항목): 위 = 전체 항목 중 · 아래 = 지금 폴더의 파일 중
line(
trf('전체 {0}개 중 {1}번째', [job.total, (cur + 1).clamp(1, job.total)]),
job.overall,
'${pct(job.overall)} · ${trf('파일 {0}/{1}', [job.allDone, job.allFiles])}'
'${!job.finished && job.speed != null ? ' · ${formatSize(job.speed!.round())}/s' : ''}',
),
line(
job.counting
? tr('파일 세는 중…')
: job.pruning
? (job.finished ? trf('빈 폴더 {0}개 지움', [job.pruned]) : tr('원본의 빈 폴더 정리 중…'))
: trf('{0} 안의 파일', [job.currentName]),
job.current,
'${pct(job.current)} · ${trf('파일 {0}/{1}', [curDone, curFiles])}',
),
],
]);
}
}