/// 약속 (W 공용 위젯, 설계 7-2 JjField - 비밀 칸): 비밀번호 · API 키 · 키 암호 칸 하나. /// 입력: 글 칸 · 칸 이름 (짧게) · 도움 글 (칸 아래, 3줄까지) · 오류 글 · 가리기 (스스로 들거나 [hidden] · [onToggle] 로 바깥에서). /// 출력: 가린 글 칸 + 눈 단추 (가렸으면 "보이기", 보이면 "가리기" - 아이콘 · 크기 · 툴팁이 어디서나 같음). 자동 고침 · 추천은 늘 끔. /// 오류 없음. 쓰는 곳: WebDAV · FTP · SFTP · SMB 서버 설정 · AI 서비스 · 압축 비밀번호. 화면은 부르기만 한다 library; import 'package:flutter/material.dart'; import '../l10n/tr.dart'; import 'jj_field.dart'; class SecretField extends StatefulWidget { const SecretField({ super.key, required this.controller, required this.label, this.helper, this.errorText, this.isDense = false, this.onChanged, this.hidden, this.onToggle, }); final TextEditingController controller; final String label; final String? helper; final String? errorText; final bool isDense; final ValueChanged? onChanged; /// 바깥에서 가리기를 정할 때 (눈 단추 하나로 칸 여럿 - 비밀번호와 확인 칸 등). null 이면 칸이 스스로 든다 final bool? hidden; /// [hidden] 을 바깥에서 줄 때의 눈 단추 (null 이면 이 칸에는 단추 없음) final VoidCallback? onToggle; @override State createState() => _SecretFieldState(); } class _SecretFieldState extends State { bool _hidden = true; @override Widget build(BuildContext context) { final outside = widget.hidden != null; final hidden = widget.hidden ?? _hidden; final toggle = outside ? widget.onToggle : () => setState(() => _hidden = !_hidden); return JjField( controller: widget.controller, label: widget.label, helper: widget.helper, errorText: widget.errorText, isDense: widget.isDense, obscureText: hidden, plainText: true, onChanged: widget.onChanged, suffix: toggle == null ? null : IconButton( tooltip: hidden ? tr('보이기') : tr('가리기'), icon: Icon(hidden ? Icons.visibility_outlined : Icons.visibility_off_outlined, size: 18), onPressed: toggle, ), ); } }