import 'package:flutter/material.dart'; import '../app/app_controller.dart'; import '../app/live_sync.dart'; import '../app/settings.dart'; import '../l10n/tr.dart'; import 'app_actions.dart'; import 'copy_sync_settings.dart' show addLiveSyncPairDialog, LiveSyncRunOptions; import 'monitor/copy_task_card.dart'; import 'monitor/live_pair_card.dart'; import 'monitor/live_sync_flow.dart'; import 'theme.dart'; // 모니터링의 부품은 lib/ui/monitor/ 의 모듈에 있다. 예전부터 이 파일에서 가져다 쓰던 이름은 그대로 내보낸다. export 'monitor/live_sync_flow.dart' show confirmLiveDeletes, confirmLiveDeleteNow, confirmEmptySourceSync, askLiveSyncStart; export 'monitor/transfer_widgets.dart' show TransferProgressLines, PruneChoice, pruneLabel; /// 모니터링 (파일 탐색기 > 모니터링): 복사 · rsync 와 lsync (실시간 동기화) 를 따로 보여 준다. class MonitorPage extends StatelessWidget { final AppController c; final int initialTab; const MonitorPage({super.key, required this.c, this.initialTab = 0}); static Future open(BuildContext context, AppController c, {int tab = 0}) => Navigator.of(context) .push(MaterialPageRoute(builder: (_) => MonitorPage(c: c, initialTab: tab))); @override Widget build(BuildContext context) => DefaultTabController( length: 2, initialIndex: initialTab, child: Scaffold( body: Column(children: [ Builder(builder: (context) { // 좁은 화면: 둘째 줄 (높이 48) 에 맞게 탭에 아이콘을 옆으로 final compact = isCompact(context); Tab tab(IconData icon, String text) => compact ? Tab(height: 46, child: Row(mainAxisSize: MainAxisSize.min, children: [Icon(icon, size: 16), const SizedBox(width: 6), Text(text)])) : Tab(icon: Icon(icon, size: 18), text: text); return AppTopBar( nav: const AppNavButtons(), actions: AppActions(c: c), middle: Row(children: [ Text(tr('모니터링'), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(width: 16), Expanded( child: TabBar( isScrollable: true, tabAlignment: TabAlignment.start, tabs: [ tab(Icons.copy_all_outlined, tr('복사 · rsync')), tab(Icons.sync, tr('lsync (실시간 동기화)')), ], ), ), ]), ); }), Expanded( child: TabBarView(children: [ CopyTaskList(c: c), _LiveTab(c: c), ]), ), ]), ), ); } // ───────── lsync (실시간 동기화) ───────── class _LiveTab extends StatefulWidget { final AppController c; const _LiveTab({required this.c}); @override State<_LiveTab> createState() => _LiveTabState(); } class _LiveTabState extends State<_LiveTab> { AppController get c => widget.c; @override void initState() { super.initState(); // 열 때 맞출 것을 새로 센다 final live = LiveSync.instance; if (live != null) { for (final x in c.settings.copy.liveSyncPairs) { live.refreshPending(x); } } } /// 최종 정리를 시작했으면 진행을 볼 수 있게 복사 · rsync 탭으로 Future _finalize(LiveSyncPair x) async { if (await finalizeLiveSync(context, c, x) && mounted) DefaultTabController.of(context).animateTo(0); } @override Widget build(BuildContext context) { final live = LiveSync.instance; return ListenableBuilder( listenable: Listenable.merge([c, ?live]), builder: (context, _) { final pairs = c.settings.copy.liveSyncPairs; return ListView(padding: const EdgeInsets.all(12), children: [ Row(children: [ Expanded( child: Text( tr('원본이 바뀌면 대상에 맞춥니다. 일정을 정하면 그 시간에만 맞추고, 그 밖의 시간에는 바뀐 것을 모아 두었다가 다음 시작 시간에 맞춥니다.'), style: TextStyle(fontSize: 12, color: JjColors.textDim), ), ), TextButton.icon(onPressed: () => addLiveSyncPairDialog(context, c), icon: const Icon(Icons.add), label: Text(tr('추가'))), ]), // 백그라운드로 실행 · 다시 켤 때 동기화 (환경 설정과 같은 값) · 모두 시작 / 멈춤 Wrap(spacing: 12, runSpacing: 4, crossAxisAlignment: WrapCrossAlignment.center, children: [ LiveSyncRunOptions(c: c, dense: true), if (live != null && pairs.any((x) => x.enabled)) live.paused.isEmpty ? TextButton.icon( onPressed: live.pauseAll, icon: const Icon(Icons.pause, size: 18), label: Text(tr('모두 멈춤'))) : FilledButton.tonalIcon( onPressed: live.resumeAll, icon: const Icon(Icons.play_arrow, size: 18), label: Text(trf('모두 시작 (멈춘 것 {0}개)', [pairs.where(live.isPaused).length]))), ]), const SizedBox(height: 8), if (pairs.isEmpty) Padding( padding: const EdgeInsets.all(24), child: Center(child: Text(tr('실시간 동기화가 없습니다. [추가] 하거나 복사 · rsync 탭에서 [lsync 로 이동] 하세요.'), style: TextStyle(color: JjColors.textDim))), ), for (final x in pairs) LivePairCard( c: c, live: live, pair: x, onFinalize: () => _finalize(x), onShowCopyList: () => DefaultTabController.of(context).animateTo(0), ), ]); }, ); } }