JJ_MKVMaker/lib/ui/settings/settings_tiles.dart

126 lines
4.5 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../theme.dart';
import '../../l10n/tr.dart';
import '../setting_tile.dart';
// 약속 (설정 B): 환경 설정 분야들이 같이 쓰는 줄 - 작은 제목 · 글 넣는 줄 · 폴더 고르는 줄 · 개수 고르기 (1 · 5 · 10 · 무한 · 직접)
/// 묶음 안의 작은 제목
Widget settingsSubTitle(String t) => Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: Text(t, style: TextStyle(fontSize: 13, color: JjColors.accent, fontWeight: FontWeight.w600)),
);
/// 글 넣는 줄 - 칸을 벗어나거나 Enter · 바꿀 때마다 저장
Widget settingsTextTile(String title, String value, String hint, ValueChanged<String> onSave,
{bool obscure = false}) =>
SettingTile(
title: Text(title),
subtitle: Text(hint),
trailing: SizedBox(
width: 320,
child: TextFormField(
initialValue: value,
obscureText: obscure,
decoration: const InputDecoration(isDense: true, border: OutlineInputBorder()),
// 입력칸을 벗어나거나 Enter 를 누르면 저장
onFieldSubmitted: onSave,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
onChanged: onSave,
),
),
);
/// 폴더 고르는 줄 - [폴더 선택] · 고른 것이 있으면 [기본값]
Widget settingsFolderTile({
required String title,
required String? value,
required String defaultText,
required VoidCallback onPick,
required VoidCallback onReset,
}) =>
SettingTile(
title: Text(title),
subtitle: Text(value ?? defaultText, maxLines: 2, overflow: TextOverflow.ellipsis),
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
OutlinedButton(onPressed: onPick, child: Text(tr('폴더 선택'))),
if (value != null) ...[
const SizedBox(width: 6),
TextButton(onPressed: onReset, child: Text(tr('기본값'))),
],
]),
);
/// 동시 작업 수 고르기: 1 · 5 · 10 · 무한(0) · 직접 입력
class CountSelector extends StatelessWidget {
final int value;
final ValueChanged<int> onChanged;
const CountSelector({super.key, required this.value, required this.onChanged});
static const _presets = [1, 5, 10, 0];
static String label(int n) => n <= 0 ? tr('무한') : trf('{0}개', [n]);
Future<void> _custom(BuildContext context) async {
// 목록 메뉴가 완전히 닫힌 뒤 입력 창을 연다
await Future<void>.delayed(Duration.zero);
if (!context.mounted) return;
final n = await showDialog<int>(
context: context,
builder: (_) => _CountInputDialog(initial: value),
);
if (n != null && n >= 0) onChanged(n);
}
@override
Widget build(BuildContext context) {
final items = {..._presets, value};
return DropdownButton<int>(
value: value,
items: [
for (final n in items) DropdownMenuItem(value: n, child: Text(label(n))),
DropdownMenuItem(value: -1, child: Text(tr('직접 입력…'))),
],
onChanged: (n) => n == -1 ? _custom(context) : onChanged(n!),
);
}
}
/// 개수 직접 입력 창 (입력칸은 창이 닫히는 애니메이션이 끝난 뒤 정리)
class _CountInputDialog extends StatefulWidget {
final int initial;
const _CountInputDialog({required this.initial});
@override
State<_CountInputDialog> createState() => _CountInputDialogState();
}
class _CountInputDialogState extends State<_CountInputDialog> {
late final _ctrl = TextEditingController(text: widget.initial > 0 ? '${widget.initial}' : '');
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => AlertDialog(
scrollable: true,
title: Text(tr('직접 입력')),
content: TextField(
controller: _ctrl,
autofocus: true,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(labelText: tr('동시에 처리할 개수 (0 = 무한)'), border: OutlineInputBorder()),
onSubmitted: (t) => Navigator.pop(context, int.tryParse(t)),
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('취소'))),
FilledButton(onPressed: () => Navigator.pop(context, int.tryParse(_ctrl.text)), child: Text(tr('확인'))),
],
);
}