폰 세로 모니터링 버튼 줄 넘침 (257) · 지운 쌍의 "동기화 멈춤" 알림이 남던 것 (258)
a4 의 934 기기 확인 · 관리자 지시 (10/12, 버그 수정): - 257: 폰 세로 모니터링 > lsync 카드의 버튼 줄이 화면 밖으로 넘쳐 [고치기] · [지우기] 를 누를 수 없었다 → Wrap 으로 줄 바꿈 (복사 카드 버튼 줄도). 같은 폭 (411) 시험에서 함께 넘치던 곳도 고침: lsync 탭 위 "다시 켤 때 동기화" 줄, 카드 머리의 방식 칩 (남는 폭만큼 · … · 전체는 마우스), 일정 글. - 258: 멈춘 lsync 쌍을 지워도 작업 알림 "⚠ 동기화 멈춤 1개" 가 앱을 다시 켤 때까지 남았다 → 쌍 목록이 바뀌면 지켜보지 않는 쌍 (지움 · 끔 · 멈춤) 의 멈춤 기록을 지운다. - 시험: 411 폭에서 넘침 없음 · 버튼이 화면 안 (고치기 전에는 실패) · 지우면 멈춤 기록이 바로 사라짐. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>통합
parent
4ad66dd5a1
commit
031e8d3f84
|
|
@ -322,6 +322,9 @@ class LiveSync extends ChangeNotifier {
|
|||
if (sig == _sig) return;
|
||||
_sig = sig;
|
||||
_stopAll();
|
||||
// 258: 지우거나 끈 · 멈춘 쌍의 "원본을 읽지 못해 멈춤" 은 지운다 (작업 알림 "⚠ 동기화 멈춤 N개" 가 앱을 다시 켤 때까지 남았다)
|
||||
final keys = pairs.map(keyOf).toSet();
|
||||
problems.removeWhere((k, _) => !keys.contains(k));
|
||||
for (final x in pairs) {
|
||||
final k = keyOf(x);
|
||||
if (!isDav(x.source) && !Directory(x.source).existsSync()) {
|
||||
|
|
|
|||
|
|
@ -517,9 +517,9 @@ class LiveSyncRunOptions extends StatelessWidget {
|
|||
]),
|
||||
),
|
||||
),
|
||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
// 257: 폰 세로에서 넘쳤다 - 모자라면 고르기 상자를 아래 줄로
|
||||
Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||
Text(tr('다시 켤 때 동기화'), style: const TextStyle(fontSize: 13, color: JjColors.textDim)),
|
||||
const SizedBox(width: 8),
|
||||
onStart,
|
||||
]),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -642,8 +642,8 @@ class _CopyCardState extends State<_CopyCard> {
|
|||
]),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
// 257: 폰 세로에서 버튼이 화면 밖으로 넘치지 않게 줄을 바꾼다 (오른쪽 정렬은 그대로)
|
||||
Wrap(alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, spacing: 2, runSpacing: 2, children: [
|
||||
if (running)
|
||||
TextButton.icon(onPressed: () => center.cancel(t.id), icon: const Icon(Icons.stop, size: 18), label: Text(tr('취소')))
|
||||
else
|
||||
|
|
@ -652,7 +652,6 @@ class _CopyCardState extends State<_CopyCard> {
|
|||
icon: const Icon(Icons.play_arrow, size: 18),
|
||||
label: Text(t.lastResult == 'cancelled' || t.lastResult == 'failed' ? tr('재개') : tr('실행')),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
TextButton.icon(
|
||||
onPressed: () => _toLive(t),
|
||||
icon: const Icon(Icons.sync_alt, size: 18),
|
||||
|
|
@ -959,10 +958,17 @@ class _LiveTabState extends State<_LiveTab> {
|
|||
),
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
// 243: 실시간 동기화는 늘 복사로 맞춘다 (원본은 지우지 않음 - 원본을 비우려면 [최종 정리])
|
||||
label: Text([tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · ')),
|
||||
// 257: 폰 세로에서 이 줄이 넘쳤다 - 칩은 남는 폭만큼 (넘치면 … 로 줄임, 전체는 길게 누르기 · 마우스)
|
||||
Flexible(
|
||||
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)
|
||||
|
|
@ -1008,12 +1014,15 @@ class _LiveTabState extends State<_LiveTab> {
|
|||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
WeekGridPreview(lines: x.schedule, width: 120),
|
||||
const SizedBox(width: 8),
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(scheduleSummary(x.schedule), style: TextStyle(fontSize: 12, color: active ? JjColors.accent : Colors.orangeAccent)),
|
||||
if (x.schedule.isNotEmpty)
|
||||
Text(x.schedule.join(' | '), style: const TextStyle(fontSize: 11, fontFamily: 'monospace', color: JjColors.textDim)),
|
||||
Text(tr('누르면 일정 바꾸기'), style: const TextStyle(fontSize: 10, color: JjColors.textDim)),
|
||||
]),
|
||||
// 257: 폰 세로에서 글이 줄을 바꿀 수 있게 (넘쳤다)
|
||||
Flexible(
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
||||
Text(scheduleSummary(x.schedule), style: TextStyle(fontSize: 12, color: active ? JjColors.accent : Colors.orangeAccent)),
|
||||
if (x.schedule.isNotEmpty)
|
||||
Text(x.schedule.join(' | '), style: const TextStyle(fontSize: 11, fontFamily: 'monospace', color: JjColors.textDim)),
|
||||
Text(tr('누르면 일정 바꾸기'), style: const TextStyle(fontSize: 10, color: JjColors.textDim)),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
|
@ -1060,8 +1069,8 @@ class _LiveTabState extends State<_LiveTab> {
|
|||
]),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
// 257: 폰 세로에서 [고치기] · [지우기] 가 화면 밖으로 밀려 누를 수 없었다 - 줄을 바꾼다 (오른쪽 정렬은 그대로)
|
||||
Wrap(alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, spacing: 2, runSpacing: 2, children: [
|
||||
TextButton.icon(
|
||||
onPressed: live == null ? null : () => live.refreshPending(x),
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
|
|
@ -1072,7 +1081,6 @@ class _LiveTabState extends State<_LiveTab> {
|
|||
icon: const Icon(Icons.sync, size: 18),
|
||||
label: Text(tr('지금 맞추기')),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
TextButton.icon(
|
||||
onPressed: running ? null : () => _finalize(x),
|
||||
icon: const Icon(Icons.cleaning_services_outlined, size: 18),
|
||||
|
|
|
|||
|
|
@ -61,6 +61,18 @@ void main() {
|
|||
live.dispose();
|
||||
});
|
||||
|
||||
test('258: 멈춘 쌍을 지우면 "동기화 멈춤" 도 바로 사라진다 (앱을 다시 켜지 않아도)', () async {
|
||||
final pair = LiveSyncPair(src, dst, delete: true).copyWith(deleteConfirmed: true);
|
||||
c.settings.liveSyncPairs = [pair];
|
||||
final live = LiveSync(c)..start();
|
||||
final k = LiveSync.keyOf(pair);
|
||||
await live.syncNow(pair);
|
||||
expect(live.problems[k], isNotNull);
|
||||
await c.updateSettings((s) => s.liveSyncPairs = []);
|
||||
expect(live.problems, isEmpty);
|
||||
live.dispose();
|
||||
});
|
||||
|
||||
test('70: 원본이 정말 비었으면 멈추고 지울 목록 → [그래도 맞추기] 로 그 한 번만 지운다', () async {
|
||||
Directory(src).createSync();
|
||||
final pair = LiveSyncPair(src, dst, delete: true).copyWith(deleteConfirmed: true);
|
||||
|
|
@ -138,6 +150,33 @@ void main() {
|
|||
expect(find.text('원본 폴더가 비어 있어 멈췄습니다'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('257: 폰 세로 (411 폭) 에서 lsync 카드의 [고치기] · [지우기] 가 화면 안에 있어 누를 수 있다', (tester) async {
|
||||
Directory(src).createSync();
|
||||
File(p.join(src, 'x.txt')).writeAsStringSync('x');
|
||||
final pair = LiveSyncPair(src, dst);
|
||||
c.settings.liveSyncPairs = [pair];
|
||||
final live = LiveSync(c);
|
||||
LiveSync.instance = live;
|
||||
addTearDown(() {
|
||||
LiveSync.instance = null;
|
||||
live.dispose();
|
||||
});
|
||||
tester.view.physicalSize = const Size(411, 800);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(MaterialApp(home: MonitorPage(c: c, initialTab: 1)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.takeException(), isNull, reason: '넘치지 않음');
|
||||
for (final tip in ['다시 세기', '지금 맞추기', '최종 정리', '복사 · rsync 로 이동']) {
|
||||
final r = tester.getRect(find.text(tip));
|
||||
expect(r.right <= 411 && r.left >= 0, isTrue, reason: '$tip $r');
|
||||
}
|
||||
for (final tip in ['고치기', '지우기']) {
|
||||
final r = tester.getRect(find.byTooltip(tip).first);
|
||||
expect(r.right <= 411 && r.left >= 0, isTrue, reason: '$tip $r 이 화면 밖');
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('93: Rsync --delete 실행은 비교가 끝나 지울 목록이 보인 뒤에만 [실행] 이 켜진다', (tester) async {
|
||||
final l = p.join(tmp.path, 'L'), r = p.join(tmp.path, 'R');
|
||||
File(p.join(l, 'sub', 'a.txt'))
|
||||
|
|
|
|||
Loading…
Reference in New Issue