WIP 실시간 동기화 화면 (관리자 지시 10/20, 28 지적): 경고 글 안의 옵션 (--filter=H a.txt) 을 줄바꿈 없이 묶음 · 좁은 화면에서는 카드의 "rsync · 복사로 맞춤 · 지우기 포함" 칩을 경로 아래 한 줄로 (잘리던 것) (검토 대기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_d2dc267
RyuJongJin 2026-10-20 13:50:49 +09:00
parent b2c400a0fb
commit 0bd1466212
3 changed files with 44 additions and 14 deletions

View File

@ -21,7 +21,7 @@ import 'path_label.dart';
import 'rsync_setup.dart'; import 'rsync_setup.dart';
import 'schedule_editor.dart'; import 'schedule_editor.dart';
import '../core/sync_preview.dart' show optionsRemoveSource; import '../core/sync_preview.dart' show optionsRemoveSource;
import 'sync_preview_view.dart' show FilterLimitNote, confirmDeletingRun, effectiveRunText; import 'sync_preview_view.dart' show FilterLimitNote, confirmDeletingRun, effectiveRunText, unbreakableOption;
import 'theme.dart'; import 'theme.dart';
/// 모니터링 (파일 탐색기 > 모니터링): 복사 · rsync 와 lsync (실시간 동기화) 를 따로 보여 준다. /// 모니터링 (파일 탐색기 > 모니터링): 복사 · rsync 와 lsync (실시간 동기화) 를 따로 보여 준다.
@ -969,6 +969,18 @@ class _LiveTabState extends State<_LiveTab> {
final active = LiveSync.activeNow(x); final active = LiveSync.activeNow(x);
final running = live?.isRunning(x) ?? false; final running = live?.isRunning(x) ?? false;
final held = live?.isPaused(x) ?? false; final held = live?.isPaused(x) ?? false;
// 257: 칩은 길게 누르기 · 마우스로 전체를 본다. 관리자 지시 (10/20, 28 지적): 좁은 화면 (폰 세로) 에서는 경로 줄에 끼면
// "rsync · 복…" 으로 잘려 경로 아래 따로 한 줄로 (넓으면 예전처럼 경로 옆)
final narrow = MediaQuery.sizeOf(context).width < 600;
final chipText = [tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · ');
final chip = Tooltip(
message: chipText,
child: Chip(
visualDensity: VisualDensity.compact,
// 243: 실시간 동기화는 늘 복사로 맞춘다 (원본은 지우지 않음 - 원본을 비우려면 [최종 정리])
label: Text(chipText, maxLines: 1, overflow: TextOverflow.ellipsis),
),
);
return Card( return Card(
color: JjColors.panel, color: JjColors.panel,
margin: const EdgeInsets.only(bottom: 10), margin: const EdgeInsets.only(bottom: 10),
@ -1006,17 +1018,7 @@ class _LiveTabState extends State<_LiveTab> {
), ),
), ),
// 257: 폰 세로에서 이 줄이 넘쳤다 - 칩은 남는 폭만큼 (넘치면 … 로 줄임, 전체는 길게 누르기 · 마우스) // 257: 폰 세로에서 이 줄이 넘쳤다 - 칩은 남는 폭만큼 (넘치면 … 로 줄임, 전체는 길게 누르기 · 마우스)
Flexible( if (!narrow) Flexible(child: chip),
child: Tooltip(
message: [tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · '),
child: Chip(
visualDensity: VisualDensity.compact,
// 243: 실시간 동기화는 늘 복사로 맞춘다 (원본은 지우지 않음 - 원본을 비우려면 [최종 정리])
label: Text([tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · '),
maxLines: 1, overflow: TextOverflow.ellipsis),
),
),
),
// 이번 실행 동안 멈춤 / 시작 (설정의 켜기 · 끄기는 오른쪽 스위치) // 이번 실행 동안 멈춤 / 시작 (설정의 켜기 · 끄기는 오른쪽 스위치)
if (live != null && x.enabled) if (live != null && x.enabled)
IconButton( IconButton(
@ -1026,6 +1028,7 @@ class _LiveTabState extends State<_LiveTab> {
), ),
Switch(value: x.enabled, onChanged: (v) => _replace(x, x.copyWith(enabled: v))), Switch(value: x.enabled, onChanged: (v) => _replace(x, x.copyWith(enabled: v))),
]), ]),
if (narrow) Padding(padding: const EdgeInsets.only(left: 32, top: 2), child: chip),
if (held) if (held)
Padding( Padding(
padding: const EdgeInsets.only(left: 32, top: 2), padding: const EdgeInsets.only(left: 32, top: 2),
@ -1054,7 +1057,7 @@ class _LiveTabState extends State<_LiveTab> {
const Icon(Icons.block, size: 16, color: Colors.redAccent), const Icon(Icons.block, size: 16, color: Colors.redAccent),
Text(tr('이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다'), Text(tr('이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다'),
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)), style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
Text('(${live.noAutoDelete[k]!.join(' · ')})', style: const TextStyle(fontSize: 12, color: Colors.redAccent)), Text('(${live.noAutoDelete[k]!.map(unbreakableOption).join(' · ')})', style: const TextStyle(fontSize: 12, color: Colors.redAccent)),
OutlinedButton( OutlinedButton(
onPressed: () => confirmLiveDeleteNow(context, live, x), onPressed: () => confirmLiveDeleteNow(context, live, x),
child: Text(tr('지금 지우기 (확인)')), child: Text(tr('지금 지우기 (확인)')),

View File

@ -307,6 +307,10 @@ Future<bool> confirmDeletingRun(BuildContext context, CopyTask t, {bool includeM
/// 15 검토 F2 (177): rsync 옵션에 미리 볼 수 없는 거르기 규칙 (고치개 · H · P · merge · -F · -C · 읽지 못한 파일 등) 이 있으면 /// 15 검토 F2 (177): rsync 옵션에 미리 볼 수 없는 거르기 규칙 (고치개 · H · P · merge · -F · -C · 읽지 못한 파일 등) 이 있으면
/// 지울 목록이 실제보다 적게 보일 수 있다 - 빨갛게 알리고, 지우기 (--delete) 와 함께면 체크해야 [실행] 이 켜진다 (72 · 104 와 같게) /// 지울 목록이 실제보다 적게 보일 수 있다 - 빨갛게 알리고, 지우기 (--delete) 와 함께면 체크해야 [실행] 이 켜진다 (72 · 104 와 같게)
/// 옵션 글 (예: --filter=H a.txt) 이 좁은 창에서 "--" 와 "filter=…" 로 갈리지 않게 줄바꿈 없이 묶는다 (관리자 지시 10/20, 28 지적):
/// 빈칸은 줄바꿈 없는 빈칸, - · / 뒤에는 줄바꿈을 막는 글자 (U+2060, 보이지 않음)
String unbreakableOption(String s) => s.replaceAll(' ', ' ').replaceAllMapped(RegExp(r'[-/]'), (m) => '${m[0]}⁠');
class FilterLimitNote extends StatelessWidget { class FilterLimitNote extends StatelessWidget {
final List<String> unsupported; final List<String> unsupported;
final bool deletes; final bool deletes;
@ -316,7 +320,7 @@ class FilterLimitNote extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Widget build(BuildContext context) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(trf('이 옵션의 일부 거르기 규칙은 미리 볼 수 없습니다 - 실제로 더 지워질 수 있습니다: {0}', [unsupported.join(' · ')]), Text(trf('이 옵션의 일부 거르기 규칙은 미리 볼 수 없습니다 - 실제로 더 지워질 수 있습니다: {0}', [unsupported.map(unbreakableOption).join(' · ')]),
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)), style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
if (deletes) if (deletes)
CheckboxListTile( CheckboxListTile(

View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/ui/sync_preview_view.dart';
/// 관리자 지시 (10/20, 28 지적): 경고 글 안의 옵션 (--filter=H a.txt) 이 좁은 창에서 "--" 와 "filter=…" 로 갈리던 것
void main() {
double widest(String s, {bool min = true}) {
final tp = TextPainter(text: TextSpan(text: s, style: const TextStyle(fontSize: 12)), textDirection: TextDirection.ltr)..layout();
final w = min ? tp.minIntrinsicWidth : tp.maxIntrinsicWidth;
tp.dispose();
return w;
}
test('옵션 글은 줄바꿈 자리가 없다 (가장 좁게 접어도 한 줄 폭)', () {
for (final o in ['--filter=H a.txt', '-f"- *.log"', '--files-from=list/a b.txt', '-C']) {
final u = unbreakableOption(o);
expect(widest(u), closeTo(widest(u, min: false), 0.01), reason: o);
expect(u.replaceAll('⁠', '').replaceAll(' ', ' '), o, reason: '보이는 글은 그대로');
}
// 묶지 않은 글은 줄이 갈릴 수 있다 (시험이 실제로 무언가를 보는지)
expect(widest('--filter=H a.txt'), lessThan(widest('--filter=H a.txt', min: false)));
});
}