126 lines
4.5 KiB
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('확인'))),
|
|
],
|
|
);
|
|
}
|