WIP 큰 글자 · 줄 간격 (관리자 지시 10/20, 28 검토): ① 글자가 커서 (기기 글자 × 목록 글자 크기) 좁은 화면에 "폴더 + 파일 목록" 두 창이 다 들어가지 않으면 그동안만 한 창 (트리 + 위 경로 막대) - 설정은 split 그대로, 넓어지거나 글자가 작아지면 다시 두 창 (d3 판단: 두 창 필요 폭 = 182 + 90 × 글자 배율, 1배는 360 폭에도 들어가고 1.3 × 150% 는 412 에도 모자람) ② 트리 이름 자리를 48 또는 줄 폭의 40% 로 (깊은 폴더 · 큰 글자에서 들여쓰기에 밀려 몇 글자만 남던 것) ③ 줄 간격 설명에 "좁게는 손가락으로 누르기 어려울 수 있습니다" (하한으로 막지 않음) · 시험 · 캡처 (기기 1.3) (검토 대기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xplore_view
RyuJongJin 2026-10-21 00:54:47 +09:00
parent f3f1cebbc8
commit cb50d28486
7 changed files with 53 additions and 10 deletions

View File

@ -2698,7 +2698,7 @@
"파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer tap: single tap selects → single tap opens. To revert: Settings > File explorer > Tap",
"파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "File explorer two-pane layout: follow screen shape → side by side. To revert: the [Columns ⇆ / Rows ⇅] button in the file explorer, or Pane layout",
"파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "Only the name and size text in the file explorer list. Multiplied by the app screen size and the device text size",
"파일 탐색기 목록의 줄 높이": "Row height in the file explorer list",
"파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "Row height in the file explorer list. Compact rows may be hard to tap with a finger",
"파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "You can also change it from the ⋮ menu at the top of the file explorer (side by side · top and bottom · button bar position · buttons)",
"파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "File explorer panes: two panes → by screen size (one pane on a portrait phone). To revert: Settings > File explorer > Pane layout",
"파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer: a single tap now opens a file right away (long-press to select or for the menu). To change: Settings > File explorer > Tap",

View File

@ -2698,7 +2698,7 @@
"파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラーのタップ: 1 回で選択 → 1 回ですぐ開く。元に戻す: 環境設定 > ファイルエクスプローラー > タップ",
"파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "ファイルエクスプローラーの 2 画面配置: 画面の形に合わせる → 左右。元に戻す: ファイルエクスプローラーの [左右 ⇆ / 上下 ⇅] ボタンまたは画面配置",
"파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "ファイルエクスプローラー一覧の名前・サイズの文字のみ。アプリの画面サイズ・端末の文字サイズと掛け合わされます",
"파일 탐색기 목록의 줄 높이": "ファイルエクスプローラー一覧の行の高さ",
"파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "ファイルエクスプローラー一覧の行の高さ。狭くすると指でタップしにくい場合があります",
"파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "ファイルエクスプローラー上部の ⋮ メニューからも変えられます (左右 · 上下 · ボタン列の位置 · ボタン構成)",
"파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "ファイルエクスプローラーの画面: 2 画面 → 画面サイズに合わせる (縦向きのスマホは 1 画面)。元に戻す: 環境設定 > ファイルエクスプローラー > 画面配置",
"파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラー: ファイルを 1 回タップするとすぐ開きます (選択 · メニューは長押し)。変更: 環境設定 > ファイルエクスプローラー > タップ",

View File

@ -2698,7 +2698,7 @@
"파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器点击: 单击选择 → 单击直接打开。恢复: 设置 > 文件浏览器 > 点击",
"파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "文件浏览器双窗格布局: 跟随屏幕形状 → 左右。恢复: 文件浏览器中的 [左右 ⇆ / 上下 ⇅] 按钮或窗格布局",
"파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "仅文件管理器列表中的名称和大小文字。会与应用界面大小及设备文字大小相乘",
"파일 탐색기 목록의 줄 높이": "文件管理器列表的行高",
"파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "文件管理器列表的行高。紧凑时可能难以用手指点按",
"파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "也可以在文件浏览器顶部的 ⋮ 菜单中更改 (左右 · 上下 · 按钮栏位置 · 按钮配置)",
"파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "文件浏览器窗格: 双窗格 → 按屏幕大小 (竖屏手机为单窗格)。恢复: 设置 > 文件浏览器 > 窗格布局",
"파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器:单击文件会立即打开(选择 · 菜单请长按)。更改:设置 > 文件浏览器 > 点按",

View File

@ -31,7 +31,7 @@ void main() {
final key = GlobalKey();
// 폰처럼 (Android 는 트리 펼치기 화살표가 44 로 넓다 - Windows 로 그리면 폰보다 넉넉해 보인다)
debugDefaultTargetPlatformOverride = TargetPlatform.android;
for (final (layout, width) in [('single', 412.0), ('split', 412.0), ('split', 360.0)]) {
for (final (layout, width, device) in [('single', 412.0, 1.0), ('split', 412.0, 1.0), ('split', 360.0, 1.0), ('split', 412.0, 1.3)]) {
for (final style in ['xplore', 'windows', 'totalcmd']) {
for (final (scale, spacing) in [(100, 'normal'), (150, 'wide'), (80, 'compact')]) {
c.settings
@ -43,17 +43,19 @@ void main() {
tester.view.devicePixelRatio = 1;
await tester.runAsync(() => tester.pumpWidget(MaterialApp(
theme: buildTheme(),
// 기기 글자 크기 (관리자 지시 10/20: 1.3 × 150% 에서 두 창이 모자라면 한 창)
builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: TextScaler.linear(device)), child: child!),
home: RepaintBoundary(key: key, child: ExplorerPage(c: c)),
)));
for (var i = 0; i < 12; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 250)));
await tester.pump();
}
expect(tester.takeException(), isNull, reason: '$layout $width $style $scale% $spacing 넘치지 않음');
expect(tester.takeException(), isNull, reason: '$layout $width ×$device $style $scale% $spacing 넘치지 않음');
final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final image = await tester.runAsync(() => boundary.toImage());
final bytes = await tester.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png));
File('$out/explorer_text_$layout${width.round()}_${style}_${scale}_$spacing.png').writeAsBytesSync(bytes!.buffer.asUint8List());
File('$out/explorer_text_$layout${width.round()}${device == 1 ? '' : '_dev${(device * 10).round()}'}_${style}_${scale}_$spacing.png').writeAsBytesSync(bytes!.buffer.asUint8List());
await tester.pumpWidget(const SizedBox());
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 300)));
}

