환경 설정: 위쪽 묶음 버튼으로 화면 밖 묶음 (다운로드 등) 에도 이동, 묶음 버튼 줄은 위에 고정

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
RyuJongJin 2026-10-04 18:36:55 +09:00
parent bfdb554b31
commit 52deef4118
2 changed files with 36 additions and 7 deletions

View File

@ -264,12 +264,16 @@ class _SettingsPageState extends State<SettingsPage> {
AppActions(c: c, onSettingsPage: true),
]),
),
// 묶음 바로가기 (누르면 그 묶음으로). 스크롤해도 위에 그대로
Padding(
padding: const EdgeInsets.fromLTRB(24, 12, 24, 0),
child: _groupBar(desk),
),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
children: [
// 묶음 바로가기 (누르면 그 묶음으로)
_groupBar(desk),
// 모든 묶음을 한 번에 그린다 (ListView 는 화면 밖 묶음을 그리지 않아 바로가기로 갈 수 없었음)
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(24, 4, 24, 16),
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
_group('general', Icons.tune, tr('일반'), [
// 화면 언어 (읽을 수 없는 언어를 골라도 바로 찾아 되돌릴 수 있게 맨 위)
const LanguageSettings(),
@ -696,7 +700,7 @@ class _SettingsPageState extends State<SettingsPage> {
]),
),
]),
],
]),
),
),
]),
@ -736,7 +740,9 @@ class _SettingsPageState extends State<SettingsPage> {
onPressed: () {
final ctx = _groupKeys[id]?.currentContext;
if (ctx != null) {
Scrollable.ensureVisible(ctx, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
// 그 묶음의 제목이 맨 위에 오게
Scrollable.ensureVisible(ctx,
alignment: 0, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
}
},
),

View File

@ -204,6 +204,29 @@ void main() {
expect(find.byTooltip('작업 기록 숨기기'), findsOneWidget);
});
testWidgets('환경 설정: 위쪽 묶음 버튼을 누르면 (화면 밖 묶음도) 그 묶음으로 간다', (tester) async {
tester.view.physicalSize = const Size(1400, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final c = AppController(PlatformServices(mediaTool: _Tool(), storage: DesktopStorageService()));
await tester.pumpWidget(MaterialApp(home: SettingsPage(c: c)));
await tester.pumpAndSettle();
final groups = ['다운로드', '웹 브라우저', '자막 (AI · 인터넷)', 'MKV 만들기', '화면', '실행 · 종료', '재생', '일반'];
for (final g in groups) {
await tester.tap(find.widgetWithText(ActionChip, g));
await tester.pumpAndSettle();
// 묶음 제목 (버튼이 아닌 카드 안의 글) 이 화면 위쪽에 보인다
final title = find.descendant(of: find.byType(Card), matching: find.text(g)).first;
final y = tester.getTopLeft(title).dy;
expect(y, inInclusiveRange(0, 400), reason: '$g 묶음 제목 위치 $y');
}
// 끝의 묶음 (프로그램 정보) 도 화면에 보인다
await tester.tap(find.widgetWithText(ActionChip, '프로그램 정보'));
await tester.pumpAndSettle();
final about = find.descendant(of: find.byType(Card), matching: find.text('프로그램 정보')).first;
expect(tester.getTopLeft(about).dy, inInclusiveRange(0, 800));
});
testWidgets('환경 설정: 새 항목이 깨지지 않고 보임', (tester) async {
// 스크롤 없이 모두 보이도록 세로로 긴 화면
tester.view.physicalSize = const Size(1400, 3200);