import 'package:flutter/material.dart'; import '../../core/file_ops.dart' show samePath; import '../../l10n/tr.dart'; import '../explorer_look.dart'; import '../app_actions.dart' show isCompact; import 'explorer_controller.dart'; import 'explorer_pane.dart'; /// 191: Rsync 화면이 좌우 두 창 + 가운데 버튼 줄로 보이는 가장 작은 폭 (이보다 좁으면 창 하나씩) const double rsyncTwoPaneMinWidth = 560; /// 약속 (E5d-6): 창 배치 규칙 - 두 창 · 폴더 + 파일 목록 · 한 창 (설정, "자동" 은 화면 크기 따라), 좁아서 잠깐 한 창 ([splitTight]) · /// 좁은 "폴더 + 파일 목록" ([narrowSplit]) 은 [ExplorerPanesBody] 가 폭을 보고 정한다. 줄 높이 (목록 글자 크기 · 줄 간격), /// 복사 · 이동 갈 곳 창 ([other]), 밀어서 창 바꾸기, 항목이 보이게 스크롤 ([reveal]). [context]: 화면 (글자 크기 · 폭) class ExplorerLayout { ExplorerLayout(this.ctl, {required this.context}); final ExplorerController ctl; final BuildContext Function() context; /// 180: 왼쪽 창 · 오른쪽 창 · 파일 목록 (배치가 바뀌어도 같은 위젯) final viewKeys = [GlobalKey(), GlobalKey(), GlobalKey()]; /// 모양 · 배치가 바뀌면 (줄 높이 · 창 너비가 달라져) 보던 폴더가 밀려나므로 다시 그 폴더로 String shape = ''; /// 화면에 보일 경로 (5 · 14): Android 는 "SD 카드 › JJ_sdtest › jj_mkv", WebDAV 는 "☁ 서버 › 폴더", Windows 는 그대로 /// 그 항목이 목록 위쪽 1/4 쯤에 보이게 스크롤 (깊은 폴더로 가면 위쪽 폴더들의 다른 항목에 밀려 안 보이므로) void reveal(ExplorerPane pane, String path) => WidgetsBinding.instance.addPostFrameCallback((_) => _revealNow(pane, path)); void _revealNow(ExplorerPane pane, String path) { if (!ctl.host.alive || !pane.scroll.hasClients) return; // 화면의 트리와 같은 줄로 센다 (폴더 + 파일 목록 배치 · Rsync 화면은 폴더만) final i = ctl.rows(pane, foldersOnly: split || ctl.rsync).indexWhere((r) => r.kind == 0 && samePath(r.entry.path, path)); if (i < 0) return; final pos = pane.scroll.position; // 180: 끝 (maxScrollExtent) 으로 자르지 않는다 - 줄이 막 늘어난 때는 끝이 아직 앞의 것이라 보던 폴더로 못 간다. // 넘치면 다음 배치 때 실제 끝으로 맞춰진다 pane.scroll.jumpTo((i * rowHeight() - pos.viewportDimension / 4).clamp(0.0, double.infinity)); } /// 28 확인 (10/20): 창의 목록이 화면에 새로 붙으면 (좁은 Rsync 화면의 [오른쪽] 탭을 처음 볼 때 · 폭이 바뀌어 두 창이 될 때) /// 그 창의 지금 폴더를 보이게 한다. 켤 때 그려지지 않은 창은 [ctl.goTo] 의 스크롤을 건너뛰어, 나중에 보면 맨 위 (저장 장치) 에 있었다 void revealNewViews() => WidgetsBinding.instance.addPostFrameCallback((_) { if (!ctl.host.alive) return; for (final pane in ctl.panes) { final on = pane.scroll.hasClients; if (on && !pane.onScreen && pane.current.isNotEmpty) _revealNow(pane, pane.current); pane.onScreen = on; } }); /// X-plore 비교 40: 목록 글자 크기 (설정 %) · 줄 간격을 넣은 줄 높이 double get listTextScale => ctl.c.settings.explorerTextScale / 100; double rowHeight() => ctl.look.rowHeightOf(context(), textScale: listTextScale, spacing: rowSpacingFactor(ctl.c.settings.explorerRowSpacing)); /// 왼쪽 폴더 트리 + 오른쪽 파일 목록 배치 bool get split => !ctl.rsync && ctl.c.settings.explorerLayout == 'split' && !splitTight; /// 관리자 지시 10/20 (28 검토): 글자가 커서 (기기 글자 × 목록 글자 크기) 좁은 화면에 트리 · 목록 두 창이 다 들어가지 않으면 /// 그동안만 한 창 (트리 + 위 경로 막대) 으로 보인다 - 고른 배치 (설정) 는 그대로, 화면이 넓어지거나 글자가 작아지면 다시 두 창. [ExplorerPanesBody] 가 정한다 bool splitTight = false; /// 관리자 지시 10/20: 좁은 화면 (폭 < [narrowSplitWidth]) 의 "폴더 + 파일 목록" 좌우 배치 - 트리 창에 최소 폭을 두고 /// ✔ 칸 · 크기 줄을 숨긴다 (412 폭에서 트리가 77px 이라 폴더 이름이 한 글자도 안 보였다). [ExplorerPanesBody] 가 정한다 bool narrowSplit = false; static const narrowSplitWidth = 600.0; /// 좌우 두 창 (Rsync 화면은 늘) bool get dual => ctl.rsync || ctl.c.settings.explorerLayout == 'dual' || // 자동: 넓으면 두 창, 좁은 화면 (폰 세로) 은 한 창 - 같은 목록이 반씩 잘려 두 번 보이지 않게 (ctl.c.settings.explorerLayout == 'auto' && !isCompact(context())); /// 트리 줄. [foldersOnly]: 폴더만 ("폴더 + 파일 목록" 배치의 왼쪽) /// X-plore 비교 c: 트리 맨 위에 저장 장치 · 서버 · 즐겨찾기 (X-plore 스타일 · 설정) /// X-plore 비교 ⚖: X-plore 스타일에서 표시 동그라미 늘 보이기 (설정) bool get alwaysMarks => !ctl.rsync && ctl.look == ExplorerStyle.xplore && ctl.c.settings.explorerAlwaysMarks; /// X-plore 비교 ⚖: X-plore 스타일 탐색기 안에서 밀기 = 창 바꾸기 (설정) bool get swipePanes => !ctl.rsync && ctl.look == ExplorerStyle.xplore && ctl.c.settings.explorerSwipePanes; /// 밀어서 창 바꾸기: 두 창이면 다른 창을 활성으로, 한 창이면 숨은 창과 바꿔 보인다 bool switchPane(int dir) { if (!swipePanes) return false; if (dual || split) { ctl.active = 1 - ctl.active; } else { final t = ctl.panes[0]; ctl.panes[0] = ctl.panes[1]; ctl.panes[1] = t; ctl.active = 0; } ctl.update(); ctl.remember(ctl.pane, ctl.pane.current); ctl.host.snack(trf('창 바꿈: {0}', [ctl.displayPath(ctl.pane.current)])); return true; } /// 복사 · 이동 갈 곳 창 (두 창 · 숨은 다른 창, 없으면 null - 담아 두기) ExplorerPane? get other => dual || hiddenTarget ? ctl.panes[1 - ctl.active] : null; /// X-plore 비교 a: 한 창인데 "숨은 다른 창으로" 복사 · 이동 (위에 갈 곳 줄) bool get hiddenTarget => !ctl.rsync && !dual && !split && ctl.c.settings.explorerOnePaneCopy == 'other'; }