View File

@ -1151,7 +1151,11 @@ class _ExplorerPageState extends State<ExplorerPage> {
double _rowH() => _look.rowHeightOf(context, textScale: _listTextScale, spacing: rowSpacingFactor(c.settings.explorerRowSpacing));
/// 왼쪽 폴더 트리 + 오른쪽 파일 목록 배치
bool get _split => !widget.rsync && c.settings.explorerLayout == 'split';
bool get _split => !widget.rsync && c.settings.explorerLayout == 'split' && !_splitTight;
/// 관리자 지시 10/20 (28 검토): 글자가 커서 (기기 글자 × 목록 글자 크기) 좁은 화면에 트리 · 목록 두 창이 다 들어가지 않으면
/// 그동안만 한 창 (트리 + 위 경로 막대) 으로 보인다 - 고른 배치 (설정) 는 그대로, 화면이 넓어지거나 글자가 작아지면 다시 두 창. [_body] 가 정한다
bool _splitTight = false;
/// 관리자 지시 10/20: 좁은 화면 (폭 < [narrowSplitWidth]) 의 "폴더 + 파일 목록" 좌우 배치 - 트리 창에 최소 폭을 두고
/// ✔ 칸 · 크기 줄을 숨긴다 (412 폭에서 트리가 77px 이라 폴더 이름이 한 글자도 안 보였다). [_body] 가 정한다
@ -4581,8 +4585,18 @@ class _ExplorerPageState extends State<ExplorerPage> {
_shape = shape;
}
final dual = _dual;
final split = _split;
return LayoutBuilder(builder: (context, box) {
// 두 창이 다 들어가는지: 트리 (화살표 · 아이콘 · 여백 78 + 이름 40) + 목록 (아이콘 · ✔ · 여백 104 + 이름 50), 이름은 글자 배율만큼.
// 1배면 360 폭에도 들어가고, 기기 글자 1.3 × 150% (약 2배) 면 412 폭에도 모자란다
final wantSplit = !widget.rsync && s.explorerLayout == 'split';
final textScale = MediaQuery.textScalerOf(context).scale(14) / 14 * _listTextScale;
final barW = s.explorerToolbar == 'hidden' ? 0.0 : 72.0;
final sideNow = switch (s.explorerOrientation) { 'side' => true, 'stacked' => false, _ => box.maxWidth >= box.maxHeight };
final tightWas = _splitTight;
_splitTight = wantSplit && sideNow && box.maxWidth - barW < 182 + 90 * textScale;
// 바뀌면 이 builder 밖 (버튼 이름 "두 창" 등) 도 한 번 다시 그린다
if (tightWas != _splitTight) WidgetsBinding.instance.addPostFrameCallback((_) => mounted ? setState(() {}) : null);
final split = _split;
// 폭이 바뀌면 (좁은 한 창 ↔ 두 창) 이 builder 만 다시 불린다 - 그때도 새로 붙은 창을 보이게
if (_ready) _revealNewViews();
final side = !(dual || split) ||
@ -5540,7 +5554,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
// 좁은 창 (폰 세로의 좌우 두 창) 에서 깊은 폴더: 들여쓰기는 이름 자리를 남기는 만큼만 (넘치지 않게)
Widget nameCell() => LayoutBuilder(builder: (context, box) {
final fixed = (list ? 0 : chevronWidth) + (look.rich ? 64 : look.iconSize + 12) + 48.0;
// 이름 자리는 48 또는 줄 폭의 40% (관리자 지시 10/20: 글자가 크고 폴더가 깊으면 들여쓰기에 밀려 이름이 몇 글자만 남았다)
final fixed = (list ? 0 : chevronWidth) + (look.rich ? 64 : look.iconSize + 12) + math.max(48.0, box.maxWidth * 0.4);
// 좁은 트리 (slim): 들여쓰기는 반만 (폭의 1/10 까지) - 깊은 폴더에서 이름이 한 글자도 안 보이지 않게
final indent = list
? 0.0

View File

@ -913,7 +913,7 @@ class _SettingsPageState extends State<SettingsPage> {
),
SettingTile(
title: Text(tr('줄 간격')),
subtitle: Text(tr('파일 탐색기 목록의 줄 높이')),
subtitle: Text(tr('파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다')),
trailing: DropdownButton<String>(
value: s.explorerRowSpacing,
items: [

View File

@ -105,6 +105,32 @@ void main() {
}
}
// 관리자 지시 10/20 (28 검토): 글자가 아주 크면 (기기 1.3 × 150%) 좁은 화면에 두 창이 다 들어가지 않는다 - 그동안만 한 창 (설정은 그대로)
for (final style in ['xplore', 'windows', 'totalcmd']) {
testWidgets('글자가 아주 크면 (기기 1.3 × 150% · 넓게, 412 폭, $style) 폴더 + 파일 목록 대신 한 창 - 설정은 split 그대로 · 넘침 없음', (tester) async {
tester.view.physicalSize = const Size(412, 900);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final sub = style == 'totalcmd' ? '[sub]' : 'sub';
final a = style == 'totalcmd' ? 'a' : 'a.txt';
final c = await open(tester, style, sub, device: 1.3, scale: 150, spacing: 'wide');
await tester.pump();
expect(tester.takeException(), isNull);
expect(c.settings.explorerLayout, 'split', reason: '고른 배치는 바꾸지 않는다');
expect(find.text(sub), findsOneWidget, reason: '트리 · 목록 두 창이 아니라 한 창');
expect(find.text(a), findsOneWidget, reason: '한 창 트리에 파일도 보인다');
final para = tester.renderObject<RenderParagraph>(find.text(sub));
// X-plore 는 이름이 줄 폭을 다 쓰고, Windows · TC 는 열 (날짜 · 유형 · 크기) 이 있어 이름 열이 6/13 쯤
expect(para.constraints.maxWidth, greaterThan(style == 'xplore' ? 100 : 55), reason: '이름이 보인다');
await close(tester);
// 글자가 보통이면 같은 폭에서 두 창
await open(tester, style, sub);
await tester.pump();
expect(find.text(sub), findsNWidgets(2), reason: '보통 글자면 폴더 + 파일 목록');
await close(tester);
});
}
testWidgets('넓은 화면 (1280) 의 폴더 + 파일 목록은 그대로 (2 : 3, 트리에도 ✔)', (tester) async {
tester.view.physicalSize = const Size(1280, 900);
tester.view.devicePixelRatio = 1;