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'; class PetRegistrationScreen extends StatefulWidget { const PetRegistrationScreen({super.key}); @override State createState() => _PetRegistrationScreenState(); } class _PetRegistrationScreenState extends State { // 정확한 날짜를 몰라요 상태 bool _isDateUnknown = false; final TextEditingController _yearController = TextEditingController(); final TextEditingController _monthController = TextEditingController(); final TextEditingController _dayController = TextEditingController(); // 보유 질환 데이터 final List _diseaseList = [ "피부질환", "눈 질환", "치아 / 구강 질환", "뼈 / 관절 질환", "생식기 / 비뇨기 질환", "심장 / 혈관 질환", "소화기 질환", "호흡기 질환", "내분비계 질환", "뇌신경 질환", "생식기 질환", "귀 질환", "코 질환", "기타", ]; // 각 항목별 선택 상태 및 컨트롤러 final TextEditingController _nameController = TextEditingController(); // 이름 컨트롤러 final TextEditingController _speciesController = TextEditingController(); // 종 컨트롤러 final TextEditingController _breedController = TextEditingController(); // 품종 컨트롤러 final TextEditingController _genderController = TextEditingController(); // 성별 컨트롤러 // 종 데이터 (대분류 -> 중분류 -> 품종) final Map>> _petData = { "포유류": { "강아지": [ "말티즈", "푸들", "포메라니안", "믹스견", "치와와", "시츄", "비숑 프리제", "골든 리트리버", "진돗개", "웰시 코기", "기타(직접 입력)", ], "고양이": [ "코리안 숏헤어", "페르시안", "러시안 블루", "샴", "렉돌", "스코티시 폴드", "먼치킨", "노르웨이 숲", "믹스묘", "기타(직접 입력)", ], "햄스터": ["정글리안", "펄", "푸딩", "골든 햄스터", "로보로브스키", "기타(직접 입력)"], "토끼": ["롭이어", "더치", "라이언 헤드", "드워프", "렉스", "기타(직접 입력)"], "기니피그": ["잉글리쉬", "아비시니안", "페루비안", "실키", "기타(직접 입력)"], "고슴도치": ["플라티나", "화이트 초코", "알비노", "핀토", "기타(직접 입력)"], "기타": ["기타(직접 입력)"], }, "파충류": { "거북이": ["커먼 머스크 터틀", "레이저백", "육지거북", "붉은귀거북", "남생이", "기타(직접 입력)"], "도마뱀": ["크레스티드 게코", "레오파드 게코", "비어디 드래곤", "블루텅 스킨크", "이구아나", "기타(직접 입력)"], "뱀": ["볼 파이톤", "콘 스네이크", "킹 스네이크", "밀크 스네이크", "기타(직접 입력)"], "기타": ["기타(직접 입력)"], }, "조류": { "앵무새": [ "사랑앵무(잉꼬)", "코카티엘(왕관앵무)", "모란앵무", "코뉴어", "퀘이커", "금강앵무", "기타(직접 입력)", ], "카나리아": ["옐로우 카나리아", "레드 카나리아", "보더 카나리아", "기타(직접 입력)"], "핀치": ["문조", "십자매", "금화조", "호금조", "기타(직접 입력)"], "기타": ["기타(직접 입력)"], }, "어류": { "금붕어": ["오란다", "유금", "단정", "진주린", "코메트", "기타(직접 입력)"], "열대어": ["네온 테트라", "엔젤피쉬", "플래티", "몰리", "디스커스", "기타(직접 입력)"], "구피": ["고정 구피", "막구피(믹스)", "기타(직접 입력)"], "잉어": ["비단잉어", "향어", "기타(직접 입력)"], "기타": ["기타(직접 입력)"], }, "곤충": { "장수풍뎅이": ["국산 장수풍뎅이", "헤라클레스 장수풍뎅이", "코카서스 장수풍뎅이", "기타(직접 입력)"], "사슴벌레": ["넓적사슴벌레", "왕사슴벌레", "톱사슴벌레", "애사슴벌레", "기타(직접 입력)"], "나비/나방": ["배추흰나비", "호랑나비", "누에나방", "기타(직접 입력)"], "사마귀": ["왕사마귀", "사마귀", "넓적배사마귀", "기타(직접 입력)"], "기타": ["기타(직접 입력)"], }, "절지동물": { "타란툴라(거미)": ["로즈헤어", "골든니", "화이트니", "핑크토", "기타(직접 입력)"], "전갈": ["황제전갈", "극동전갈", "아시안 포레스트 전갈", "기타(직접 입력)"], "지네": ["왕지네", "청지네", "기타(직접 입력)"], "소라게": ["인도 소라게", "딸기 소라게", "바이오라센트", "기타(직접 입력)"], "기타": ["기타(직접 입력)"], }, "기타": { "기타(직접 입력)": ["기타(직접 입력)"], }, }; // 선택된 종 정보 (품종 선택을 위해 필요) 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); }); } }, ), 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(); super.dispose(); } 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: _diseaseList.length, padding: const EdgeInsets.symmetric(vertical: 10), itemBuilder: (context, index) { final disease = _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.keys.length, itemBuilder: (context, index) { final major = _petData.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[selectedMajor]!.length, itemBuilder: (context, index) { final minor = _petData[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[_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/profileicon.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), SizedBox(height: 8.h), _buildTextField( controller: _nameController, hint: '이름 입력 (2~10글자/영문/숫자/한글)', inputFormatters: [ LengthLimitingTextInputFormatter(10), // 최대 10글자 제한 ], ), SizedBox(height: 24.h), // 3. 선택 박스들 (종, 품종, 성별) _buildSearchField( '반려동물 종 선택', controller: _speciesController, readOnly: true, onTap: _showSpeciesSelectionModal, ), SizedBox(height: 12.h), _buildSearchField( '반려동물 품종 선택', controller: _breedController, readOnly: true, onTap: _showBreedSelectionModal, ), const SizedBox(height: 12), _buildSearchField( '반려동물 성별', controller: _genderController, readOnly: true, onTap: _showGenderSelectionModal, ), const SizedBox(height: 24), // 4. 생년월일 _buildLabel('반려동물 생년월일', isRequired: true), const SizedBox(height: 8), Row( children: [ Expanded( child: _buildTextField( controller: _yearController, hint: 'YYYY', textAlign: TextAlign.center, hintColor: _isDateUnknown ? const Color(0xFFC8C8C8) : const Color(0xFF7D7C7C), enabled: !_isDateUnknown, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(4), ], ), ), SizedBox(width: 12.w), Expanded( child: _buildTextField( controller: _monthController, 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), ], ), ), SizedBox(width: 12.w), Expanded( child: _buildTextField( controller: _dayController, 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( 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(', '); }); }, ), ), const SizedBox(height: 24), _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(', '); }); }, ), ), const SizedBox(height: 24), _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: () {}, style: ElevatedButton.styleFrom( backgroundColor: AppColors.highlight, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.r), ), ), child: 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( children: [ if (isRequired) Padding( padding: EdgeInsets.only(right: 4.w), child: Icon(Icons.circle, size: 4.w, color: Colors.red), ), Text( text, style: TextStyle( fontFamily: 'SCDream', fontSize: 13.sp, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ], ); } // Helper Widget: 텍스트 입력 필드 Widget _buildTextField({ required String hint, TextAlign textAlign = TextAlign.start, Color? hintColor, bool enabled = true, TextEditingController? controller, TextInputType? keyboardType, List? inputFormatters, }) { return TextField( controller: controller, 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, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontFamily: 'SCDream', fontSize: 13.sp, fontWeight: FontWeight.bold, color: Colors.black87, ), ), 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, ), ), ), ], ); } } // 범위 제한 Formatter (월: 1~12) class _DateRangeInputFormatter extends TextInputFormatter { final int min; final int max; _DateRangeInputFormatter({required this.min, required this.max}); @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { if (newValue.text.isEmpty) { return newValue; } final int? value = int.tryParse(newValue.text); if (value == null) { return oldValue; } if (value < 0) { return oldValue; } // 입력 중에는 자릿수 제한이 있으므로, max값 초과 여부만 확인 if (value > max) { return oldValue; } return newValue; } } // 일(Day) 입력 Formatter (월에 따라 28~31일 제한) class _DayInputFormatter extends TextInputFormatter { final TextEditingController monthController; final TextEditingController yearController; _DayInputFormatter({ required this.monthController, required this.yearController, }); @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { if (newValue.text.isEmpty) { return newValue; } final int? day = int.tryParse(newValue.text); if (day == null) { return oldValue; } int maxDay = 31; final int? month = int.tryParse(monthController.text); if (month != null) { if (month == 2) { maxDay = 29; // 기본 29일 // 윤년 계산 final int? year = int.tryParse(yearController.text); if (year != null) { bool isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); maxDay = isLeap ? 29 : 28; } } else if ([4, 6, 9, 11].contains(month)) { maxDay = 30; } } if (day > maxDay) { return oldValue; } return newValue; } }