WIP 253 글자 크게 (1.6배) 해도 넘치지 않게: 탐색기 줄 높이 · 설정 고르기 상자 · 위 막대 · 인코딩 · 모니터링 · 언어 · 화면 전체 시험 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>backup_묶음6_d46e089
parent
56816a8637
commit
d9d7b312d1
|
|
@ -360,10 +360,16 @@ class AppNavButtons extends StatelessWidget {
|
|||
child: icon,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontSize: 10, color: here ? JjColors.accent : JjColors.textDim)),
|
||||
// 253: 글자가 크면 칸 (높이 46) 아래로 넘쳤다 - 남는 높이에 맞게 줄인다
|
||||
Flexible(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontSize: 10, color: here ? JjColors.accent : JjColors.textDim)),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ enum ExplorerStyle {
|
|||
static ExplorerStyle of(String name) => values.firstWhere((s) => s.name == name, orElse: () => xplore);
|
||||
|
||||
double get rowHeight => switch (this) { xplore => 52, windows => 34, totalcmd => 26 };
|
||||
|
||||
/// 253: 줄 높이 - 기기 글자 크기를 키우면 그만큼 (고정 높이라 1.6배에서 이름 · 크기 두 줄이 넘쳤다)
|
||||
double rowHeightOf(BuildContext context) {
|
||||
final s = MediaQuery.textScalerOf(context).scale(fontSize) / fontSize;
|
||||
return s <= 1 ? rowHeight : (rowHeight * s).roundToDouble();
|
||||
}
|
||||
double get iconSize => switch (this) { xplore => 30, windows => 20, totalcmd => 16 };
|
||||
double get indent => switch (this) { xplore => 16, windows => 14, totalcmd => 12 };
|
||||
double get fontSize => switch (this) { xplore => 14, windows => 13, totalcmd => 12.5 };
|
||||
|
|
|
|||
|
|
@ -782,7 +782,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
final pos = pane.scroll.position;
|
||||
// 180: 끝 (maxScrollExtent) 으로 자르지 않는다 - 줄이 막 늘어난 때는 끝이 아직 앞의 것이라 보던 폴더로 못 간다.
|
||||
// 넘치면 다음 배치 때 실제 끝으로 맞춰진다
|
||||
pane.scroll.jumpTo((i * _look.rowHeight - pos.viewportDimension / 4).clamp(0.0, double.infinity));
|
||||
pane.scroll.jumpTo((i * _look.rowHeightOf(context) - pos.viewportDimension / 4).clamp(0.0, double.infinity));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -3094,7 +3094,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
ListView.builder(
|
||||
controller: pane.scroll,
|
||||
itemCount: rows.length,
|
||||
itemExtent: look.rowHeight,
|
||||
itemExtent: look.rowHeightOf(context),
|
||||
itemBuilder: (_, k) => _rowView(pane, rows[k], compact: foldersOnly),
|
||||
),
|
||||
// 읽는 중 (WebDAV 서버를 기다릴 때 등). 180: 목록 위에 겹쳐 그린다 (끼어들면 목록이 밀려 다음 것을 누르려다 엉뚱한 것을 누른다)
|
||||
|
|
@ -3135,7 +3135,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
: ListView.builder(
|
||||
controller: pane.listScroll,
|
||||
itemCount: rows.length,
|
||||
itemExtent: look.rowHeight,
|
||||
itemExtent: look.rowHeightOf(context),
|
||||
itemBuilder: (_, k) => _rowView(pane, rows[k], list: true),
|
||||
),
|
||||
),
|
||||
|
|
@ -3383,7 +3383,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
// 손가락으로 누르는 기기 (Android) 는 폭도 넓게
|
||||
Widget chevron() => SizedBox(
|
||||
width: chevronWidth,
|
||||
height: look.rowHeight,
|
||||
height: look.rowHeightOf(context),
|
||||
child: e.isDir && !list && !row.isUp
|
||||
? (loading
|
||||
? const Center(
|
||||
|
|
@ -3414,7 +3414,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
if (!list) chevron(),
|
||||
SizedBox(
|
||||
width: look.rich ? 56 : look.iconSize + 6,
|
||||
height: look.rowHeight - 8,
|
||||
height: look.rowHeightOf(context) - 8,
|
||||
child: Center(child: _icon(e, row.isRoot, open, up: row.isUp)),
|
||||
),
|
||||
SizedBox(width: look.rich ? 8 : 6),
|
||||
|
|
|
|||
|
|
@ -571,11 +571,17 @@ class _EncodeBar extends StatelessWidget {
|
|||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Text(t, style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||
);
|
||||
// 253: 글자가 크면 고르기 상자가 화면보다 넓어져 넘쳤다 - 제 폭 (가장 긴 항목) 을 쓰되 화면 폭을 넘지 않게 (글은 …)
|
||||
final maxDrop = (MediaQuery.sizeOf(context).width - 40).clamp(120.0, double.infinity);
|
||||
Widget drop<T>(T value, List<T> items, String Function(T) text,
|
||||
ValueChanged<T> onChanged, {bool Function(T)? enabled}) =>
|
||||
DropdownButton<T>(
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxDrop),
|
||||
child: IntrinsicWidth(
|
||||
child: DropdownButton<T>(
|
||||
value: value,
|
||||
isDense: true,
|
||||
isExpanded: true,
|
||||
underline: const SizedBox(),
|
||||
style: const TextStyle(fontSize: 13, color: JjColors.text),
|
||||
items: [
|
||||
|
|
@ -585,12 +591,16 @@ class _EncodeBar extends StatelessWidget {
|
|||
enabled: enabled?.call(i) ?? true,
|
||||
child: Text(
|
||||
text(i) + ((enabled?.call(i) ?? true) ? '' : tr(' (사용 불가)')),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: (enabled?.call(i) ?? true) ? null : JjColors.textDim),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: locked ? null : (v) => onChanged(v as T),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final compact = isCompact(context);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,13 @@ class LanguageSettings extends StatelessWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(72, 0, 16, 8),
|
||||
child: Wrap(spacing: 12, runSpacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||
DropdownButton<String>(
|
||||
// 253: 글자가 크면 (긴 "시스템 언어 따르기 (…)") 화면보다 넓어져 넘쳤다 - 남는 폭에 맞게 줄인다
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: (MediaQuery.sizeOf(context).width - 100).clamp(120.0, double.infinity)),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: DropdownButton<String>(
|
||||
value: i18n.chosen == I18nController.system || i18n.available.contains(i18n.chosen)
|
||||
? i18n.chosen
|
||||
: 'ko',
|
||||
|
|
@ -42,6 +48,8 @@ class LanguageSettings extends StatelessWidget {
|
|||
DropdownMenuItem(value: code, child: Text(I18nController.nativeName(code))),
|
||||
],
|
||||
onChanged: (v) => i18n.apply(v!),
|
||||
),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: i18n.canTranslate ? () => addLanguageDialog(context) : null,
|
||||
|
|
|
|||
|
|
@ -642,8 +642,8 @@ class _CopyCardState extends State<_CopyCard> {
|
|||
]),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
// 253: 글자가 크거나 폭이 좁으면 다음 줄로 (한 줄 Row 는 넘쳤다)
|
||||
Wrap(alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, spacing: 6, runSpacing: 4, 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),
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ class SettingTile extends StatelessWidget {
|
|||
|
||||
Widget _tile(BuildContext context) {
|
||||
final t = trailing;
|
||||
if (t == null || _small(t) || !isCompact(context)) {
|
||||
// 253: 글자를 크게 (1.2배 넘게) 하면 넓은 화면에서도 고르기 상자를 설명 아래로 (오른쪽에 두면 넘쳤다)
|
||||
final large = MediaQuery.textScalerOf(context).scale(10) > 12;
|
||||
if (t == null || _small(t) || (!isCompact(context) && !large)) {
|
||||
return ListTile(
|
||||
leading: leading,
|
||||
title: title,
|
||||
|
|
@ -60,7 +62,8 @@ class SettingTile extends StatelessWidget {
|
|||
subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
|
||||
?subtitle,
|
||||
const SizedBox(height: 6),
|
||||
Align(alignment: Alignment.centerLeft, child: t),
|
||||
// 253: 그래도 화면보다 넓으면 (긴 항목 · 큰 글자) 줄여서 다 보이게
|
||||
Align(alignment: Alignment.centerLeft, child: FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: t)),
|
||||
]),
|
||||
enabled: enabled,
|
||||
dense: dense,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||
import 'package:jj_mkvmaker/app/download_manager.dart';
|
||||
import 'package:jj_mkvmaker/app/settings.dart';
|
||||
import 'package:jj_mkvmaker/core/models.dart';
|
||||
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
||||
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
||||
import 'package:jj_mkvmaker/services/platform_services.dart';
|
||||
import 'package:jj_mkvmaker/ui/downloads_page.dart';
|
||||
import 'package:jj_mkvmaker/ui/explorer_page.dart';
|
||||
import 'package:jj_mkvmaker/ui/home_page.dart';
|
||||
import 'package:jj_mkvmaker/ui/monitor_page.dart';
|
||||
import 'package:jj_mkvmaker/ui/settings_page.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// 253: 글자를 크게 (기기 설정 글꼴 크기 · 접근성) 해도 화면이 넘치지 않는다 - 줄 높이가 고정이던 목록이 넘쳤다
|
||||
void main() {
|
||||
late Directory tmp;
|
||||
setUp(() => tmp = Directory.systemTemp.createTempSync('jj_big_'));
|
||||
tearDown(() => tmp.deleteSync(recursive: true));
|
||||
|
||||
final pages = <String, Widget Function(AppController, DownloadManager)>{
|
||||
'MKV': (c, d) => HomePage(c: c),
|
||||
'파일 탐색기': (c, d) => ExplorerPage(c: c),
|
||||
'Rsync': (c, d) => ExplorerPage(c: c, rsync: true),
|
||||
'모니터링': (c, d) => MonitorPage(c: c),
|
||||
'다운로드': (c, d) => DownloadsPage(d: d),
|
||||
'환경 설정': (c, d) => SettingsPage(c: c),
|
||||
};
|
||||
for (final MapEntry(key: name, value: page) in pages.entries) {
|
||||
for (final size in [const Size(412, 900), const Size(900, 412), const Size(1280, 800)]) {
|
||||
testWidgets('253: $name (${size.width.round()}×${size.height.round()}, 글자 1.6배) 넘치지 않는다', (tester) async {
|
||||
tester.view.physicalSize = size;
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
const pp = MethodChannel('plugins.flutter.io/path_provider');
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(pp, (_) async => tmp.path);
|
||||
addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(pp, null));
|
||||
File(p.join(tmp.path, '아주 긴 이름의 동영상 파일 - 시험용 2026.mp4')).writeAsStringSync('v');
|
||||
Directory(p.join(tmp.path, 'sub')).createSync();
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..explorerPaths = [tmp.path, tmp.path]
|
||||
..rsyncPaths = [tmp.path, tmp.path]
|
||||
..copyTasks = [CopyTask(id: '1', sources: [tmp.path], dest: p.join(tmp.path, 'sub'), method: 'rsync', options: '-av')]
|
||||
..liveSyncPairs = [LiveSyncPair(tmp.path, p.join(tmp.path, 'x'))];
|
||||
c.videos.add(VideoItem(p.join(tmp.path, '아주 긴 이름의 동영상 파일 - 시험용 2026.mp4')));
|
||||
c.selected = c.videos.first;
|
||||
final d = DownloadManager(backends: const [], settings: () => c.settings, readClipboard: () async => null);
|
||||
addTearDown(d.dispose);
|
||||
await tester.runAsync(() => tester.pumpWidget(MaterialApp(
|
||||
builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: const TextScaler.linear(1.6)), child: child!),
|
||||
home: page(c, d))));
|
||||
for (var i = 0; i < 15; i++) {
|
||||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
||||
await tester.pump();
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
// 화면을 내리고 남은 타이머 (탐색기 목록 읽기 등) 가 끝나게
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue