JJ_MKVMaker/lib/ui/settings/general_sections.dart

235 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<GeneralSettingsSection> createState() => _GeneralSettingsSectionState();
}
class _GeneralSettingsSectionState extends State<GeneralSettingsSection> {
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.migrationNotice,
onChanged: (v) => c.updateSettings((x) => x.migrationNotice = v),
title: Text(tr('업데이트로 바뀐 기본값 알림')),
subtitle: Text(tr('새 버전이 예전 기본값을 바꿨으면 켤 때 한 번 무엇이 바뀌었는지 알려 줍니다')),
),
SwitchListTile(
value: s.rememberPasswords,
onChanged: (v) => c.updateSettings((x) => x.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<String>(
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<String>(
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<DisplaySettingsSection> createState() => _DisplaySettingsSectionState();
}
class _DisplaySettingsSectionState extends State<DisplaySettingsSection> {
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<String>(
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<double>(
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<String>(
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('처음 아이콘 (파랑)'),
};
}