import 'package:flutter/material.dart'; import '../../app/app_controller.dart'; import '../../services/app_shell.dart'; import '../update_dialog.dart'; import '../../app/settings.dart'; import '../../platform/android/android_shell.dart' show applyScreenOrientation; import '../language_settings.dart'; import '../theme.dart'; import '../../l10n/tr.dart'; import '../setting_tile.dart'; // 약속 (설정 B): 환경 설정의 분야 - 일반 (언어 · 알림 · 앱 아이콘) · 화면. 바꾸면 바로 설정에 저장 (c.updateSettings) /// 환경 설정 > 일반 class GeneralSettingsSection extends StatefulWidget { const GeneralSettingsSection({super.key, required this.c, required this.desk}); final AppController c; /// PC (Android 가 아님) - 창 · 트레이 · 탐색기 · 다운로드 · 내장 Chrome 설정 final bool desk; @override State createState() => _GeneralSettingsSectionState(); } class _GeneralSettingsSectionState extends State { AppController get c => widget.c; bool get desk => widget.desk; @override Widget build(BuildContext context) { final s = c.settings; return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ // 화면 언어 (읽을 수 없는 언어를 골라도 바로 찾아 되돌릴 수 있게 맨 위) const LanguageSettings(), // 102: 물어 정한 것 · 1차 판단은 모두 여기서 바꿀 수 있다 (기본값 = 그 답) SwitchListTile( value: s.general.migrationNotice, onChanged: (v) => c.updateSettings((x) => x.general.migrationNotice = v), title: Text(tr('업데이트로 바뀐 기본값 알림')), subtitle: Text(tr('새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다')), ), SwitchListTile( value: s.general.rememberPasswords, onChanged: (v) => c.updateSettings((x) => x.general.rememberPasswords = v), title: Text(tr('비밀번호 기억 (안전 저장소)')), subtitle: Text(tr('WebDAV · OpenSubtitles 비밀번호를 이 기기의 안전 저장소에 둡니다. 끄면 저장하지 않고 (있던 것도 지움) 앱을 켤 때마다 다시 넣습니다')), ), AppIconTile(c: c, desk: desk), if (c.services.updater != null) ...[ SettingTile( leading: Icon(Icons.system_update_alt, color: JjColors.accent), title: FutureBuilder( future: c.services.updater!.currentVersion(), builder: (_, v) => Text('JJ_MKVMaker v${v.data ?? '…'}'), ), subtitle: Text(tr('GitHub 에 올려 둔 모든 버전 중에서 골라 설치합니다 (새 버전으로 · 예전 정상 버전으로 되돌리기). ' '바꾸기 전 버전의 설정은 보관해 두었다가, 그 버전으로 돌아오면 되살립니다')), isThreeLine: true, trailing: Wrap(spacing: 8, crossAxisAlignment: WrapCrossAlignment.center, children: [ UpdateCheckButton(c: c), FilledButton.icon( onPressed: () => chooseVersion(context, c), icon: const Icon(Icons.system_update_alt, size: 18), label: Text(tr('업데이트 · 버전 고르기')), ), ]), ), SwitchListTile( value: s.run.autoCheckUpdates, onChanged: (v) => c.updateSettings((x) => x.run.autoCheckUpdates = v), title: Text(tr('시작할 때 새 버전 확인 (하루 한 번)')), ), ], SettingTile( title: Text(tr('홈 화면')), subtitle: Text(tr('프로그램을 켰을 때, 그리고 위쪽 왼쪽 JJ 아이콘을 눌렀을 때 보일 화면')), trailing: DropdownButton( value: s.display.startScreen, items: [ DropdownMenuItem(value: 'home', child: Text(tr('MKV 화면 (기본)'))), DropdownMenuItem(value: 'browser', child: Text(tr('웹 브라우저 (홈 주소)'))), DropdownMenuItem(value: 'files', child: Text(tr('파일 탐색기'))), if (s.display.components.contains('tor') || s.display.startScreen == 'tor') DropdownMenuItem(value: 'tor', child: Text(tr('Tor 브라우저'))), ], onChanged: (v) => c.updateSettings((x) => x.display.startScreen = v!), ), ), ]); } } /// 환경 설정 > 화면 class DisplaySettingsSection extends StatefulWidget { const DisplaySettingsSection({super.key, required this.c, required this.desk}); final AppController c; /// PC (Android 가 아님) - 창 · 트레이 · 탐색기 · 다운로드 · 내장 Chrome 설정 final bool desk; @override State createState() => _DisplaySettingsSectionState(); } class _DisplaySettingsSectionState extends State { AppController get c => widget.c; bool get desk => widget.desk; @override Widget build(BuildContext context) { final s = c.settings; return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ if (!desk) SettingTile( title: Text(tr('화면 방향')), subtitle: Text(tr('가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)')), trailing: DropdownButton( value: s.display.orientation, items: [ DropdownMenuItem(value: 'auto', child: Text(tr('자동 (기본)'))), DropdownMenuItem(value: 'landscape', child: Text(tr('가로 고정'))), DropdownMenuItem(value: 'portrait', child: Text(tr('세로 고정'))), ], onChanged: (v) { c.updateSettings((x) => x.display.orientation = v!); applyScreenOrientation(v!); }, ), ), SettingTile( title: Text(tr('기본 화면 크기')), subtitle: Text(trf('글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 ' '(지금 {0}%)', [(s.display.scale * 100).round()])), trailing: DropdownButton( value: s.display.scaleDefault, items: [ for (var v = DisplaySettings.scaleMin; v <= DisplaySettings.scaleMax + 0.001; v += 0.1) DropdownMenuItem( value: DisplaySettings.clampScale(v), child: Text('${(v * 100).round()}%${(v - 1).abs() < 0.001 ? tr(' (보통)') : ''}')), // 5% 단위로 저장된 값이 목록에 없을 때 if (((s.display.scaleDefault * 100).round() % 10) != 0) DropdownMenuItem( value: s.display.scaleDefault, child: Text('${(s.display.scaleDefault * 100).round()}%')), ], // 기본 크기를 바꾸면 지금 크기도 그 크기로 onChanged: (v) => c.updateSettings((x) => x ..display.scaleDefault = v! ..display.scale = v), ), ), // X-plore 비교 5-1 ⑦ (관리자 지시 10/21): 화면 밝기 - 처음 값 어둡게 (예전 그대로), 다 끝날 때까지 "시험" SettingTile( title: Row(mainAxisSize: MainAxisSize.min, children: [ Flexible(child: Text(tr('화면 밝기'))), const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), decoration: BoxDecoration(border: Border.all(color: JjColors.warning), borderRadius: BorderRadius.circular(4)), child: Text(tr('시험'), style: TextStyle(fontSize: 11, color: JjColors.warning)), ), ]), subtitle: Text(tr('밝은 화면 · 기기 따라는 아직 시험 중입니다 - 일부 화면의 색이 덜 맞을 수 있습니다')), trailing: DropdownButton( value: s.display.brightness, items: [ DropdownMenuItem(value: 'dark', child: Text(tr('어두운 화면'))), DropdownMenuItem(value: 'light', child: Text(tr('밝은 화면'))), DropdownMenuItem(value: 'system', child: Text(tr('기기 따라'))), ], onChanged: (v) => c.updateSettings((x) => x.display.brightness = v!), ), ), ]); } } /// 앱 아이콘 고르기: 그림을 누르면 바로 바뀐다 class AppIconTile extends StatelessWidget { const AppIconTile({super.key, required this.c, required this.desk}); final AppController c; final bool desk; @override Widget build(BuildContext context) { final now = appIconOf(c.settings.display.appIcon); return SettingTile( title: Text(tr('앱 아이콘')), subtitle: Padding( padding: const EdgeInsets.only(top: 4), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(desk ? tr('창 · 작업 표시줄 · 트레이와 앱 안 왼쪽 위 버튼의 아이콘입니다 (실행 파일 · 바탕 화면 바로 가기 아이콘은 그대로)') : tr('앱 목록 · 홈 화면과 앱 안 왼쪽 위 버튼의 아이콘입니다. 바꾸면 홈 화면에 둔 아이콘이 없어질 수 있어 앱 목록에서 다시 끌어다 놓아야 할 수 있습니다')), const SizedBox(height: 8), Wrap(spacing: 10, runSpacing: 10, children: [ for (final id in appIconIds) Tooltip( message: id == appIconIds.first ? '${_name(id)} (${tr('기본')})' : _name(id), child: InkWell( borderRadius: BorderRadius.circular(14), onTap: id == now ? null : () { c.updateSettings((x) => x.display.appIcon = id); c.services.shell.setAppIcon(id); }, child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: id == now ? JjColors.accent : Colors.transparent, width: 3), ), child: Image.asset(appIconPreviewAsset(id), width: 56, height: 56, filterQuality: FilterQuality.medium), ), ), ), ]), ]), ), ); } static String _name(String id) => switch (id) { 'yellow' => tr('노랑'), 'black' => tr('검정'), 'film_jj' => tr('필름 + JJ'), 'film' => tr('필름'), _ => tr('처음 아이콘 (파랑)'), }; }