JJ_MKVMaker/lib/ui/explorer/transfer_panel.dart

143 lines
6.5 KiB
Dart

import 'package:flutter/material.dart';
import '../../app/app_controller.dart';
import '../../app/transfer_job.dart';
import '../../core/file_ops.dart' show FileOpCancelled;
import '../../l10n/tr.dart';
import '../theme.dart';
import 'job_view.dart';
/// 약속 (E5d-4): 아래에서 올라오는 복사 · 이동 진행 패널 (작업마다 [TransferJobView]). 작업이 없으면 내려가 숨는다.
/// 설정 explorerProgressHidden 이면 오른쪽 아래 작은 표시 ([TransferProgressChip], 멈춘 것이 있으면 빨갛게) - 누르면 다시 펼침
class ExplorerTransferPanel extends StatelessWidget {
const ExplorerTransferPanel({super.key, required this.c, required this.jobs, required this.rsync});
final AppController c;
final List<TransferJob> jobs;
/// Rsync 화면 (작업 줄 모양이 다름)
final bool rsync;
@override
Widget build(BuildContext context) {
// 85: 숨겼으면 오른쪽 아래 작은 표시 "복사 · 이동 n개 · 45%" (누르면 다시 펼침)
if (jobs.isNotEmpty && c.settings.explorer.progressHidden) {
return Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ListenableBuilder(
listenable: Listenable.merge(jobs),
builder: (context, _) {
final run = [for (final j in jobs) if (!j.finished) j];
// 관리자 판단: 숨겨도 멈춘 (실패한) 것은 분명히 - 빨간 표시 (끝난 알림 · 묻는 창은 숨김과 상관없이 늘 뜬다)
final failed = jobs.any((j) => j.finished && j.error != null && j.error is! FileOpCancelled);
final pct = run.isEmpty || run.any((j) => j.counting)
? null
: (run.fold<double>(0, (a, j) => a + j.overall) / run.length * 100).round();
return TransferProgressChip(
running: run.length,
percent: pct,
failed: failed,
onTap: () => c.updateSettings((x) => x.explorer.progressHidden = false));
},
),
),
);
}
return AnimatedSlide(
offset: jobs.isEmpty ? const Offset(0, 1.2) : Offset.zero,
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
child: jobs.isEmpty
? const SizedBox(height: 0)
: Material(
elevation: 12,
color: JjColors.panel,
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 12, 12),
// 여러 개가 함께 돌면 (23) 높이를 넘지 않게 넘겨 보기
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: MediaQuery.sizeOf(context).height * 0.45),
child: SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [
// 85: 숨기기 (아래 작은 표시로) - 다른 파일을 보면서 기다릴 수 있게. Android 는 알림줄에도 진행률
Align(
alignment: Alignment.centerRight,
child: SizedBox(
height: 28,
child: TextButton.icon(
style: TextButton.styleFrom(visualDensity: VisualDensity.compact),
onPressed: () => c.updateSettings((x) => x.explorer.progressHidden = true),
icon: const Icon(Icons.expand_more, size: 18),
label: Text(tr('숨기기'), style: const TextStyle(fontSize: 12)),
),
),
),
for (final (k, job) in jobs.indexed) ...[
if (k > 0) const Divider(height: 18),
ListenableBuilder(listenable: job, builder: (context, _) => TransferJobView(job: job, rsync: rsync)),
],
]),
),
),
),
),
);
}
}
/// 85: 진행 패널을 숨겼을 때 오른쪽 아래 작은 표시 "복사 · 이동 n개 · 45%" (멈추면 빨갛게). 누르면 다시 펼침
class TransferProgressChip extends StatelessWidget {
/// 도는 것 수 (0 이면 끝) · 진행률 (모르면 null) · 멈춘 (실패한) 것이 있는지
final int running;
final int? percent;
final bool failed;
final VoidCallback onTap;
const TransferProgressChip({super.key, required this.running, this.percent, this.failed = false, required this.onTap});
@override
Widget build(BuildContext context) => Material(
elevation: 6,
color: failed ? JjColors.danger.withValues(alpha: 0.25) : JjColors.panelHigh,
borderRadius: BorderRadius.circular(18),
child: InkWell(
borderRadius: BorderRadius.circular(18),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(mainAxisSize: MainAxisSize.min, children: [
// 멈춤은 진행 고리 대신 경고 표시 (꽉 찬 고리는 "끝났다" 로 보였다)
if (failed)
Icon(Icons.error_outline, size: 16, color: JjColors.danger)
else
SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2, value: running == 0 ? 1 : (percent == null ? null : percent! / 100)),
),
const SizedBox(width: 8),
// 좁은 화면 · 큰 글자면 두 줄로 (412 · 글자 1.6배에서 화면을 넘었다 - 253 시험)
Flexible(
child: Text(
failed
? tr('복사 · 이동 멈춤 - 눌러서 보기')
: running == 0
? tr('복사 · 이동 끝')
: percent == null
? trf('복사 · 이동 {0}개', [running])
: trf('복사 · 이동 {0}개 · {1}%', [running, percent]),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12.5),
),
),
const SizedBox(width: 4),
Tooltip(message: tr('진행 패널 보기'), child: const Icon(Icons.expand_less, size: 18)),
]),
),
),
);
}