F7 낮은 화면에서도 [이동] 을 칩 줄 아래로 · F8 다운로드 주소 칸을 좁고 글자가 클 때만 제목 아래 줄로 · 214 c 시험을 보내기 시작 뒤로 (관리자 승인 10/21, 15 찾음)
- F7: 폰 가로 (900×412) 에서 떠 있는 [이동] 이 [자막 파일 추가] 를 반쯤 가렸다 (잘못 누를 수 있음) → 높이 < 500 × 글자 배율 이면 206 의 배치 (칩 줄 아래) 를 씀. 처음에 높이 < 500 만 봤더니 960×600 · 글자 1.6 (ja) 에서도 가려 글자 배율을 곱함. 높이가 넉넉하면 그대로 - F8: 다운로드 화면 ja 412 글자 1.6 에서 제목이 넓어 주소 칸이 「動画...」 만큼으로 눌렸다 → 주소 칸이 200 보다 좁아질 때만 주소 칸 · [추가] 를 제목 아래 한 줄로 (글 폭은 지금 글꼴 · 배율로 잼). 이를 위해 AppTopBar 의 좁은 배치 아래 줄을 "높이 48 고정" 에서 "48 이상" 으로 (한 줄이면 예전처럼 48 가운데, 폭은 그대로 다 씀 - app_top_bar_test 201). 한 줄 막대 (넓은 화면) 에서는 늘 한 줄 - 214 c (move_settled_test): 전체 시험에서 한 번 실패 - 250ms 안에 원본 목록을 아직 읽지 않으면 새 파일까지 옮겨져 거짓 실패였다. 보내기가 실제로 시작된 뒤 새 파일을 만들고, "새 파일을 잃지 않음 (원본 또는 받는 쪽)" 을 따로 확인 - 시험: windows_mkvdetail_shot_test 에 900×412 (1.0 · 1.6) · 1280×480 과 [자막 파일 추가] 겹침 확인을 더해 18개 통과, windows_bigtext_shot_test 60개 통과 (release\test\shots_f1), 전체 단위 1150개 통과, 214 c 혼자 5번 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>rc2
parent
4554b4b98d
commit
f67e1e3a85
|
|
@ -42,13 +42,17 @@ class _Tool implements MediaTool {
|
|||
|
||||
/// F5 · 206 (관리자 지시 10/21): MKV 자세히 칸 - 오른쪽 아래 떠 있는 [이동] 단추가 마지막 줄 ("출력: …") 이나 칩을 가리지 않는다.
|
||||
/// 넓은 화면은 끝까지 내리면 마지막 줄이 단추 위로 (목록 끝 여백), 좁은 화면 (412) 은 단추가 칩 줄 아래에 놓인다.
|
||||
/// 412 · 960×600 · 1280×720 × 글자 1.0 · 1.6 × ko · ja. 단추와 출력 줄 · 칩이 겹치는지 자리로 잰다
|
||||
/// 412 · 960×600 · 1280×720 × 글자 1.0 · 1.6 · 낮은 화면 900×412 · 1280×480 (F7) × ko · ja. [자막 파일 추가] 도 단추에 가리지 않는다. 단추와 출력 줄 · 칩이 겹치는지 자리로 잰다
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? '';
|
||||
|
||||
for (final lang in ['ko', 'ja']) {
|
||||
for (final (w, h, scale) in [(412, 900, 1.0), (412, 900, 1.6), (960, 600, 1.0), (960, 600, 1.6), (1280, 720, 1.0), (1280, 720, 1.6)]) {
|
||||
for (final (w, h, scale) in [
|
||||
(412, 900, 1.0), (412, 900, 1.6), (960, 600, 1.0), (960, 600, 1.6), (1280, 720, 1.0), (1280, 720, 1.6),
|
||||
// F7 (10/21): 높이가 낮은 화면 (폰 가로 · 낮은 창) - [이동] 이 [자막 파일 추가] 를 가렸다
|
||||
(900, 412, 1.0), (900, 412, 1.6), (1280, 480, 1.0),
|
||||
]) {
|
||||
testWidgets('MKV 자세히 칸 이동 단추 $lang ${w}x$h 글자 $scale', (t) async {
|
||||
t.view.physicalSize = Size(w.toDouble(), h.toDouble());
|
||||
t.view.devicePixelRatio = 1;
|
||||
|
|
@ -105,7 +109,7 @@ void main() {
|
|||
// 206: 처음 자리에서 칩 (음성 n개 등) 이 [이동] 단추에 가리지 않음 (좁은 화면은 단추가 칩 줄 아래에 놓임)
|
||||
if (fab.evaluate().isNotEmpty) {
|
||||
final b = t.getRect(fab.first);
|
||||
for (final text in [trf('음성 {0}개', [2]), trf('영상 {0}', ['h264']), '1920×1080']) {
|
||||
for (final text in [trf('음성 {0}개', [2]), trf('영상 {0}', ['h264']), '1920×1080', tr('자막 파일 추가')]) {
|
||||
if (find.text(text).evaluate().isEmpty) continue;
|
||||
final r = t.getRect(find.text(text));
|
||||
expect(r.overlaps(b), isFalse, reason: '칩 "$text" 이 단추에 가리지 않음 ($r · 단추 $b)');
|
||||
|
|
|
|||
|
|
@ -103,11 +103,17 @@ class _AppTopBarState extends State<AppTopBar> {
|
|||
actions,
|
||||
]),
|
||||
),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
// F8 (10/21): 아래 줄은 48 이상 - middle 이 좁아 두 줄로 바꾸면 (다운로드 화면의 주소 칸) 그만큼 늘어난다. 한 줄이면 예전처럼 48 가운데
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 48),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
heightFactor: 1,
|
||||
child: SizedBox(
|
||||
width: double.infinity, // 아래 줄은 가운데가 폭을 다 쓴다 (201)
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: widget.trailing == null ? middle : Row(children: [Expanded(child: middle), widget.trailing!]))),
|
||||
child: widget.trailing == null ? middle : Row(children: [Expanded(child: middle), widget.trailing!]))))),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,12 +93,10 @@ class _DownloadsPageState extends State<DownloadsPage> {
|
|||
minMiddleWidth: 420,
|
||||
nav: const AppNavButtons(onDownloadsPage: true),
|
||||
actions: const AppActions(),
|
||||
middle: Row(children: [
|
||||
Text(trf('다운로드 ({0})', [d.tasks.length]),
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
SizedBox(width: isCompact(context) ? 8 : 20),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
middle: LayoutBuilder(builder: (context, box) {
|
||||
final title = Text(trf('다운로드 ({0})', [d.tasks.length]),
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600));
|
||||
final field = TextField(
|
||||
controller: _url,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
decoration: InputDecoration(
|
||||
|
|
@ -110,11 +108,30 @@ class _DownloadsPageState extends State<DownloadsPage> {
|
|||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSubmitted: (_) => _add(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(onPressed: _add, child: Text(tr('추가'))),
|
||||
]),
|
||||
);
|
||||
final add = FilledButton(onPressed: _add, child: Text(tr('추가')));
|
||||
// F8 (관리자 승인 10/21, 15 찾음): 좁고 글자가 크면 제목이 넓어 주소 칸이 「動画...」 만큼으로 눌렸다 -
|
||||
// 주소 칸이 200 보다 좁아질 때만 주소 칸 · [추가] 를 제목 아래 한 줄로 (넓으면 예전 한 줄 그대로)
|
||||
final scaler = MediaQuery.textScalerOf(context);
|
||||
double textW(String t, double size, [FontWeight? w]) => (TextPainter(
|
||||
text: TextSpan(text: t, style: DefaultTextStyle.of(context).style.copyWith(fontSize: size, fontWeight: w)),
|
||||
textDirection: TextDirection.ltr,
|
||||
textScaler: scaler,
|
||||
maxLines: 1)
|
||||
..layout())
|
||||
.width;
|
||||
final gap = isCompact(context) ? 8.0 : 20.0;
|
||||
final need = textW(trf('다운로드 ({0})', [d.tasks.length]), 16, FontWeight.w600) + gap + 200 + 8 + textW(tr('추가'), 14) + 48;
|
||||
// 한 줄 막대 (높이가 정해짐) 에서는 늘 한 줄 - 모자라면 막대가 스스로 두 줄 배치로 바뀐다 (AppTopBar)
|
||||
if (box.maxWidth >= need || box.maxHeight.isFinite) {
|
||||
return Row(children: [title, SizedBox(width: gap), Expanded(child: field), const SizedBox(width: 8), add]);
|
||||
}
|
||||
return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
title,
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [Expanded(child: field), const SizedBox(width: 8), add]),
|
||||
]);
|
||||
}),
|
||||
),
|
||||
Container(
|
||||
color: JjColors.bg,
|
||||
|
|
|
|||
|
|
@ -1103,7 +1103,9 @@ class _VideoDetail extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
// 206: 좁은 화면 (폰 세로) 은 자세히 칸이 낮아 떠 있는 버튼이 칩 (음성 n개 등) 을 가렸다 - 버튼을 칩 줄 아래에 놓는다
|
||||
if (isCompact(context)) {
|
||||
// F7 (관리자 승인 10/21, 15 찾음): 높이가 낮은 화면 (폰 가로 등) 도 - 떠 있는 버튼이 [자막 파일 추가] 를 반쯤 가렸다.
|
||||
// 높이는 글자 배율로 나눠 본다 (글자가 크면 같은 높이에 들어가는 줄이 적다 - 960×600 · 글자 1.6 에서도 가렸다)
|
||||
if (isCompact(context) || MediaQuery.sizeOf(context).height < 500 * MediaQuery.textScalerOf(context).scale(1)) {
|
||||
final row = Wrap(alignment: WrapAlignment.end, spacing: 10, runSpacing: 10, children: [
|
||||
if (buttons.isEmpty) button(null),
|
||||
for (final t in buttons) button(t),
|
||||
|
|
|
|||
|
|
@ -55,12 +55,19 @@ void main() {
|
|||
File(p.join(src, 'big.bin')).writeAsBytesSync(List.filled(300 * 1024, 7));
|
||||
final job = TransferJob(sources: [src], dest: dst, move: true, contents: true, bandwidthKBps: 400);
|
||||
final running = job.run();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 250));
|
||||
// 정해진 시간 (예전 250ms) 이 아니라 보내기가 실제로 시작된 뒤 (받는 쪽에 무엇이 생긴 뒤) 새 파일을 만든다 -
|
||||
// 전체 시험 부하에서는 250ms 안에 원본 목록을 아직 읽지 않아 새 파일까지 옮겨져 거짓 실패 (10/21, 15)
|
||||
for (var i = 0; i < 200; i++) {
|
||||
if (Directory(dst).existsSync() && Directory(dst).listSync(recursive: true).isNotEmpty) break;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 25));
|
||||
}
|
||||
File(p.join(src, 'arrived.txt')).writeAsStringSync('new');
|
||||
await running;
|
||||
expect(job.error, isNull);
|
||||
expect(File(p.join(dst, 'big.bin')).lengthSync(), 300 * 1024);
|
||||
expect(File(p.join(src, 'big.bin')).existsSync(), isFalse);
|
||||
// 데이터: 새 파일은 어디에도 없어지면 안 된다 (원본에 남았거나 옮겨졌거나)
|
||||
expect(File(p.join(src, 'arrived.txt')).existsSync() || File(p.join(dst, 'arrived.txt')).existsSync(), isTrue, reason: '새 파일을 잃지 않음');
|
||||
expect(File(p.join(src, 'arrived.txt')).existsSync(), isTrue, reason: '복사하지 않은 것은 남김');
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue