diff --git a/app/assets/icons/calendar_icon.svg b/app/assets/icons/calendar_icon.svg index de904fd..568a3b5 100644 --- a/app/assets/icons/calendar_icon.svg +++ b/app/assets/icons/calendar_icon.svg @@ -1,12 +1,11 @@ - - - - - + + + + - - + + diff --git a/app/assets/icons/left_icon.svg b/app/assets/icons/left_icon.svg new file mode 100644 index 0000000..5c32193 --- /dev/null +++ b/app/assets/icons/left_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/assets/icons/right_icon.svg b/app/assets/icons/right_icon.svg new file mode 100644 index 0000000..319ebc8 --- /dev/null +++ b/app/assets/icons/right_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/lib/main.dart b/app/lib/main.dart index d3de19b..aacc0e1 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:intl/date_symbol_data_local.dart'; // Import here import 'dart:developer'; import 'screens/splash_screen.dart'; import 'screens/register_complete_screen.dart'; @@ -11,6 +12,7 @@ final GlobalKey navigatorKey = GlobalKey(); void main() async { WidgetsFlutterBinding.ensureInitialized(); + await initializeDateFormatting('ko_KR', null); // Add this line // 글로벌 에러 핸들링 FlutterError.onError = (FlutterErrorDetails details) { diff --git a/app/lib/screens/daily_care_screen.dart b/app/lib/screens/daily_care_screen.dart new file mode 100644 index 0000000..dfbbf0b --- /dev/null +++ b/app/lib/screens/daily_care_screen.dart @@ -0,0 +1,607 @@ +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( + bottom: 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), // 스크롤 테스트용 여백 + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/app/lib/screens/main_screen.dart b/app/lib/screens/main_screen.dart index 1752423..a862b64 100644 --- a/app/lib/screens/main_screen.dart +++ b/app/lib/screens/main_screen.dart @@ -2,9 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; // Import screenutil import 'home_screen.dart'; -import 'reservation_screen.dart'; +import 'daily_care_screen.dart'; import 'mungnyangz_screen.dart'; -import 'shop_screen.dart'; import 'my_info_screen.dart'; import '../theme/app_colors.dart'; @@ -22,9 +21,8 @@ class _MainScreenState extends State { // 탭별 화면 리스트 final List _screens = [ const HomeScreen(), - const ReservationScreen(), - const MungNyangzScreen(), - const ShopScreen(), + const DailyCareScreen(), + const MungNyangzScreen(), // 멍냥즈 (샵 대신) const MyInfoScreen(), ]; @@ -94,9 +92,9 @@ class _MainScreenState extends State { BottomNavigationBarItem( icon: Padding( padding: EdgeInsets.only(bottom: 10.h), - child: _buildSvgIcon('assets/icons/appointment_icon.svg', 1), + child: _buildSvgIcon('assets/icons/calendar_icon.svg', 1), ), - label: '예약/조회', + label: '데일리케어', ), BottomNavigationBarItem( icon: Padding( @@ -115,14 +113,10 @@ class _MainScreenState extends State { BottomNavigationBarItem( icon: Padding( padding: EdgeInsets.only(bottom: 10.h), - child: _buildSvgIcon('assets/icons/shop_icon.svg', 3), - ), - label: '상점', - ), - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.only(bottom: 10.h), - child: _buildSvgIcon('assets/icons/my_icon.svg', 4), + child: _buildSvgIcon( + 'assets/icons/my_icon.svg', + 3, + ), // Index adjusted ), label: '내 정보', ), diff --git a/app/lib/screens/pet_form_screen.dart b/app/lib/screens/pet_form_screen.dart index 05baf7d..d4555c4 100644 --- a/app/lib/screens/pet_form_screen.dart +++ b/app/lib/screens/pet_form_screen.dart @@ -6,8 +6,6 @@ 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 '../widgets/pet_registration/selection_modal.dart'; -import '../widgets/pet_registration/input_formatters.dart'; import '../services/firestore_service.dart'; import '../models/pet_model.dart'; @@ -1724,3 +1722,86 @@ class _PetFormScreenState extends State { ); } } + +// --- Helper Classes (Restored) --- + +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 < min || value > max) { + return oldValue; + } + + return newValue; + } +} + +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? month = int.tryParse(monthController.text); + int? year = int.tryParse(yearController.text); + + if (month == null || month < 1 || month > 12) { + // 월이 입력되지 않았거나 유효하지 않으면 31일까지 허용 + if (day < 1 || day > 31) return oldValue; + return newValue; + } + + int maxDay = _getDaysInMonth(year, month); + if (day < 1 || day > maxDay) { + return oldValue; + } + + return newValue; + } + + int _getDaysInMonth(int? year, int month) { + if (month == 2) { + if (year != null && + ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { + return 29; + } + return 28; + } + const daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + return daysInMonth[month]; + } +} diff --git a/app/lib/screens/shop_screen.dart b/app/lib/screens/shop_screen.dart deleted file mode 100644 index 4152071..0000000 --- a/app/lib/screens/shop_screen.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; // Import screenutil - -class ShopScreen extends StatelessWidget { - const ShopScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: SafeArea( - child: Center( - child: Text( - '상점 화면 준비 중입니다.', - style: TextStyle(fontFamily: 'SCDream', fontSize: 14.sp), - ), - ), - ), - ); - } -} diff --git a/app/pubspec.lock b/app/pubspec.lock index 412931c..0a6df21 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -720,6 +720,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + simple_gesture_detector: + dependency: transitive + description: + name: simple_gesture_detector + sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3 + url: "https://pub.dev" + source: hosted + version: "0.2.1" sky_engine: dependency: transitive description: flutter @@ -757,6 +765,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + table_calendar: + dependency: "direct main" + description: + name: table_calendar + sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63 + url: "https://pub.dev" + source: hosted + version: "3.1.3" term_glyph: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index fcd7b52..b6978f7 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: cloud_firestore: ^5.0.0 firebase_storage: ^12.0.0 uuid: ^4.0.0 + table_calendar: ^3.0.9 dev_dependencies: flutter_test: