147 좁은 화면의 버튼 줄: 들어가지 않는 버튼은 [더 보기] 메뉴로 · 창 배치 버튼은 늘 줄에 · 처음 값은 자주 쓰는 것부터
- 버튼 줄은 밀어 넘기는 줄이라 아래쪽 버튼 (숨은 항목 표시 등) 을 찾지 못했다 - 들어가는 만큼 보이고 나머지는 [더 보기 ⋯] - 창 배치 (두 창으로 / 좌우 ⇆) 는 사용자 요청대로 늘 줄에 (넘치면 마지막 자리를 내줌) - 처음 값 (버튼 구성을 바꾸지 않았을 때 · [기본값]) 순서: 복사 · 이동 · 삭제 · 새 폴더 · 이름 변경 · 선택 · 찾기 · 창 배치 → 나머지. 사용자가 정한 순서는 그대로 - 시험: 낮은 화면에서 [더 보기] · 메뉴로 숨은 항목 표시 · 창 배치 버튼은 줄에 · 처음 값 순서 · 정한 순서 유지 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>묶음3
parent
6a5aea5ff2
commit
746082db22
|
|
@ -134,13 +134,20 @@ enum ExplorerButton {
|
||||||
/// Rsync 화면의 버튼 (늘 이대로, 두 창 사이)
|
/// Rsync 화면의 버튼 (늘 이대로, 두 창 사이)
|
||||||
static const rsyncButtons = [up, refresh, newFolder, toRight, toLeft, both, monitor];
|
static const rsyncButtons = [up, refresh, newFolder, toRight, toLeft, both, monitor];
|
||||||
|
|
||||||
|
/// 147 (관리자): 처음 값 (버튼 구성을 바꾸지 않았을 때) 은 자주 쓰는 것부터 - 좁은 화면에서 [더 보기] 로 가는 것은 뒤쪽
|
||||||
|
static List<ExplorerButton> get defaultOrder {
|
||||||
|
const first = [copy, move, delete, newFolder, rename, select, search, orient];
|
||||||
|
return [...first, for (final b in explorerValues) if (!first.contains(b)) b];
|
||||||
|
}
|
||||||
|
|
||||||
static List<ExplorerButton> fromSettings(List<String> names) {
|
static List<ExplorerButton> fromSettings(List<String> names) {
|
||||||
final out = [
|
final out = [
|
||||||
for (final n in names)
|
for (final n in names)
|
||||||
for (final b in explorerValues)
|
for (final b in explorerValues)
|
||||||
if (b.name == n) b,
|
if (b.name == n) b,
|
||||||
];
|
];
|
||||||
return out.isEmpty ? explorerValues : out;
|
// 사용자가 정한 순서는 그대로 (비어 있을 때만 처음 값)
|
||||||
|
return out.isEmpty ? defaultOrder : out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2423,7 +2430,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
onPressed: () => set(() {
|
onPressed: () => set(() {
|
||||||
order
|
order
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(ExplorerButton.explorerValues);
|
..addAll(ExplorerButton.defaultOrder);
|
||||||
on
|
on
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(ExplorerButton.explorerValues);
|
..addAll(ExplorerButton.explorerValues);
|
||||||
|
|
@ -3083,17 +3090,72 @@ class _Toolbar extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 147: 다 들어가지 않으면 (좁은 · 낮은 화면) 들어가는 만큼 보이고 나머지는 [더 보기 ⋯] 메뉴로 - 밀어야 보이는 것을 몰라 못 찾던 것
|
||||||
|
Widget more(List<ExplorerButton> rest) => Builder(
|
||||||
|
builder: (context) => Tooltip(
|
||||||
|
message: tr('더 보기'),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
onTap: () async {
|
||||||
|
final box = context.findRenderObject()! as RenderBox;
|
||||||
|
final at = box.localToGlobal(Offset.zero);
|
||||||
|
final pick = await showMenu<ExplorerButton>(
|
||||||
|
context: context,
|
||||||
|
position: RelativeRect.fromLTRB(at.dx + box.size.width, at.dy, at.dx + box.size.width, at.dy),
|
||||||
|
items: [
|
||||||
|
for (final b in rest)
|
||||||
|
PopupMenuItem(
|
||||||
|
value: b,
|
||||||
|
enabled: enabled(b),
|
||||||
|
height: 40,
|
||||||
|
child: Row(children: [
|
||||||
|
Icon(_icon(b), size: 18, color: selected(b) ? JjColors.accent : null),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(tr(_label(b))),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (pick != null) onPressed(pick);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 72,
|
||||||
|
height: 58,
|
||||||
|
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
const Icon(Icons.more_horiz, size: 22),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(tr('더 보기'), style: const TextStyle(fontSize: 10.5, height: 1.1)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: JjColors.panel,
|
color: JjColors.panel,
|
||||||
width: vertical ? 76 : null,
|
width: vertical ? 76 : null,
|
||||||
height: vertical ? null : 62,
|
height: vertical ? null : 62,
|
||||||
child: SingleChildScrollView(
|
padding: const EdgeInsets.all(2),
|
||||||
scrollDirection: vertical ? Axis.vertical : Axis.horizontal,
|
child: LayoutBuilder(builder: (context, box) {
|
||||||
padding: const EdgeInsets.all(2),
|
final room = vertical ? box.maxHeight : box.maxWidth;
|
||||||
child: vertical
|
final each = vertical ? 58.0 : 72.0;
|
||||||
? Column(children: [for (final b in buttons) btn(b)])
|
var shown = buttons;
|
||||||
: Row(children: [for (final b in buttons) btn(b)]),
|
var rest = const <ExplorerButton>[];
|
||||||
),
|
if (room.isFinite && buttons.length * each > room) {
|
||||||
|
final fit = (room / each).floor() - 1; // 하나는 [더 보기] 자리
|
||||||
|
if (fit >= 1) {
|
||||||
|
shown = buttons.take(fit).toList();
|
||||||
|
rest = buttons.skip(fit).toList();
|
||||||
|
// 창 배치 버튼은 늘 줄에 (사용자가 가운데 아이콘 모음에 두라고 함, 10/9) - 넘치면 마지막 자리를 내준다
|
||||||
|
if (rest.contains(ExplorerButton.orient)) {
|
||||||
|
rest = [shown.removeLast(), ...rest.where((b) => b != ExplorerButton.orient)];
|
||||||
|
shown = [...shown, ExplorerButton.orient];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final children = [for (final b in shown) btn(b), if (rest.isNotEmpty) more(rest)];
|
||||||
|
return vertical ? Column(children: children) : Row(children: children);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,34 @@ void main() {
|
||||||
expect(find.textContaining('이미 이 폴더에 있어 건너뜀: b.txt'), findsOneWidget, reason: '끝난 알림에도');
|
expect(find.textContaining('이미 이 폴더에 있어 건너뜀: b.txt'), findsOneWidget, reason: '끝난 알림에도');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('147: 버튼 줄이 다 들어가지 않으면 (낮은 화면) 나머지는 [더 보기] 메뉴로 - "숨은 항목 표시" 도 누를 수 있다', (tester) async {
|
||||||
|
tester.view.physicalSize = const Size(1200, 560);
|
||||||
|
tester.view.devicePixelRatio = 1;
|
||||||
|
addTearDown(tester.view.reset);
|
||||||
|
final before = c.settings.explorerShowHidden;
|
||||||
|
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
|
||||||
|
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
|
||||||
|
expect(tester.takeException(), isNull, reason: '넘침 없음');
|
||||||
|
expect(find.text('더 보기'), findsOneWidget);
|
||||||
|
expect(find.text('숨은 항목 표시'), findsNothing, reason: '들어가지 않는 버튼은 줄에 없음');
|
||||||
|
await tester.tap(find.text('더 보기'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text('숨은 항목 표시'), findsOneWidget);
|
||||||
|
await tester.runAsync(() => tester.tap(find.text('숨은 항목 표시')));
|
||||||
|
await settle(tester, () => c.settings.explorerShowHidden != before, rounds: 20);
|
||||||
|
expect(c.settings.explorerShowHidden, !before);
|
||||||
|
// 창 배치 버튼은 좁아도 늘 줄에 (사용자 요청) · 처음 값은 자주 쓰는 것부터 (복사 · 이동 · 삭제 …)
|
||||||
|
expect(find.byTooltip('창 배치: 좌우 (누르면 위아래)'), findsOneWidget);
|
||||||
|
expect(ExplorerButton.fromSettings(const []).take(8).map((b) => b.name),
|
||||||
|
['copy', 'move', 'delete', 'newFolder', 'rename', 'select', 'search', 'orient']);
|
||||||
|
expect(ExplorerButton.fromSettings(const ['hidden', 'copy']), [ExplorerButton.hidden, ExplorerButton.copy],
|
||||||
|
reason: '사용자가 정한 순서는 그대로');
|
||||||
|
// 넉넉한 화면에서는 [더 보기] 없이 모두
|
||||||
|
tester.view.physicalSize = const Size(1600, 1400);
|
||||||
|
await settle(tester, () => find.text('더 보기').evaluate().isEmpty, rounds: 20); // 숨은 항목을 바꿔 다시 읽는 중일 수 있어 정해진 만큼만
|
||||||
|
expect(find.text('더 보기'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('146 · 37-2: 없는 경로를 넣으면 창을 닫지 않고 칸 아래에 "폴더가 없거나 열 수 없습니다" · 고친 경로로 간다', (tester) async {
|
testWidgets('146 · 37-2: 없는 경로를 넣으면 창을 닫지 않고 칸 아래에 "폴더가 없거나 열 수 없습니다" · 고친 경로로 간다', (tester) async {
|
||||||
tester.view.physicalSize = const Size(1600, 900);
|
tester.view.physicalSize = const Size(1600, 900);
|
||||||
tester.view.devicePixelRatio = 1;
|
tester.view.devicePixelRatio = 1;
|
||||||
|
|
|
||||||
|
|
@ -214,24 +214,29 @@ void main() {
|
||||||
addTearDown(tester.view.reset);
|
addTearDown(tester.view.reset);
|
||||||
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
|
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
|
||||||
await settle(tester, () => find.text('a.txt').evaluate().isNotEmpty);
|
await settle(tester, () => find.text('a.txt').evaluate().isNotEmpty);
|
||||||
await act(tester, () => tester.tap(find.byTooltip('두 창으로 보기 (좌우)')));
|
// 그 버튼을 누른다: 버튼 줄에 있으면 바로, 다 들어가지 않아 [더 보기] 메뉴에 있으면 메뉴에서 (147)
|
||||||
await settle(tester, () => false, rounds: 5);
|
Future<void> press(String tooltip, String label) async {
|
||||||
expect((c.settings.explorerLayout, c.settings.explorerOrientation), ('dual', 'side'));
|
if (find.byTooltip(tooltip).evaluate().isNotEmpty) {
|
||||||
expect(find.byTooltip('창 배치: 좌우 (누르면 위아래)'), findsOneWidget, reason: '폰 세로에서도 좌우');
|
await act(tester, () => tester.tap(find.byTooltip(tooltip)));
|
||||||
|
} else {
|
||||||
|
await tester.tap(find.text('더 보기'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text(label), findsOneWidget, reason: '[더 보기] 메뉴에 지금 배치가 보임: $label');
|
||||||
|
await act(tester, () => tester.tap(find.text(label)));
|
||||||
|
}
|
||||||
|
await settle(tester, () => false, rounds: 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
await press('두 창으로 보기 (좌우)', '두 창으로');
|
||||||
|
expect((c.settings.explorerLayout, c.settings.explorerOrientation), ('dual', 'side'), reason: '폰 세로에서도 좌우');
|
||||||
expect(find.text('a.txt'), findsNWidgets(2));
|
expect(find.text('a.txt'), findsNWidgets(2));
|
||||||
final left = tester.getCenter(find.text('a.txt').first), right = tester.getCenter(find.text('a.txt').last);
|
final left = tester.getCenter(find.text('a.txt').first), right = tester.getCenter(find.text('a.txt').last);
|
||||||
expect(right.dx, greaterThan(left.dx));
|
expect(right.dx, greaterThan(left.dx));
|
||||||
await act(tester, () => tester.tap(find.byTooltip('창 배치: 좌우 (누르면 위아래)')));
|
await press('창 배치: 좌우 (누르면 위아래)', '좌우 ⇆');
|
||||||
await settle(tester, () => false, rounds: 5);
|
|
||||||
expect(c.settings.explorerOrientation, 'stacked');
|
expect(c.settings.explorerOrientation, 'stacked');
|
||||||
expect(find.byTooltip('창 배치: 위아래 (누르면 좌우)'), findsOneWidget);
|
|
||||||
final top = tester.getCenter(find.text('a.txt').first), bottom = tester.getCenter(find.text('a.txt').last);
|
final top = tester.getCenter(find.text('a.txt').first), bottom = tester.getCenter(find.text('a.txt').last);
|
||||||
expect(bottom.dy, greaterThan(top.dy));
|
expect(bottom.dy, greaterThan(top.dy));
|
||||||
// 위아래면 버튼 줄이 가로로 밀어 보는 줄 - 보이게 한 뒤 누른다
|
await press('창 배치: 위아래 (누르면 좌우)', '위아래 ⇅');
|
||||||
await tester.ensureVisible(find.byTooltip('창 배치: 위아래 (누르면 좌우)'));
|
|
||||||
await tester.pump();
|
|
||||||
await act(tester, () => tester.tap(find.byTooltip('창 배치: 위아래 (누르면 좌우)')));
|
|
||||||
await settle(tester, () => false, rounds: 5);
|
|
||||||
expect(c.settings.explorerOrientation, 'side');
|
expect(c.settings.explorerOrientation, 'side');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,9 @@ void main() {
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
await tester.tap(find.text('버튼 구성').last);
|
await tester.tap(find.text('버튼 구성').last);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
// 처음 값 순서 (자주 쓰는 것부터) 에서 뒤쪽이라 목록을 내려 보이게 한 뒤
|
||||||
|
await tester.scrollUntilVisible(find.widgetWithText(CheckboxListTile, 'MKV 목록에 추가'), 80,
|
||||||
|
scrollable: find.descendant(of: find.byType(AlertDialog), matching: find.byType(Scrollable)).first);
|
||||||
await tester.tap(find.widgetWithText(CheckboxListTile, 'MKV 목록에 추가'));
|
await tester.tap(find.widgetWithText(CheckboxListTile, 'MKV 목록에 추가'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.tap(find.widgetWithText(FilledButton, '확인'));
|
await tester.tap(find.widgetWithText(FilledButton, '확인'));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue