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 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 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 _custom(BuildContext context) async { // 목록 메뉴가 완전히 닫힌 뒤 입력 창을 연다 await Future.delayed(Duration.zero); if (!context.mounted) return; final n = await showDialog( context: context, builder: (_) => _CountInputDialog(initial: value), ); if (n != null && n >= 0) onChanged(n); } @override Widget build(BuildContext context) { final items = {..._presets, value}; return DropdownButton( 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('확인'))), ], ); }