Refine schedule/todo UI and integrate Google Photos API

This commit is contained in:
kihong.kim
2026-02-01 00:30:32 +09:00
parent 7ddd29dfed
commit c614c883d4
15 changed files with 2124 additions and 565 deletions

View File

@@ -1,9 +1,51 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../models/schedule_item.dart';
import '../services/schedule_service.dart';
class CalendarWidget extends StatelessWidget {
class CalendarWidget extends StatefulWidget {
const CalendarWidget({super.key});
@override
State<CalendarWidget> createState() => _CalendarWidgetState();
}
class _CalendarWidgetState extends State<CalendarWidget> {
List<ScheduleItem> _schedules = [];
@override
void initState() {
super.initState();
_loadSchedules();
}
Future<void> _loadSchedules() async {
try {
final schedules =
await Provider.of<ScheduleService>(context, listen: false)
.fetchMonthlySchedules();
if (mounted) {
setState(() {
_schedules = schedules;
});
}
} catch (e) {
debugPrint('Error loading schedules: $e');
}
}
bool _hasScheduleOn(DateTime date) {
final checkDate = DateTime(date.year, date.month, date.day);
return _schedules.any((s) {
final start =
DateTime(s.startDate.year, s.startDate.month, s.startDate.day);
final end = DateTime(s.endDate.year, s.endDate.month, s.endDate.day);
return (checkDate.isAtSameMomentAs(start) || checkDate.isAfter(start)) &&
(checkDate.isAtSameMomentAs(end) || checkDate.isBefore(end));
});
}
@override
Widget build(BuildContext context) {
final now = DateTime.now();
@@ -12,19 +54,10 @@ class CalendarWidget extends StatelessWidget {
final daysInMonth = lastDayOfMonth.day;
final startingWeekday = firstDayOfMonth.weekday; // Mon=1, Sun=7
// Simple calendar logic
// We need to pad the beginning with empty slots
// If week starts on Sunday, adjust accordingly. Let's assume Mon start for now or use locale.
// Let's assume standard Sun-Sat or Mon-Sun. Let's go with Sun-Sat for standard calendar view often seen in KR/US.
// DateTime.weekday: Mon=1, Sun=7.
// If we want Sun start: Sun=0, Mon=1...
// Let's adjust so Sunday is first.
int offset = startingWeekday %
7; // If startingWeekday is 7 (Sun), offset is 0. If 1 (Mon), offset is 1.
int offset = startingWeekday % 7;
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).cardTheme.color,
borderRadius: BorderRadius.circular(16),
@@ -32,38 +65,45 @@ class CalendarWidget extends StatelessWidget {
child: Column(
children: [
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
DateFormat('MMMM yyyy').format(now),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const Icon(Icons.calendar_today, color: Colors.white54),
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
DateFormat('yyyy년 M월').format(now),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20,
),
),
const Icon(Icons.calendar_today,
color: Colors.white54, size: 20),
],
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
// Days Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: ['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day) {
children: ['', '', '', '', '', '', ''].asMap().entries.map((entry) {
final isSunday = entry.key == 0;
return Expanded(
child: Center(
child: Text(
day,
style: const TextStyle(
color: Colors.white54,
entry.value,
style: TextStyle(
color: isSunday ? Colors.redAccent : Colors.white54,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
);
}).toList(),
),
const SizedBox(height: 8),
const SizedBox(height: 4),
// Days Grid
Expanded(
child: Column(
@@ -73,36 +113,60 @@ class CalendarWidget extends StatelessWidget {
children: List.generate(7, (col) {
final index = row * 7 + col;
final dayNumber = index - offset + 1;
final isSunday = col == 0;
if (dayNumber < 1 || dayNumber > daysInMonth) {
return const Expanded(child: SizedBox.shrink());
}
final dayDate =
DateTime(now.year, now.month, dayNumber);
final isToday = dayNumber == now.day;
final hasSchedule = _hasScheduleOn(dayDate);
return Expanded(
child: Container(
margin: const EdgeInsets.all(2),
margin: const EdgeInsets.all(1),
decoration: isToday
? BoxDecoration(
color: Theme.of(context).colorScheme.primary,
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 2,
),
shape: BoxShape.circle,
)
: null,
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'$dayNumber',
style: TextStyle(
color: isToday ? Colors.black : Colors.white,
fontWeight: isToday
? FontWeight.bold
: FontWeight.normal,
fontSize: 12,
child: Stack(
alignment: Alignment.center,
children: [
Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'$dayNumber',
style: TextStyle(
color: isToday
? Theme.of(context).colorScheme.primary
: (isSunday ? Colors.redAccent : Colors.white),
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
),
),
if (hasSchedule) // Show indicator even if it's today
Positioned(
bottom: 2,
child: Container(
width: 14,
height: 4,
decoration: BoxDecoration(
color: Colors.yellowAccent,
borderRadius: BorderRadius.circular(2),
),
),
),
],
),
),
);