From cb50d284867c5cea6a205929d1741639e01936a4 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:54:47 +0900 Subject: [PATCH] =?UTF-8?q?WIP=20=ED=81=B0=20=EA=B8=80=EC=9E=90=20=C2=B7?= =?UTF-8?q?=20=EC=A4=84=20=EA=B0=84=EA=B2=A9=20(=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20,=2028=20=EA=B2=80?= =?UTF-8?q?=ED=86=A0):=20=E2=91=A0=20=EA=B8=80=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=EC=BB=A4=EC=84=9C=20(=EA=B8=B0=EA=B8=B0=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=20=C3=97=20=EB=AA=A9=EB=A1=9D=20=EA=B8=80=EC=9E=90=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0)=20=EC=A2=81=EC=9D=80=20=ED=99=94=EB=A9=B4=EC=97=90?= =?UTF-8?q?=20"=ED=8F=B4=EB=8D=94=20+=20=ED=8C=8C=EC=9D=BC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D"=20=EB=91=90=20=EC=B0=BD=EC=9D=B4=20=EB=8B=A4=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=80=EC=A7=80=20=EC=95=8A=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EA=B7=B8=EB=8F=99=EC=95=88=EB=A7=8C=20=ED=95=9C=20?= =?UTF-8?q?=EC=B0=BD=20(=ED=8A=B8=EB=A6=AC=20+=20=EC=9C=84=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=89=EB=8C=80)=20-=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9D=80=20split=20=EA=B7=B8=EB=8C=80=EB=A1=9C,=20=EB=84=93?= =?UTF-8?q?=EC=96=B4=EC=A7=80=EA=B1=B0=EB=82=98=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=9E=91=EC=95=84=EC=A7=80=EB=A9=B4=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EB=91=90=20=EC=B0=BD=20(d3=20=ED=8C=90=EB=8B=A8:?= =?UTF-8?q?=20=EB=91=90=20=EC=B0=BD=20=ED=95=84=EC=9A=94=20=ED=8F=AD=20=3D?= =?UTF-8?q?=20182=20+=2090=20=C3=97=20=EA=B8=80=EC=9E=90=20=EB=B0=B0?= =?UTF-8?q?=EC=9C=A8,=201=EB=B0=B0=EB=8A=94=20360=20=ED=8F=AD=EC=97=90?= =?UTF-8?q?=EB=8F=84=20=EB=93=A4=EC=96=B4=EA=B0=80=EA=B3=A0=201.3=20=C3=97?= =?UTF-8?q?=20150%=20=EB=8A=94=20412=20=EC=97=90=EB=8F=84=20=EB=AA=A8?= =?UTF-8?q?=EC=9E=90=EB=9E=8C)=20=E2=91=A1=20=ED=8A=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=9E=90=EB=A6=AC=EB=A5=BC=2048=20?= =?UTF-8?q?=EB=98=90=EB=8A=94=20=EC=A4=84=20=ED=8F=AD=EC=9D=98=2040%=20?= =?UTF-8?q?=EB=A1=9C=20(=EA=B9=8A=EC=9D=80=20=ED=8F=B4=EB=8D=94=20=C2=B7?= =?UTF-8?q?=20=ED=81=B0=20=EA=B8=80=EC=9E=90=EC=97=90=EC=84=9C=20=EB=93=A4?= =?UTF-8?q?=EC=97=AC=EC=93=B0=EA=B8=B0=EC=97=90=20=EB=B0=80=EB=A0=A4=20?= =?UTF-8?q?=EB=AA=87=20=EA=B8=80=EC=9E=90=EB=A7=8C=20=EB=82=A8=EB=8D=98=20?= =?UTF-8?q?=EA=B2=83)=20=E2=91=A2=20=EC=A4=84=20=EA=B0=84=EA=B2=A9=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=97=90=20"=EC=A2=81=EA=B2=8C=EB=8A=94=20?= =?UTF-8?q?=EC=86=90=EA=B0=80=EB=9D=BD=EC=9C=BC=EB=A1=9C=20=EB=88=84?= =?UTF-8?q?=EB=A5=B4=EA=B8=B0=20=EC=96=B4=EB=A0=A4=EC=9A=B8=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EC=8A=B5=EB=8B=88=EB=8B=A4"=20(=ED=95=98=ED=95=9C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=A7=89=EC=A7=80=20=EC=95=8A=EC=9D=8C)?= =?UTF-8?q?=20=C2=B7=20=EC=8B=9C=ED=97=98=20=C2=B7=20=EC=BA=A1=EC=B2=98=20?= =?UTF-8?q?(=EA=B8=B0=EA=B8=B0=201.3)=20(=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- assets/l10n/en.json | 2 +- assets/l10n/ja.json | 2 +- assets/l10n/zh-Hans.json | 2 +- integration_test/explorer_text_shot_test.dart | 8 +++--- lib/ui/explorer_page.dart | 21 ++++++++++++--- lib/ui/settings_page.dart | 2 +- test/explorer_text_scale_test.dart | 26 +++++++++++++++++++ 7 files changed, 53 insertions(+), 10 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 83d6e0b..346c9e7 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -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", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 876e41a..9b1decb 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -2698,7 +2698,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラーのタップ: 1 回で選択 → 1 回ですぐ開く。元に戻す: 環境設定 > ファイルエクスプローラー > タップ", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "ファイルエクスプローラーの 2 画面配置: 画面の形に合わせる → 左右。元に戻す: ファイルエクスプローラーの [左右 ⇆ / 上下 ⇅] ボタンまたは画面配置", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "ファイルエクスプローラー一覧の名前・サイズの文字のみ。アプリの画面サイズ・端末の文字サイズと掛け合わされます", - "파일 탐색기 목록의 줄 높이": "ファイルエクスプローラー一覧の行の高さ", + "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "ファイルエクスプローラー一覧の行の高さ。狭くすると指でタップしにくい場合があります", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "ファイルエクスプローラー上部の ⋮ メニューからも変えられます (左右 · 上下 · ボタン列の位置 · ボタン構成)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "ファイルエクスプローラーの画面: 2 画面 → 画面サイズに合わせる (縦向きのスマホは 1 画面)。元に戻す: 環境設定 > ファイルエクスプローラー > 画面配置", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラー: ファイルを 1 回タップするとすぐ開きます (選択 · メニューは長押し)。変更: 環境設定 > ファイルエクスプローラー > タップ", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 5157828..45a0ce0 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -2698,7 +2698,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器点击: 单击选择 → 单击直接打开。恢复: 设置 > 文件浏览器 > 点击", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "文件浏览器双窗格布局: 跟随屏幕形状 → 左右。恢复: 文件浏览器中的 [左右 ⇆ / 上下 ⇅] 按钮或窗格布局", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "仅文件管理器列表中的名称和大小文字。会与应用界面大小及设备文字大小相乘", - "파일 탐색기 목록의 줄 높이": "文件管理器列表的行高", + "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "文件管理器列表的行高。紧凑时可能难以用手指点按", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "也可以在文件浏览器顶部的 ⋮ 菜单中更改 (左右 · 上下 · 按钮栏位置 · 按钮配置)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "文件浏览器窗格: 双窗格 → 按屏幕大小 (竖屏手机为单窗格)。恢复: 设置 > 文件浏览器 > 窗格布局", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器:单击文件会立即打开(选择 · 菜单请长按)。更改:设置 > 文件浏览器 > 点按", diff --git a/integration_test/explorer_text_shot_test.dart b/integration_test/explorer_text_shot_test.dart index 20a208c..50bf186 100644 --- a/integration_test/explorer_text_shot_test.dart +++ b/integration_test/explorer_text_shot_test.dart @@ -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.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.delayed(const Duration(milliseconds: 300))); } diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index ecac2a9..c6a39c4 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -1151,7 +1151,11 @@ class _ExplorerPageState extends State { 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 { _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 { // 좁은 창 (폰 세로의 좌우 두 창) 에서 깊은 폴더: 들여쓰기는 이름 자리를 남기는 만큼만 (넘치지 않게) 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 diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index 7778aa0..3708e9a 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -913,7 +913,7 @@ class _SettingsPageState extends State { ), SettingTile( title: Text(tr('줄 간격')), - subtitle: Text(tr('파일 탐색기 목록의 줄 높이')), + subtitle: Text(tr('파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다')), trailing: DropdownButton( value: s.explorerRowSpacing, items: [ diff --git a/test/explorer_text_scale_test.dart b/test/explorer_text_scale_test.dart index 6fa2cf3..c9cceac 100644 --- a/test/explorer_text_scale_test.dart +++ b/test/explorer_text_scale_test.dart @@ -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(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;