폰 세로 MKV 위 막대 진행: "2/3 · 37%" 짧은 꼴 · 폭을 미리 잡아 [+ 동영상 추가] 를 가리지 않게

a4 의 934 기기 확인 · 관리자 지시 (10/12): 폰 세로에서 진행이 "37%" 만 보여 몇 번째인지 몰랐고, 동영상 추가 [+] 를
반쯤 가렸다. 위 막대는 작업이 시작될 때 재는데 그때는 진행률이 없어 글이 없다가, 나중에 글이 생기며 잰 폭보다 넓어졌다.
- 아이콘만 보이는 좁은 꼴에서 여러 개면 "n/전체 · %" · 폭은 늘 같게 (여러 개 72 · 하나 36).
- 여러 개를 만드는지가 바뀌면 위 막대를 다시 잰다.
- 시험: 폰 세로 411×800, 3개 만드는 중 진행률이 생긴 뒤에도 "n/3 · 37%" · [+] 의 보이는 부분과 겹치지 않음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
통합
RyuJongJin 2026-10-12 08:15:38 +09:00
parent 031e8d3f84
commit 1be9f58248
3 changed files with 67 additions and 4 deletions

View File

@ -416,7 +416,8 @@ class _TopBar extends StatelessWidget {
return AppTopBar( return AppTopBar(
// 242: 작업 중 (진행 표시 · 취소) 이 생기거나 없어지면 다시 잰다 // 242: 작업 중 (진행 표시 · 취소) 이 생기거나 없어지면 다시 잰다
key: ValueKey(('mkvTopBar', c.busy)), // (여러 개를 만드는지에 따라 진행 표시 폭이 달라 그때도 다시 잰다)
key: ValueKey(('mkvTopBar', c.busy, c.batchTotal > 1)),
// 242: 오른쪽 묶음 때문에 왼쪽 (동영상 추가 · AI) 이 아이콘 하나보다 좁아지면 두 줄 // 242: 오른쪽 묶음 때문에 왼쪽 (동영상 추가 · AI) 이 아이콘 하나보다 좁아지면 두 줄
minMiddleWidth: 56, minMiddleWidth: 56,
// 모든 화면 공통: [JJ 홈] [MKV 화면] [뒤로] [다운로드 목록] // 모든 화면 공통: [JJ 홈] [MKV 화면] [뒤로] [다운로드 목록]

View File

@ -419,10 +419,24 @@ class JobIndicator extends StatelessWidget {
value: p == null || p == 0 ? null : p, value: p == null || p == 0 ? null : p,
), ),
), ),
// 좁아서 아이콘만일 때도 진행률은 보이게 // 좁아서 아이콘만일 때도 진행률은 보이게. 여러 개면 짧은 꼴 "2/3 · 37%" (폰 세로에서 "MKV 3개 중 2번째" 가 잘렸다).
if (iconOnly && p != null) ...[ // 폭은 늘 같게 잡아 둔다 - 위 막대는 작업이 시작될 때 재는데, 그때는 진행률이 없어 글이 없다가 나중에 생기면
// 잰 폭보다 넓어져 왼쪽의 [+ 동영상 추가] 를 가렸다 (a4 934, 폰 세로)
if (iconOnly) ...[
const SizedBox(width: 4), const SizedBox(width: 4),
Text('${(p * 100).round()}%', style: const TextStyle(fontSize: 12, color: JjColors.accent)), SizedBox(
width: c.batchTotal > 1 ? 72 : 36,
child: Text(
[
if (c.batchTotal > 1) '${c.batchStarted}/${c.batchTotal}',
if (p != null) '${(p * 100).round()}%',
].join(' · '),
maxLines: 1,
softWrap: false,
overflow: TextOverflow.fade,
style: const TextStyle(fontSize: 12, color: JjColors.accent),
),
),
], ],
if (!iconOnly) const SizedBox(width: 6), if (!iconOnly) const SizedBox(width: 6),
if (!iconOnly) if (!iconOnly)

View File

@ -755,4 +755,52 @@ void main() {
await tester.runAsync(() => building); await tester.runAsync(() => building);
await tester.pump(); await tester.pump();
}); });
testWidgets('폰 세로 (노트 10+ 약 411×800): 여러 개를 만드는 동안 위 막대 진행은 "n/3 · 37%" 짧은 꼴 · [+ 동영상 추가] 를 가리지 않는다', (tester) async {
tester.view.physicalSize = const Size(411, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final tool = _HoldTool();
final dir = Directory.systemTemp.createTempSync('jj_batch_portrait_');
addTearDown(() => dir.deleteSync(recursive: true));
final files = [for (final n in ['a', 'b', 'c']) (File(p.join(dir.path, '$n.mp4'))..writeAsBytesSync([0])).path];
final c = AppController(PlatformServices(mediaTool: tool, storage: DesktopStorageService()));
late Future<void> building;
await tester.runAsync(() async {
await c.init();
await c.addVideos(files);
c.selected = c.videos.first;
building = c.buildVideos([...c.videos]);
for (var i = 0; i < 200 && tool.runs.isEmpty; i++) {
await Future<void>.delayed(const Duration(milliseconds: 10));
}
});
expect(c.busy, isTrue);
expect(c.batchTotal, 3);
await tester.pumpWidget(MaterialApp(home: HomePage(c: c)));
await tester.pump();
await tester.pump();
// 위 막대를 잰 뒤에 진행률이 생긴다 (예전에는 이때 글이 넓어져 [+] 를 가렸다)
for (final v in c.videos.where((v) => v.status == JobStatus.running)) {
v.progress = 0.37;
}
await c.updateSettings((_) {});
await tester.pump();
expect(tester.takeException(), isNull);
final prog = find.textContaining(RegExp(r'^\d+/3 · 37%$'));
expect(prog, findsOneWidget);
final add = find.byTooltip(Platform.isAndroid ? '동영상 추가' : '동영상 추가 (탐색기에서 끌어다 놓아도 됩니다)');
expect(add, findsWidgets);
final r = tester.getRect(prog);
// [+] 는 옆으로 미는 줄 안에 있어 끝이 잘려 보일 수 있다 - 실제로 보이는 부분 (밀기 줄 안쪽) 이 진행 글과 겹치지 않아야 한다
for (final e in add.evaluate()) {
final a = tester.getRect(find.byWidget(e.widget).first);
final view = tester.getRect(find.ancestor(of: find.byWidget(e.widget).first, matching: find.byType(Scrollable)).first);
final seen = a.intersect(view);
expect(seen.width > 0 && seen.overlaps(r), isFalse, reason: '[+] 의 보이는 부분 $seen 이 진행 글 $r 와 겹침');
}
c.cancel();
await tester.runAsync(() => building);
await tester.pump();
});
} }