import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:intl/intl.dart'; import '../models/pet_model.dart'; import '../services/firestore_service.dart'; import '../theme/app_colors.dart'; class DailyCareScreen extends StatefulWidget { const DailyCareScreen({super.key}); @override State createState() => _DailyCareScreenState(); } class _DailyCareScreenState extends State { final FirestoreService _firestoreService = FirestoreService(); DateTime _focusedDay = DateTime.now(); DateTime _selectedDay = DateTime.now(); String? _userId; Pet? _selectedPet; final DraggableScrollableController _sheetController = DraggableScrollableController(); @override void dispose() { _sheetController.dispose(); super.dispose(); } DateTime _normalizeDate(DateTime date) { return DateTime(date.year, date.month, date.day); } late final Map> _events; @override void initState() { super.initState(); _userId = _firestoreService.getCurrentUserId(); final today = _normalizeDate(DateTime.now()); _events = { today: [ 'flower', 'flower', 'flower', 'flower', 'incomplete', 'flower', 'important', 'general', 'general', ], // 오늘: 9개 (3x3 테스트) today.subtract(const Duration(days: 1)): [ 'flower', 'flower', 'flower', ], // 어제: 완료3 today.subtract(const Duration(days: 2)): ['general'], // 2일전: 일반1 today.subtract(const Duration(days: 3)): [ 'incomplete', 'flower', ], // 3일전: 실패1, 완료1 today.subtract(const Duration(days: 5)): [ 'flower', 'flower', 'flower', ], // 5일전: 완료3 today.subtract(const Duration(days: 7)): [ 'incomplete', 'incomplete', ], // 7일전: 실패2 today.subtract(const Duration(days: 10)): [ 'important', 'general', ], // 10일전: 중요1, 일반1 }; } List _getEventsForDay(DateTime day) { return _events[_normalizeDate(day)] ?? []; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: Stack( children: [ Column( children: [ _buildHeader(), SizedBox(height: 30.h), // Equal spacing 1 _buildCustomCalendarHeader(), // Custom Header SizedBox(height: 30.h), // Equal spacing 2 Expanded( child: Padding( padding: EdgeInsets.symmetric(horizontal: 20.w), child: _buildCalendar(), ), ), ], ), _buildBottomSheet(), ], ), ), floatingActionButton: FloatingActionButton( heroTag: 'daily_care_fab', onPressed: () { // 일정 추가 로직 (추후 구현) ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('일정 추가 기능은 준비 중입니다.'))); }, backgroundColor: AppColors.highlight, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), child: const Icon(Icons.add, color: Colors.white), ), ); } Widget _buildHeader() { return Padding( padding: EdgeInsets.only(left: 20.w, right: 20.w, top: 10.h, bottom: 0), child: StreamBuilder>( stream: _userId != null ? _firestoreService.getPets(_userId!) : const Stream.empty(), builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data!.isEmpty) { return const SizedBox.shrink(); } final pets = snapshot.data!; if (_selectedPet == null || !pets.any((p) => p.id == _selectedPet!.id)) { _selectedPet = pets.first; } return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ InkWell( onTap: () {}, child: Row( children: [ CircleAvatar( radius: 20.r, backgroundColor: Colors.grey[200], backgroundImage: _selectedPet!.profileImageUrl != null ? NetworkImage(_selectedPet!.profileImageUrl!) : null, child: _selectedPet!.profileImageUrl == null ? SvgPicture.asset( 'assets/icons/profile_icon.svg', width: 20.w, colorFilter: ColorFilter.mode( Colors.grey[400]!, BlendMode.srcIn, ), ) : null, ), SizedBox(width: 8.w), Text( _selectedPet!.name, style: TextStyle( fontFamily: 'SCDream', fontSize: 18.sp, fontWeight: FontWeight.bold, ), ), Icon(Icons.keyboard_arrow_down, size: 24.w), ], ), ), ], ); }, ), ); } Widget _buildCustomCalendarHeader() { return Padding( padding: EdgeInsets.symmetric( horizontal: 20.w, ), // Removed vertical padding child: Stack( alignment: Alignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ InkWell( onTap: () { setState(() { _focusedDay = DateTime( _focusedDay.year, _focusedDay.month - 1, ); }); }, child: SvgPicture.asset( 'assets/icons/left_icon.svg', width: 12.w, // Changed to 12px height: 12.w, ), ), SizedBox(width: 30.w), Text( DateFormat('yyyy년 M월', 'ko_KR').format(_focusedDay), style: TextStyle( fontFamily: 'SCDream', fontSize: 18.sp, fontWeight: FontWeight.bold, ), ), SizedBox(width: 30.w), InkWell( onTap: () { setState(() { _focusedDay = DateTime( _focusedDay.year, _focusedDay.month + 1, ); }); }, child: SvgPicture.asset( 'assets/icons/right_icon.svg', width: 12.w, // Changed to 12px height: 12.w, ), ), ], ), Positioned( right: 0, child: Container( width: 40.w, height: 40.w, decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(12.r), ), child: const Icon(Icons.download, color: Colors.white, size: 20), ), ), ], ), ); } Widget _buildCalendar() { return TableCalendar( shouldFillViewport: false, locale: 'ko_KR', rowHeight: 85 .h, // Increased to fit 3 rows of icons (approx 40px + 32px top offset) daysOfWeekHeight: 30.h, firstDay: DateTime.utc(2020, 1, 1), lastDay: DateTime.utc(2030, 12, 31), focusedDay: _focusedDay, selectedDayPredicate: (day) => isSameDay(_selectedDay, day), headerVisible: false, // Hide default header onDaySelected: (selectedDay, focusedDay) { setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; }); }, onPageChanged: (focusedDay) { _focusedDay = focusedDay; }, calendarStyle: CalendarStyle( defaultTextStyle: TextStyle( fontFamily: 'SCDream', fontWeight: FontWeight.w500, fontSize: 15.sp, color: const Color(0xFF1f1f1f), ), weekendTextStyle: TextStyle( fontFamily: 'SCDream', fontWeight: FontWeight.w500, fontSize: 15.sp, color: const Color(0xFF1f1f1f), ), todayDecoration: const BoxDecoration( color: Colors.transparent, shape: BoxShape.circle, ), todayTextStyle: const TextStyle( color: AppColors.highlight, fontWeight: FontWeight.bold, ), selectedDecoration: const BoxDecoration( color: Colors.transparent, shape: BoxShape.circle, ), selectedTextStyle: const TextStyle( color: Colors.black, fontWeight: FontWeight.bold, decoration: TextDecoration.underline, ), outsideDaysVisible: true, // Show outside days ), eventLoader: _getEventsForDay, // 이벤트 로더 calendarBuilders: CalendarBuilders( // 마커 커스텀 빌더 markerBuilder: (context, day, events) { if (events.isEmpty) return null; // 외부 날짜인지 확인 (투명도 적용을 위해) // 주의: _focusedDay는 페이지가 바뀔 때만 업데이트되므로, // 달력의 현재 페이지 월과 day의 월을 비교해야 함. // 하지만 markerBuilder는 페이징 중에 다시 빌드될 수 있음. bool isOutside = day.month != _focusedDay.month; double opacity = isOutside ? 0.5 : 1.0; // 이벤트가 있으면 아이콘 표시 (최대 9개 등 제한 가능) return Positioned( top: 32.h, left: 0, right: 0, child: Opacity( opacity: opacity, child: Center( child: Container( width: 40.w, // Exact width: 12*3 + 2*2 = 40. Centers perfectly. child: Wrap( alignment: WrapAlignment.start, // Left aligned spacing: 2.w, // Horizontal spacing runSpacing: 2.h, // Vertical spacing children: events.take(9).map((event) { String iconPath = 'assets/icons/general_schedule_icon.svg'; switch (event) { case 'flower': iconPath = 'assets/icons/flower_icon.svg'; break; case 'incomplete': iconPath = 'assets/icons/incomplete_icon.svg'; break; case 'important': iconPath = 'assets/icons/important_schedule_icon.svg'; break; case 'general': iconPath = 'assets/icons/general_schedule_icon.svg'; break; } return SvgPicture.asset( iconPath, width: 12.w, height: 12.w, ); }).toList(), ), ), ), ), ); }, // 요일 헤더 커스텀 dowBuilder: (context, day) { final text = DateFormat.E('ko_KR').format(day); // 요일 헤더는 '현재 달' 개념이 모호하므로(항상 보임) 기본 색상 사용 Color color = const Color(0xFF939393); if (day.weekday == DateTime.sunday) color = const Color(0xFFFF3F3F); // 일요일 #FF3F3F로 복귀 return Container( // Removed bottom border to avoid double border with first row's top border padding: EdgeInsets.only(bottom: 10.h), alignment: Alignment.bottomCenter, child: Text( text, style: TextStyle( color: color, fontFamily: 'SCDream', fontSize: 12.sp, fontWeight: FontWeight.w500, ), ), ); }, // 외부 날짜 (지난달/다음달) 커스텀 outsideBuilder: (context, day, focusedDay) { // 기본 색상에서 투명도 50% 적용 Color color = const Color(0xFF1F1F1F).withOpacity(0.5); // 평일 50% if (day.weekday == DateTime.sunday) { color = const Color(0xFFFF3F3F).withOpacity(0.5); // 일요일 50% } return Container( decoration: const BoxDecoration( border: Border( top: BorderSide( color: Color(0xFFE0E0E0), width: 1, ), // Changed to top ), ), padding: EdgeInsets.only(top: 8.h), alignment: Alignment.topCenter, child: Text( day.day.toString().padLeft(2, '0'), style: TextStyle( fontFamily: 'SCDream', fontSize: 15.sp, fontWeight: FontWeight.w500, color: color, ), ), ); }, // 날짜 셀 커스텀 defaultBuilder: (context, day, focusedDay) { // 현재 달 날짜 색상 Color color = const Color(0xFF1F1F1F); if (day.weekday == DateTime.sunday) { color = const Color(0xFFFF3F3F); } return Container( decoration: const BoxDecoration( border: Border( top: BorderSide( color: Color(0xFFE0E0E0), width: 1, ), // Changed to top ), ), padding: EdgeInsets.only(top: 8.h), // Move closer to top line alignment: Alignment.topCenter, // Align to top child: Text( day.day.toString().padLeft(2, '0'), style: TextStyle( fontFamily: 'SCDream', fontSize: 15.sp, fontWeight: FontWeight.w500, color: color, ), ), ); }, // 오늘/선택 날짜 커스텀 prioritizedBuilder: (context, day, focusedDay) { final isToday = isSameDay(day, DateTime.now()); final isSelected = isSameDay(day, _selectedDay); // 우선순위가 없는 날짜는 null을 반환하여 default/outsideBuilder가 실행되도록 함 if (!isToday && !isSelected) return null; final isSunday = day.weekday == DateTime.sunday; // 텍스트 색상 결정 Color textColor = const Color(0xFF1F1F1F); // 기본 검정 if (isToday) { textColor = const Color(0xFFFF9500); // 오늘: #FF9500 (유지) } else if (isSunday) { textColor = const Color(0xFFFF3F3F); // 일요일: #FF3F3F } // 배경 색상/데코레이션 결정 (선택된 경우) BoxDecoration? innerDecoration; if (isSelected) { innerDecoration = BoxDecoration( color: const Color(0xFFFFEDBC), // 선택 배경: #FFEDBC borderRadius: BorderRadius.circular(6.r), // 모서리 덜 둥글게 (6) ); } return Container( // 바깥 컨테이너: 행 구분선 담당 decoration: const BoxDecoration( border: Border( top: BorderSide(color: Color(0xFFE0E0E0), width: 1), ), ), child: Container( // 내부 컨테이너: 선택 배경 담당 (가로 여백 제거로 넓게) margin: EdgeInsets.symmetric(vertical: 2.h), decoration: innerDecoration, alignment: Alignment.topCenter, // 상단 정렬 padding: EdgeInsets.only(top: 6.h), // 텍스트 위 여백 child: Text( day.day.toString().padLeft(2, '0'), style: TextStyle( fontFamily: 'SCDream', fontSize: 15.sp, fontWeight: FontWeight.bold, // 오늘/선택은 강조 decoration: isSelected ? null : null, color: textColor, ), ), ), ); }, ), ); } Widget _buildBottomSheet() { return DraggableScrollableSheet( controller: _sheetController, // 컨트롤러 연결 initialChildSize: 0.08, minChildSize: 0.08, maxChildSize: 0.8, builder: (context, scrollController) { return Container( margin: EdgeInsets.symmetric(horizontal: 20.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), // Lighter shadow blurRadius: 5, // Tighter blur offset: const Offset(0, -3), // Closer offset ), ], ), child: SingleChildScrollView( controller: scrollController, child: Padding( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h), child: Column( children: [ // 헤더 (터치 영역) GestureDetector( onTap: () { // 현재 높이에 따라 토글 if (_sheetController.size > 0.4) { _sheetController.animateTo( 0.08, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } else { _sheetController.animateTo( 0.8, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } }, child: Container( color: Colors.transparent, // 터치 영역 확장 width: double.infinity, child: Column( children: [ AnimatedBuilder( animation: _sheetController, builder: (context, child) { // 0.08 ~ 0.8 사이 값에서 회전 각도 계산 // 0.4 이상이면 완전히 회전하도록 설정하거나, 비례해서 회전 // 간단하게 0.4 기준으로 상태 판단 final double angle = _sheetController.isAttached && _sheetController.size > 0.4 ? 3.14159 : 0.0; return Transform.rotate( angle: angle, child: Icon( Icons.keyboard_arrow_up_rounded, color: Colors.grey[400], size: 24.w, ), ); }, ), SizedBox(height: 20.h), ], ), ), ), Align( alignment: Alignment.centerLeft, child: Text( '중요 일정 1', style: TextStyle( fontFamily: 'SCDream', fontSize: 14.sp, color: Colors.grey[600], ), ), ), SizedBox(height: 50.h), // 스크롤 테스트용 여백 ], ), ), ), ); }, ); } }