import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../theme/app_colors.dart'; import '../data/pet_data.dart'; // Import Data import '../widgets/pet_registration/selection_modal.dart'; // Import SelectionModal import '../widgets/pet_registration/input_formatters.dart'; // Import InputFormatters import '../services/firestore_service.dart'; import '../models/pet_model.dart'; class PetRegistrationScreen extends StatefulWidget { const PetRegistrationScreen({super.key}); @override State createState() => _PetRegistrationScreenState(); } class _PetRegistrationScreenState extends State { // 정확한 날짜를 몰라요 상태 bool _isDateUnknown = false; final TextEditingController _monthController = TextEditingController(); final TextEditingController _yearController = TextEditingController(); // Added back final TextEditingController _dayController = TextEditingController(); final TextEditingController _registrationNumberController = TextEditingController(); // Added Registration Number Controller final FocusNode _yearFocus = FocusNode(); final FocusNode _monthFocus = FocusNode(); final FocusNode _dayFocus = FocusNode(); bool get _isFormValid { // 1. 이름 확인 if (_nameController.text.trim().isEmpty) return false; // 2. 종 확인 if (_speciesController.text.trim().isEmpty) return false; // 3. 품종 확인 if (_breedController.text.trim().isEmpty) return false; // 4. 성별 확인 if (_selectedGender == null) return false; // 5. 생년월일 확인 if (!_isDateUnknown) { if (_yearController.text.length != 4 || _monthController.text.length != 2 || _dayController.text.length != 2) { return false; } } return true; } @override void initState() { super.initState(); // 폼 상태 변경 감지를 위한 리스너 등록 _nameController.addListener(_updateState); _speciesController.addListener(_updateState); _breedController.addListener(_updateState); _yearController.addListener(_updateState); _monthController.addListener(_updateState); _dayController.addListener(_updateState); } void _updateState() { setState(() {}); } // 보유 질환 데이터 (Removed: Use PetData.diseaseList) // 각 항목별 선택 상태 및 컨트롤러 final TextEditingController _nameController = TextEditingController(); // 이름 컨트롤러 final TextEditingController _speciesController = TextEditingController(); // 종 컨트롤러 final TextEditingController _breedController = TextEditingController(); // 품종 컨트롤러 final TextEditingController _genderController = TextEditingController(); // 성별 컨트롤러 // 종 데이터 (Removed: Use PetData.breedsData) // 선택된 종 정보 (품종 선택을 위해 필요) String? _currentMajorCategory; String? _currentMinorCategory; String? _selectedGender; // '남아', '여아' bool _isNeutered = false; // 중성화 여부 List _selectedDiseases = []; String _otherDiseaseText = ''; // 보유 질환 기타 텍스트 final TextEditingController _diseaseController = TextEditingController(); List _selectedPastDiseases = []; File? _profileImage; // 프로필 이미지 final ImagePicker _picker = ImagePicker(); // 이미지 피커 // 이미지 선택 모달 (카메라/갤러리) void _pickImage() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) { return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 20.h), Text( '프로필 사진 설정', style: TextStyle( fontFamily: 'SCDream', fontSize: 18.sp, fontWeight: FontWeight.bold, ), ), SizedBox(height: 20.h), ListTile( leading: Icon( Icons.camera_alt, color: Colors.black, size: 24.w, ), title: Text( '카메라로 촬영', style: TextStyle(fontFamily: 'SCDream', fontSize: 16.sp), ), onTap: () async { Navigator.pop(context); final XFile? image = await _picker.pickImage( source: ImageSource.camera, ); if (image != null) { setState(() { _profileImage = File(image.path); }); } }, ), ListTile( leading: Icon( Icons.photo_library, color: Colors.black, size: 24.w, ), title: Text( '갤러리에서 선택', style: TextStyle(fontFamily: 'SCDream', fontSize: 16.sp), ), onTap: () async { Navigator.pop(context); final XFile? image = await _picker.pickImage( source: ImageSource.gallery, ); if (image != null) { setState(() { _profileImage = File(image.path); }); } }, ), ListTile( leading: Icon( Icons.delete_outline, color: Colors.black, size: 24.w, ), title: Text( '기본 이미지로 변경', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, color: Colors.black, ), ), onTap: () { Navigator.pop(context); setState(() { _profileImage = null; }); }, ), SizedBox(height: 20.h), ], ), ); }, ); } String _otherPastDiseaseText = ''; // 과거 진단 기타 텍스트 final TextEditingController _pastDiseaseController = TextEditingController(); List _selectedHealthConcerns = []; String _otherHealthConcernText = ''; // 염려 건강 기타 텍스트 final TextEditingController _healthConcernController = TextEditingController(); @override void dispose() { _nameController.dispose(); _speciesController.dispose(); _breedController.dispose(); _genderController.dispose(); _yearController.dispose(); _monthController.dispose(); _dayController.dispose(); _diseaseController.dispose(); _pastDiseaseController.dispose(); _healthConcernController.dispose(); _yearFocus.dispose(); _monthFocus.dispose(); _dayFocus.dispose(); _registrationNumberController.dispose(); super.dispose(); } bool _isLoading = false; Future _registerPet() async { setState(() { _isLoading = true; }); try { final firestoreService = FirestoreService(); final userId = firestoreService.getCurrentUserId(); if (userId == null) { throw Exception('로그인이 필요합니다.'); } // 날짜 처리 DateTime? birthDate; if (!_isDateUnknown) { birthDate = DateTime( int.parse(_yearController.text), int.parse(_monthController.text), int.parse(_dayController.text), ); } // 리스트 + 기타 텍스트 합치기 (보유 질환) List finalDiseases = List.from(_selectedDiseases); if (finalDiseases.contains('기타') && _otherDiseaseText.isNotEmpty) { finalDiseases.remove('기타'); finalDiseases.add('기타($_otherDiseaseText)'); } // 리스트 + 기타 텍스트 합치기 (과거 질환) List finalPastDiseases = List.from(_selectedPastDiseases); if (finalPastDiseases.contains('기타') && _otherPastDiseaseText.isNotEmpty) { finalPastDiseases.remove('기타'); finalPastDiseases.add('기타($_otherPastDiseaseText)'); } // 리스트 + 기타 텍스트 합치기 (염려 건강) List finalHealthConcerns = List.from(_selectedHealthConcerns); if (finalHealthConcerns.contains('기타') && _otherHealthConcernText.isNotEmpty) { finalHealthConcerns.remove('기타'); finalHealthConcerns.add('기타($_otherHealthConcernText)'); } final newPet = Pet( id: firestoreService.generatePetId(), ownerId: userId, name: _nameController.text, species: _speciesController.text, // 중분류 or 직접입력 breed: _breedController.text, gender: _selectedGender!, isNeutered: _isNeutered, birthDate: birthDate, isDateUnknown: _isDateUnknown, registrationNumber: _registrationNumberController.text.isNotEmpty ? _registrationNumberController.text : null, diseases: finalDiseases, pastDiseases: finalPastDiseases, healthConcerns: finalHealthConcerns, createdAt: DateTime.now(), ); await firestoreService.registerPet(newPet, _profileImage); if (!mounted) return; Navigator.pushReplacementNamed(context, '/register_complete'); } catch (e) { if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('등록 실패: $e'))); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } void _toggleDateUnknown() { setState(() { _isDateUnknown = !_isDateUnknown; if (_isDateUnknown) { _yearController.clear(); _monthController.clear(); _dayController.clear(); } }); } // 공통 선택 모달 (보유 질환, 과거 진단, 염려 건강) - Generic Selection Modal void _showSelectionModal({ required String title, required List currentSelected, required String currentOtherText, required Function(List, String) onComplete, }) { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) { // 모달 내부 임시 상태 List tempSelected = List.from(currentSelected); final TextEditingController otherInputController = TextEditingController(text: currentOtherText); return StatefulBuilder( builder: (BuildContext context, StateSetter setModalState) { // 키보드가 올라왔을 때를 대비한 Padding 처리 final bottomInset = MediaQuery.of(context).viewInsets.bottom; return Container( height: 0.85.sh, margin: EdgeInsets.only(top: 50.h), // 상단 여백 decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)), ), child: Column( children: [ SizedBox(height: 20.h), // 타이틀 Text( title, style: TextStyle( fontFamily: 'SCDream', fontSize: 18.sp, fontWeight: FontWeight.bold, color: Colors.black, ), ), SizedBox(height: 10.h), Divider(color: const Color(0xFFEEEEEE), thickness: 1.h), // 리스트 영역 Expanded( child: ListView.builder( itemCount: PetData.diseaseList.length, padding: const EdgeInsets.symmetric(vertical: 10), itemBuilder: (context, index) { final disease = PetData.diseaseList[index]; final isSelected = tempSelected.contains(disease); return Column( children: [ InkWell( onTap: () { setModalState(() { if (isSelected) { tempSelected.remove(disease); } else { tempSelected.add(disease); } }); }, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 16, ), child: Row( children: [ Icon( Icons.check, size: 20, color: isSelected ? AppColors.highlight : Colors.grey[300], ), const SizedBox(width: 12), Expanded( child: Text( disease, style: TextStyle( fontFamily: 'SCDream', fontSize: 16, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? AppColors.highlight : Colors.black, ), ), ), ], ), ), ), // 기타가 선택되었을 때 입력창 표시 if (isSelected && disease == "기타") Padding( padding: EdgeInsets.fromLTRB( 52.w, 0, 20.w, 10.h, ), child: TextField( key: const ValueKey('other_input'), controller: otherInputController, autofocus: true, // 입력창이 생기면 바로 포커스 style: TextStyle( fontFamily: 'SCDream', fontSize: 14.sp, ), decoration: InputDecoration( hintText: '직접 입력해 주세요', isDense: true, contentPadding: EdgeInsets.symmetric( vertical: 10.h, horizontal: 10.w, ), border: const OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFDDDDDD), ), ), enabledBorder: const OutlineInputBorder( borderSide: BorderSide( color: Color(0xFFDDDDDD), ), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: AppColors.highlight, ), ), ), ), ), ], ); }, ), ), // 하단 버튼 영역 Padding( padding: EdgeInsets.fromLTRB( 20.w, 20.h, 20.w, 20.h + bottomInset, ), child: Row( children: [ // 초기화 버튼 InkWell( onTap: () { setModalState(() { tempSelected.clear(); otherInputController.clear(); }); }, child: Container( height: 52.h, padding: EdgeInsets.symmetric(horizontal: 20.w), decoration: BoxDecoration( color: const Color(0xFF333333), borderRadius: BorderRadius.circular(12.r), ), child: Row( children: [ Icon( Icons.refresh, color: Colors.white, size: 20.w, ), SizedBox(width: 4.w), const Text( '초기화', style: TextStyle( fontFamily: 'SCDream', color: Colors.white, fontWeight: FontWeight.bold, ), ), ], ), ), ), SizedBox(width: 12.w), // 선택 완료 버튼 Expanded( child: InkWell( onTap: () { onComplete( tempSelected, otherInputController.text, ); Navigator.pop(context); }, child: Container( height: 52.h, decoration: BoxDecoration( color: AppColors.highlight, borderRadius: BorderRadius.circular(12.r), ), child: Center( child: Text( '선택 완료', style: TextStyle( fontFamily: 'SCDream', color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.bold, ), ), ), ), ), ), ], ), ), ], ), ); }, ); }, ); } // 종 선택 모달 (대분류 -> 중분류 2단계) void _showSpeciesSelectionModal() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) { String? selectedMajor; // 모달 내부 임시 상태 (대분류) bool showInput = false; // 직접 입력 창 표시 여부 final TextEditingController speciesInputController = TextEditingController(); return StatefulBuilder( builder: (BuildContext context, StateSetter setModalState) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; return Container( height: 0.6.sh, // 높이 60%로 조정 margin: EdgeInsets.only(top: 50.h), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)), ), child: Column( children: [ // 상단 네비게이션바 (닫기 / 뒤로가기) Padding( padding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 12.h, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 뒤로가기 버튼 (selectedMajor != null || showInput) ? GestureDetector( onTap: () { setModalState(() { if (showInput) { showInput = false; } else { selectedMajor = null; } }); }, child: Icon( Icons.arrow_back_ios, size: 20.w, color: Colors.black, ), ) : SizedBox(width: 20.w), // 타이틀 Text( showInput ? '직접 입력' : (selectedMajor == null ? '대분류' : '중분류'), style: TextStyle( fontFamily: 'SCDream', fontSize: 18.sp, fontWeight: FontWeight.bold, color: Colors.black, ), ), // 닫기 버튼 GestureDetector( onTap: () => Navigator.pop(context), child: Icon( Icons.close, size: 24.w, color: Colors.black, ), ), ], ), ), Divider(color: const Color(0xFFEEEEEE), thickness: 1.h), // 컨텐츠 영역 Expanded( child: showInput ? Padding( padding: EdgeInsets.all(20.w), child: Column( children: [ Text( '반려동물의 종을 직접 입력해주세요.', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, color: Colors.black87, ), ), SizedBox(height: 20.h), TextField( controller: speciesInputController, autofocus: true, decoration: const InputDecoration( hintText: '예: 미어캣, 라쿤 등', border: OutlineInputBorder(), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: AppColors.highlight, ), ), ), ), const Spacer(), SizedBox( width: double.infinity, height: 52.h, child: ElevatedButton( onPressed: () { if (speciesInputController .text .isNotEmpty) { setState(() { _speciesController.text = speciesInputController.text; // 직접 입력 시 카테고리 정보 초기화 (품종 선택 불가 또는 직접 입력) _currentMajorCategory = null; _currentMinorCategory = null; _breedController.clear(); }); Navigator.pop(context); } }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.highlight, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 12.r, ), ), ), child: Text( '완료', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), SizedBox(height: bottomInset), ], ), ) : (selectedMajor == null ? ListView.builder( // 대분류 리스트 itemCount: PetData.breedsData.keys.length, itemBuilder: (context, index) { final major = PetData.breedsData.keys .elementAt(index); return ListTile( title: Text( major, style: const TextStyle( fontFamily: 'SCDream', fontSize: 16, ), ), trailing: const Icon( Icons.arrow_forward_ios, size: 16, color: Colors.grey, ), onTap: () { setModalState(() { if (major == '기타(직접 입력)') { showInput = true; } else { selectedMajor = major; } }); }, ); }, ) : ListView.builder( // 중분류 리스트 itemCount: PetData.breedsData[selectedMajor]!.length, itemBuilder: (context, index) { final minor = PetData .breedsData[selectedMajor]! .keys .elementAt(index); return ListTile( title: Text( minor, style: const TextStyle( fontFamily: 'SCDream', fontSize: 16, ), ), trailing: minor == '기타(직접 입력)' ? const Icon( Icons.arrow_forward_ios, size: 16, color: Colors.grey, ) : null, onTap: () { if (minor == '기타(직접 입력)') { setModalState(() { showInput = true; }); } else { setState(() { // 최종 선택 반영 _currentMajorCategory = selectedMajor; _currentMinorCategory = minor; _speciesController.text = minor; _breedController .clear(); // 종 변경 시 품종 초기화 }); Navigator.pop(context); } }, ); }, )), ), ], ), ); }, ); }, ); } // 품종 선택 모달 (검색 가능) void _showBreedSelectionModal() { // 1. 종 선택 선행 확인 if (_speciesController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('반려동물 종을 먼저 선택해주세요.'), duration: Duration(seconds: 1), ), ); return; } // 2. 직접 입력 등 카테고리 정보가 없는 경우 -> 바로 직접 입력 모드로 if (_currentMajorCategory == null || _currentMinorCategory == null) { _showBreedDirectInputModal(); return; } // 3. 품종 리스트 가져오기 final List originalList = PetData .breedsData[_currentMajorCategory]![_currentMinorCategory]! .where((e) => e != '기타(직접 입력)') .toList(); // '기타(직접 입력)'은 리스트 마지막에 고정하거나 별도 처리, 여기서는 필터링 후 맨 뒤에 붙일 예정 showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) { String searchText = ''; List filteredList = List.from(originalList); TextEditingController searchController = TextEditingController(); bool showInput = false; final TextEditingController manualInputController = TextEditingController(); return StatefulBuilder( builder: (BuildContext context, StateSetter setModalState) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; void filterList(String query) { setModalState(() { searchText = query; if (query.isEmpty) { filteredList = List.from(originalList); } else { filteredList = originalList .where( (breed) => breed.toLowerCase().contains(query.toLowerCase()), ) .toList(); } }); } return Container( height: 0.85.sh, margin: EdgeInsets.only(top: 50.h), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)), ), child: Column( children: [ // 상단 네비게이션바 Padding( padding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 12.h, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ showInput ? GestureDetector( onTap: () { setModalState(() { showInput = false; }); }, child: Icon( Icons.arrow_back_ios, size: 20.w, color: Colors.black, ), ) : const SizedBox(width: 20), Text( showInput ? '직접 입력' : '품종 선택', style: TextStyle( fontFamily: 'SCDream', fontSize: 18.sp, fontWeight: FontWeight.bold, color: Colors.black, ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Icon( Icons.close, size: 24.w, color: Colors.black, ), ), ], ), ), Divider(color: const Color(0xFFEEEEEE), thickness: 1.h), // 컨텐츠 Expanded( child: showInput ? Padding( padding: EdgeInsets.all(20.w), child: Column( children: [ Text( '반려동물의 품종을 직접 입력해주세요.', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, color: Colors.black87, ), ), SizedBox(height: 20.h), TextField( controller: manualInputController, autofocus: true, decoration: const InputDecoration( hintText: '예: 믹스, 시고르자브종 등', border: OutlineInputBorder(), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: AppColors.highlight, ), ), ), ), const Spacer(), SizedBox( width: double.infinity, height: 52.h, child: ElevatedButton( onPressed: () { if (manualInputController .text .isNotEmpty) { setState(() { _breedController.text = manualInputController.text; }); Navigator.pop(context); } }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.highlight, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 12.r, ), ), ), child: Text( '완료', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), SizedBox(height: bottomInset), ], ), ) : Column( children: [ // 검색창 Padding( padding: EdgeInsets.fromLTRB( 20.w, 10.h, 20.w, 10.h, ), child: TextField( controller: searchController, onChanged: filterList, decoration: InputDecoration( hintText: '품종 검색', prefixIcon: const Icon( Icons.search, color: Colors.grey, ), filled: true, fillColor: const Color(0xFFF5F5F5), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( vertical: 0, horizontal: 16, ), ), ), ), // 리스트 Expanded( child: ListView.builder( itemCount: filteredList.length + 1, // 목록 + 직접입력 itemBuilder: (context, index) { if (index == filteredList.length) { // 마지막 아이템: 직접 입력 return ListTile( title: const Text( '기타(직접 입력)', style: TextStyle( fontFamily: 'SCDream', fontSize: 16, color: Colors.black87, ), ), trailing: const Icon( Icons.arrow_forward_ios, size: 16, color: Colors.grey, ), onTap: () { setModalState(() { showInput = true; }); }, ); } final breed = filteredList[index]; return ListTile( title: Text( breed, style: const TextStyle( fontFamily: 'SCDream', fontSize: 16, ), ), onTap: () { setState(() { _breedController.text = breed; }); Navigator.pop(context); }, ); }, ), ), ], ), ), ], ), ); }, ); }, ); } // 품종 직접 입력 모달 (카테고리 정보 없을 때) void _showBreedDirectInputModal() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) { final TextEditingController manualInputController = TextEditingController(); return Container( height: 0.85.sh, margin: EdgeInsets.only(top: 50.h), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)), ), child: Padding( padding: EdgeInsets.all(20.w), child: Column( children: [ // 네비게이션 Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: () => Navigator.pop(context), child: Icon(Icons.close, size: 24.w, color: Colors.black), ), ], ), SizedBox(height: 20.h), Text( '반려동물의 품종을 직접 입력해주세요.', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, color: Colors.black87, ), ), SizedBox(height: 20.h), TextField( controller: manualInputController, autofocus: true, decoration: const InputDecoration( hintText: '예: 믹스, 시고르자브종 등', border: OutlineInputBorder(), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: AppColors.highlight), ), ), ), const Spacer(), SizedBox( width: double.infinity, height: 52.h, child: ElevatedButton( onPressed: () { if (manualInputController.text.isNotEmpty) { setState(() { _breedController.text = manualInputController.text; }); Navigator.pop(context); } }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.highlight, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.r), ), ), child: Text( '완료', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), SizedBox(height: MediaQuery.of(context).viewInsets.bottom), ], ), ), ); }, ); } // 성별 선택 모달 (남아/여아/기타 + 중성화) void _showGenderSelectionModal() { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) { // 모달 내부 임시 상태 String? tempGender = _selectedGender; bool tempNeutered = _isNeutered; return StatefulBuilder( builder: (BuildContext context, StateSetter setModalState) { return Container( // height 제거 (내용물 크기에 맞춤) decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)), ), child: Column( mainAxisSize: MainAxisSize.min, // 내용물만큼만 차지 children: [ // 상단 네비게이션바 Padding( padding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 12.h, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox(width: 24.w), // 닫기 버튼과 대칭을 위한 여백 Text( '성별 선택', style: TextStyle( fontFamily: 'SCDream', fontSize: 18.sp, fontWeight: FontWeight.bold, color: Colors.black, ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Icon( Icons.close, size: 24.w, color: Colors.black, ), ), ], ), ), Divider(color: const Color(0xFFEEEEEE), thickness: 1.h), SizedBox(height: 30.h), // 성별 선택 버튼 영역 (3개) Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( children: [ Expanded( child: _buildGenderCard( '남아', Icons.male, tempGender == '남아', (val) => setModalState(() => tempGender = val), ), ), const SizedBox(width: 12), Expanded( child: _buildGenderCard( '여아', Icons.female, tempGender == '여아', (val) => setModalState(() => tempGender = val), ), ), const SizedBox(width: 12), Expanded( child: _buildGenderCard( '기타', Icons.question_mark, tempGender == '기타', (val) => setModalState(() => tempGender = val), ), ), ], ), ), const SizedBox(height: 30), // 중성화 여부 체크박스 GestureDetector( onTap: () { setModalState(() { tempNeutered = !tempNeutered; }); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( tempNeutered ? Icons.check_box : Icons.check_box_outline_blank, color: tempNeutered ? AppColors.highlight : Colors.grey, size: 24.w, ), SizedBox(width: 8.w), Text( '중성화를 했어요', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, color: Colors.black87, ), ), ], ), ), SizedBox(height: 12.h), // 간격 축소 // 완료 버튼 Padding( padding: EdgeInsets.fromLTRB(20.w, 0, 20.w, 20.h), child: SizedBox( width: double.infinity, height: 52.h, child: ElevatedButton( onPressed: () { setState(() { _selectedGender = tempGender; _isNeutered = tempNeutered; if (_selectedGender != null) { if (_selectedGender == '기타') { _genderController.text = '기타'; } else { _genderController.text = '$_selectedGender${_isNeutered ? '(중성화)' : ''}'; } } }); Navigator.pop(context); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.highlight, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( '선택 완료', style: TextStyle( fontFamily: 'SCDream', fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ), ], ), ); }, ); }, ); } Widget _buildGenderCard( String gender, IconData icon, bool isSelected, Function(String) onTap, ) { return GestureDetector( onTap: () => onTap(gender), child: Container( width: 120.w, height: 100.h, decoration: BoxDecoration( color: isSelected ? AppColors.highlight.withOpacity(0.1) : Colors.white, borderRadius: BorderRadius.circular(16.r), border: Border.all( color: isSelected ? AppColors.highlight : const Color(0xFFEEEEEE), width: 2.w, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, size: 40.w, color: isSelected ? AppColors.highlight : Colors.grey, ), SizedBox(height: 12.h), Text( gender, style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, fontWeight: FontWeight.bold, color: isSelected ? AppColors.highlight : Colors.grey, ), ), ], ), ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text( '반려동물 등록', style: TextStyle( color: Color(0xFF1f1f1f), fontFamily: 'SCDream', fontWeight: FontWeight.w500, fontSize: 15.sp, ), ), centerTitle: true, backgroundColor: Colors.white, scrolledUnderElevation: 0, elevation: 0, leading: IconButton( icon: Icon(Icons.arrow_back_ios, color: Colors.black, size: 16.w), onPressed: () => Navigator.pop(context), ), ), body: SingleChildScrollView( padding: EdgeInsets.all(20.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 1. 프로필 이미지 영역 Center( child: GestureDetector( onTap: _pickImage, child: Stack( children: [ Container( width: 100.w, height: 100.w, decoration: BoxDecoration( color: const Color(0xFFF5F5F5), shape: BoxShape.circle, border: Border.all(color: const Color(0xFFEEEEEE)), image: _profileImage != null ? DecorationImage( image: FileImage(_profileImage!), fit: BoxFit.cover, ) : null, ), child: _profileImage == null ? Center( child: SvgPicture.asset( 'assets/icons/profile_icon.svg', width: 40.w, colorFilter: ColorFilter.mode( Colors.grey[400]!, BlendMode.srcIn, ), ), ) : null, ), Positioned( bottom: 0, right: 0, child: Container( padding: EdgeInsets.all(6.w), decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, border: Border.all(color: const Color(0xFFEEEEEE)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4.r, offset: Offset(0, 2.h), ), ], ), child: Icon( Icons.camera_alt, size: 16.w, color: Colors.black87, ), ), ), ], ), ), ), SizedBox(height: 30.h), // 2. 반려동물 이름 입력 _buildLabel('반려동물 이름 입력', isRequired: true), _buildTextField( controller: _nameController, hint: '이름 입력 (2~10글자/한글/영문/숫자)', inputFormatters: [ LengthLimitingTextInputFormatter(10), // 최대 10글자 제한 ], ), SizedBox(height: 20.h), // 3. 선택 박스들 (종, 품종, 성별) _buildSearchField( '반려동물 종 선택', controller: _speciesController, readOnly: true, onTap: _showSpeciesSelectionModal, isRequired: true, ), SizedBox(height: 20.h), _buildSearchField( '반려동물 품종 선택', controller: _breedController, readOnly: true, onTap: _showBreedSelectionModal, isRequired: true, ), SizedBox(height: 20.h), _buildSearchField( '반려동물 성별', controller: _genderController, readOnly: true, onTap: _showGenderSelectionModal, isRequired: true, ), SizedBox(height: 20.h), // 4. 생년월일 _buildLabel('반려동물 생년월일', isRequired: true), Row( children: [ Expanded( child: _buildTextField( controller: _yearController, focusNode: _yearFocus, hint: 'YYYY', textAlign: TextAlign.center, hintColor: _isDateUnknown ? const Color(0xFFC8C8C8) : const Color(0xFF7D7C7C), enabled: !_isDateUnknown, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(4), ], onChanged: (value) { if (value.length == 4) { FocusScope.of(context).requestFocus(_monthFocus); } }, ), ), SizedBox(width: 12.w), Expanded( child: _buildTextField( controller: _monthController, focusNode: _monthFocus, hint: 'MM', textAlign: TextAlign.center, hintColor: _isDateUnknown ? const Color(0xFFC8C8C8) : const Color(0xFF7D7C7C), enabled: !_isDateUnknown, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(2), DateRangeInputFormatter(min: 1, max: 12), ], onChanged: (value) { if (value.length == 2) { FocusScope.of(context).requestFocus(_dayFocus); } }, ), ), SizedBox(width: 12.w), Expanded( child: _buildTextField( controller: _dayController, focusNode: _dayFocus, hint: 'DD', textAlign: TextAlign.center, hintColor: _isDateUnknown ? const Color(0xFFC8C8C8) : const Color(0xFF7D7C7C), enabled: !_isDateUnknown, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(2), DayInputFormatter( monthController: _monthController, yearController: _yearController, ), ], ), ), ], ), const SizedBox(height: 12), GestureDetector( onTap: _toggleDateUnknown, child: Container( width: double.infinity, padding: EdgeInsets.symmetric(vertical: 14.h), decoration: BoxDecoration( color: _isDateUnknown ? AppColors.subHighlight : AppColors.inactive, borderRadius: BorderRadius.circular(30.r), ), child: Center( child: Text( '정확한 날짜를 몰라요', style: TextStyle( fontFamily: 'SCDream', color: Colors.white, fontWeight: FontWeight.w500, fontSize: 14.sp, ), ), ), ), ), const SizedBox(height: 24), // 5. 동물 등록 번호 _buildLabel('동물 등록 번호', isRequired: false), // const SizedBox(height: 8), _buildTextField( controller: _registrationNumberController, hint: '숫자만 입력', keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(15), ], ), const SizedBox(height: 24), // 6. 질환 정보 (검색 아이콘 포함) _buildSearchField( '보유 질환', controller: _diseaseController, readOnly: true, onTap: () => _showSelectionModal( title: '보유 질환 선택', currentSelected: _selectedDiseases, currentOtherText: _otherDiseaseText, onComplete: (selected, otherText) { setState(() { _selectedDiseases = selected; _otherDiseaseText = otherText; // 텍스트 필드 표시용 문자열 생성 List displayList = selected .where((e) => e != '기타') .toList(); if (selected.contains('기타') && otherText.isNotEmpty) { displayList.add('기타($otherText)'); } else if (selected.contains('기타')) { displayList.add('기타'); } _diseaseController.text = displayList.join(', '); }); }, ), ), SizedBox(height: 20.h), _buildSearchField( '과거 진단받은 질병', controller: _pastDiseaseController, readOnly: true, onTap: () => _showSelectionModal( title: '과거 진단받은 질병 선택', currentSelected: _selectedPastDiseases, currentOtherText: _otherPastDiseaseText, onComplete: (selected, otherText) { setState(() { _selectedPastDiseases = selected; _otherPastDiseaseText = otherText; List displayList = selected .where((e) => e != '기타') .toList(); if (selected.contains('기타') && otherText.isNotEmpty) { displayList.add('기타($otherText)'); } else if (selected.contains('기타')) { displayList.add('기타'); } _pastDiseaseController.text = displayList.join(', '); }); }, ), ), SizedBox(height: 20.h), _buildSearchField( '염려되는 건강 문제', controller: _healthConcernController, readOnly: true, onTap: () => _showSelectionModal( title: '염려되는 건강 문제 선택', currentSelected: _selectedHealthConcerns, currentOtherText: _otherHealthConcernText, onComplete: (selected, otherText) { setState(() { _selectedHealthConcerns = selected; _otherHealthConcernText = otherText; List displayList = selected .where((e) => e != '기타') .toList(); if (selected.contains('기타') && otherText.isNotEmpty) { displayList.add('기타($otherText)'); } else if (selected.contains('기타')) { displayList.add('기타'); } _healthConcernController.text = displayList.join(', '); }); }, ), ), SizedBox(height: 40.h), // 7. 등록 버튼 SizedBox( width: double.infinity, height: 52.h, child: ElevatedButton( onPressed: (_isFormValid && !_isLoading) ? _registerPet : null, style: ElevatedButton.styleFrom( backgroundColor: AppColors.highlight, disabledBackgroundColor: AppColors.inactive, disabledForegroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.r), ), ), child: _isLoading ? SizedBox( width: 24.w, height: 24.w, child: const CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : Text( '반려동물 등록', style: TextStyle( fontFamily: 'SCDream', fontSize: 16.sp, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), SizedBox(height: 20.h), ], ), ), ); } // Helper Widget: 라벨 (필수 표시 포함) Widget _buildLabel(String text, {bool isRequired = false}) { return Row( mainAxisSize: MainAxisSize.min, children: [ if (isRequired) Padding( padding: EdgeInsets.only(right: 4.w), child: Container( width: 4.w, height: 4.w, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), ), Text( text, style: TextStyle( fontFamily: 'SCDream', fontSize: 14.sp, fontWeight: FontWeight.w500, color: Colors.black, ), ), ], ); } // Helper Widget: 텍스트 입력 필드 Widget _buildTextField({ required String hint, TextAlign textAlign = TextAlign.start, Color? hintColor, bool enabled = true, TextEditingController? controller, TextInputType? keyboardType, List? inputFormatters, FocusNode? focusNode, ValueChanged? onChanged, }) { return TextField( controller: controller, focusNode: focusNode, onChanged: onChanged, enabled: enabled, textAlign: textAlign, keyboardType: keyboardType, inputFormatters: inputFormatters, style: TextStyle( fontFamily: 'SCDream', fontSize: 14.sp, color: AppColors.text, ), decoration: InputDecoration( hintText: hint, hintStyle: TextStyle( fontFamily: 'SCDream', fontSize: 14.sp, color: hintColor ?? Colors.grey, ), enabledBorder: const UnderlineInputBorder( borderSide: BorderSide(color: Color(0xFFDDDDDD)), ), focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: AppColors.highlight), ), contentPadding: EdgeInsets.symmetric(vertical: 8.h), isDense: true, ), ); } Widget _buildSelectionBox(String text, {required bool isRequired}) { return Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h), decoration: BoxDecoration( border: Border.all(color: const Color(0xFFDDDDDD)), borderRadius: BorderRadius.circular(4.r), ), child: Row( children: [ if (isRequired) Padding( padding: EdgeInsets.only(right: 6.w), child: Icon(Icons.circle, size: 4.w, color: Colors.red), ), Expanded( child: Text( text, style: TextStyle( fontFamily: 'SCDream', fontSize: 14.sp, fontWeight: FontWeight.w500, color: Colors.black87, ), ), ), Icon(Icons.arrow_forward_ios, size: 14.w, color: Colors.grey), ], ), ); } Widget _buildSearchField( String label, { TextEditingController? controller, VoidCallback? onTap, bool readOnly = false, bool isRequired = false, // Added isRequired param }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLabel( label, isRequired: isRequired, ), // Use _buildLabel to show label with red dot TextField( controller: controller, // 컨트롤러 연결 onTap: onTap, // 탭 이벤트 연결 readOnly: readOnly, // 읽기 전용 여부 (키보드 방지) style: TextStyle( fontFamily: 'SCDream', fontSize: 14.sp, overflow: TextOverflow.ellipsis, // ... 생략 표시 ), decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(vertical: 8.h), enabledBorder: const UnderlineInputBorder( borderSide: BorderSide(color: Color(0xFFDDDDDD)), ), focusedBorder: const UnderlineInputBorder( borderSide: BorderSide(color: AppColors.highlight), ), suffixIcon: const Icon(Icons.search, color: Colors.black87), suffixIconConstraints: BoxConstraints( minWidth: 24.w, minHeight: 24.w, ), ), ), ], ); } }