상점탭 삭제 / 데일리케어 ui
This commit is contained in:
parent
721b748703
commit
4059ea9af7
@ -1,12 +1,11 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_12643)">
|
||||
<circle cx="10" cy="10" r="10" fill="#FF7500"/>
|
||||
<path d="M11.0585 15.1849H8.92441C6.91251 15.1849 5.90629 15.1849 5.28154 14.5596C4.65678 13.9343 4.65625 12.9286 4.65625 10.9167V9.84969C4.65625 7.83779 4.65625 6.83157 5.28154 6.20682C5.90682 5.58206 6.91251 5.58153 8.92441 5.58153H11.0585C13.0704 5.58153 14.0766 5.58153 14.7014 6.20682C15.3261 6.8321 15.3267 7.83779 15.3267 9.84969V10.9167C15.3267 12.9286 15.3267 13.9349 14.7014 14.5596C14.353 14.9085 13.8862 15.0627 13.1926 15.1305M7.32385 5.58153V4.78125M12.6591 5.58153V4.78125M15.0599 8.24913H9.32455M4.65625 8.24913H6.72364" stroke="white" stroke-width="1.3" stroke-linecap="round"/>
|
||||
<path d="M13.1913 12.5114C13.1913 12.6529 13.1351 12.7886 13.035 12.8886C12.935 12.9887 12.7993 13.0449 12.6578 13.0449C12.5163 13.0449 12.3806 12.9887 12.2805 12.8886C12.1805 12.7886 12.1243 12.6529 12.1243 12.5114C12.1243 12.3699 12.1805 12.2341 12.2805 12.1341C12.3806 12.034 12.5163 11.9778 12.6578 11.9778C12.7993 11.9778 12.935 12.034 13.035 12.1341C13.1351 12.2341 13.1913 12.3699 13.1913 12.5114ZM13.1913 10.3773C13.1913 10.5188 13.1351 10.6545 13.035 10.7545C12.935 10.8546 12.7993 10.9108 12.6578 10.9108C12.5163 10.9108 12.3806 10.8546 12.2805 10.7545C12.1805 10.6545 12.1243 10.5188 12.1243 10.3773C12.1243 10.2358 12.1805 10.1001 12.2805 10C12.3806 9.89996 12.5163 9.84375 12.6578 9.84375C12.7993 9.84375 12.935 9.89996 13.035 10C13.1351 10.1001 13.1913 10.2358 13.1913 10.3773ZM10.5237 12.5114C10.5237 12.6529 10.4675 12.7886 10.3674 12.8886C10.2674 12.9887 10.1317 13.0449 9.99018 13.0449C9.84869 13.0449 9.71298 12.9887 9.61293 12.8886C9.51287 12.7886 9.45666 12.6529 9.45666 12.5114C9.45666 12.3699 9.51287 12.2341 9.61293 12.1341C9.71298 12.034 9.84869 11.9778 9.99018 11.9778C10.1317 11.9778 10.2674 12.034 10.3674 12.1341C10.4675 12.2341 10.5237 12.3699 10.5237 12.5114ZM10.5237 10.3773C10.5237 10.5188 10.4675 10.6545 10.3674 10.7545C10.2674 10.8546 10.1317 10.9108 9.99018 10.9108C9.84869 10.9108 9.71298 10.8546 9.61293 10.7545C9.51287 10.6545 9.45666 10.5188 9.45666 10.3773C9.45666 10.2358 9.51287 10.1001 9.61293 10C9.71298 9.89996 9.84869 9.84375 9.99018 9.84375C10.1317 9.84375 10.2674 9.89996 10.3674 10C10.4675 10.1001 10.5237 10.2358 10.5237 10.3773ZM7.8561 12.5114C7.8561 12.6529 7.79989 12.7886 7.69984 12.8886C7.59978 12.9887 7.46408 13.0449 7.32258 13.0449C7.18108 13.0449 7.04538 12.9887 6.94533 12.8886C6.84527 12.7886 6.78906 12.6529 6.78906 12.5114C6.78906 12.3699 6.84527 12.2341 6.94533 12.1341C7.04538 12.034 7.18108 11.9778 7.32258 11.9778C7.46408 11.9778 7.59978 12.034 7.69984 12.1341C7.79989 12.2341 7.8561 12.3699 7.8561 12.5114ZM7.8561 10.3773C7.8561 10.5188 7.79989 10.6545 7.69984 10.7545C7.59978 10.8546 7.46408 10.9108 7.32258 10.9108C7.18108 10.9108 7.04538 10.8546 6.94533 10.7545C6.84527 10.6545 6.78906 10.5188 6.78906 10.3773C6.78906 10.2358 6.84527 10.1001 6.94533 10C7.04538 9.89996 7.18108 9.84375 7.32258 9.84375C7.46408 9.84375 7.59978 9.89996 7.69984 10C7.79989 10.1001 7.8561 10.2358 7.8561 10.3773Z" fill="white"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_77_604)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5 3.75376C13.5 4.15158 13.342 4.53311 13.0607 4.81442C12.7794 5.09572 12.3978 5.25376 12 5.25376C11.6022 5.25376 11.2206 5.09572 10.9393 4.81442C10.658 4.53311 10.5 4.15158 10.5 3.75376C10.5 3.35593 10.658 2.9744 10.9393 2.6931C11.2206 2.41179 11.6022 2.25376 12 2.25376C12.3978 2.25376 12.7794 2.41179 13.0607 2.6931C13.342 2.9744 13.5 3.35593 13.5 3.75376ZM15.675 3.00376C15.5029 2.15604 15.043 1.3939 14.3732 0.846476C13.7034 0.299049 12.865 0 12 0C11.135 0 10.2966 0.299049 9.62681 0.846476C8.95705 1.3939 8.49714 2.15604 8.325 3.00376H4.5C4.10218 3.00376 3.72064 3.16179 3.43934 3.4431C3.15804 3.7244 3 4.10593 3 4.50376V22.5038C3 22.9016 3.15804 23.2831 3.43934 23.5644C3.72064 23.8457 4.10218 24.0038 4.5 24.0038H19.5C19.8978 24.0038 20.2794 23.8457 20.5607 23.5644C20.842 23.2831 21 22.9016 21 22.5038V4.50376C21 4.10593 20.842 3.7244 20.5607 3.4431C20.2794 3.16179 19.8978 3.00376 19.5 3.00376H15.675ZM12 7.50376H8.25V5.25376H5.25V21.7538H18.75V5.25376H15.75V7.50376H12Z" fill="#C8C8C8"/>
|
||||
<path d="M8.47461 10.5283C8.2757 10.5283 8.08493 10.6073 7.94428 10.748C7.80363 10.8886 7.72461 11.0794 7.72461 11.2783C7.72461 11.4772 7.80363 11.668 7.94428 11.8087C8.08493 11.9493 8.2757 12.0283 8.47461 12.0283H15.5297C15.7286 12.0283 15.9194 11.9493 16.0601 11.8087C16.2007 11.668 16.2797 11.4772 16.2797 11.2783C16.2797 11.0794 16.2007 10.8886 16.0601 10.748C15.9194 10.6073 15.7286 10.5283 15.5297 10.5283H8.47461ZM8.47461 13.5283C8.2757 13.5283 8.08493 13.6073 7.94428 13.748C7.80363 13.8886 7.72461 14.0794 7.72461 14.2783C7.72461 14.4772 7.80363 14.668 7.94428 14.8087C8.08493 14.9493 8.2757 15.0283 8.47461 15.0283H15.5297C15.7286 15.0283 15.9194 14.9493 16.0601 14.8087C16.2007 14.668 16.2797 14.4772 16.2797 14.2783C16.2797 14.0794 16.2007 13.8886 16.0601 13.748C15.9194 13.6073 15.7286 13.5283 15.5297 13.5283H8.47461ZM7.72461 17.2783C7.72461 17.0794 7.80363 16.8886 7.94428 16.748C8.08493 16.6073 8.2757 16.5283 8.47461 16.5283H14.0297C14.2286 16.5283 14.4194 16.6073 14.5601 16.748C14.7007 16.8886 14.7797 17.0794 14.7797 17.2783C14.7797 17.4772 14.7007 17.668 14.5601 17.8087C14.4194 17.9493 14.2286 18.0283 14.0297 18.0283H8.47461C8.2757 18.0283 8.08493 17.9493 7.94428 17.8087C7.80363 17.668 7.72461 17.4772 7.72461 17.2783Z" fill="#C8C8C8"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_12643">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
<clipPath id="clip0_77_604">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.5 KiB |
10
app/assets/icons/left_icon.svg
Normal file
10
app/assets/icons/left_icon.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_80_2119)">
|
||||
<path d="M7.71875 10.4297L3.28906 6L7.71875 1.57031L7.71875 10.4297Z" fill="#1F1F1F" stroke="#1F1F1F" stroke-width="2.95312" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_80_2119">
|
||||
<rect width="12" height="12" fill="white" transform="translate(12 12) rotate(-180)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 438 B |
10
app/assets/icons/right_icon.svg
Normal file
10
app/assets/icons/right_icon.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_80_2122)">
|
||||
<path d="M4.28906 1.57031L8.71875 6L4.28906 10.4297V1.57031Z" fill="#1F1F1F" stroke="#1F1F1F" stroke-width="2.95312" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_80_2122">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
@ -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<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await initializeDateFormatting('ko_KR', null); // Add this line
|
||||
|
||||
// 글로벌 에러 핸들링
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
|
||||
607
app/lib/screens/daily_care_screen.dart
Normal file
607
app/lib/screens/daily_care_screen.dart
Normal file
@ -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<DailyCareScreen> createState() => _DailyCareScreenState();
|
||||
}
|
||||
|
||||
class _DailyCareScreenState extends State<DailyCareScreen> {
|
||||
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<DateTime, List<String>> _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<String> _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<List<Pet>>(
|
||||
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), // 스크롤 테스트용 여백
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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<MainScreen> {
|
||||
// 탭별 화면 리스트
|
||||
final List<Widget> _screens = [
|
||||
const HomeScreen(),
|
||||
const ReservationScreen(),
|
||||
const MungNyangzScreen(),
|
||||
const ShopScreen(),
|
||||
const DailyCareScreen(),
|
||||
const MungNyangzScreen(), // 멍냥즈 (샵 대신)
|
||||
const MyInfoScreen(),
|
||||
];
|
||||
|
||||
@ -94,9 +92,9 @@ class _MainScreenState extends State<MainScreen> {
|
||||
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<MainScreen> {
|
||||
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: '내 정보',
|
||||
),
|
||||
|
||||
@ -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<PetFormScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user