import 'dart:async'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import '../models/todo_item.dart'; import '../models/family_member.dart'; import '../services/todo_service.dart'; import '../services/family_service.dart'; class TodoListWidget extends StatefulWidget { const TodoListWidget({super.key}); @override State createState() => _TodoListWidgetState(); } class _TodoListWidgetState extends State { Timer? _timer; List _todos = []; List _members = []; bool _isLoading = true; String? _error; @override void initState() { super.initState(); _fetchData(); _startAutoRefresh(); } void _startAutoRefresh() { _timer = Timer.periodic(const Duration(seconds: 30), (timer) { _fetchData(); }); } Future _fetchData() async { try { final results = await Future.wait([ Provider.of( context, listen: false, ).fetchTodayTodos(), Provider.of( context, listen: false, ).fetchFamilyMembers(), ]); if (mounted) { setState(() { _todos = results[0] as List; _members = results[1] as List; _isLoading = false; _error = null; }); } } catch (e) { if (mounted) { setState(() { _error = 'Failed to load todos'; _isLoading = false; }); } } } @override void dispose() { _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Theme.of(context).cardTheme.color, borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "오늘의 할 일", style: Theme.of(context).textTheme.titleLarge?.copyWith( color: Colors.white, fontWeight: FontWeight.bold, ), ), Icon( Icons.check_circle_outline, color: Theme.of(context).colorScheme.secondary, ), ], ), const SizedBox(height: 12), Expanded( child: _buildContent(), ), ], ), ); } Widget _buildContent() { if (_isLoading && _todos.isEmpty) { return const Center(child: CircularProgressIndicator()); } if (_error != null && _todos.isEmpty) { return Center( child: Text( _error!, style: const TextStyle(color: Colors.white54), ), ); } if (_todos.isEmpty) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.thumb_up, color: Colors.white24, size: 32), SizedBox(height: 8), Text( '오늘 할 일을 모두 마쳤습니다!', style: TextStyle(color: Colors.white54), ), ], ), ); } return ListView.separated( itemCount: _todos.length, separatorBuilder: (context, index) => const Divider(color: Colors.white10), itemBuilder: (context, index) { final todo = _todos[index]; final member = _members.firstWhere( (m) => m.id == todo.familyMemberId, orElse: () => const FamilyMember( id: '', name: 'Unknown', iconUrl: '', emoji: '👤', color: '#888888', order: 0, ), ); // Parse color Color memberColor; try { String cleanHex = member.color.replaceAll('#', '').replaceAll('0x', ''); if (cleanHex.length == 6) { cleanHex = 'FF$cleanHex'; } if (cleanHex.length == 8) { memberColor = Color(int.parse('0x$cleanHex')); } else { memberColor = Colors.grey; } } catch (_) { memberColor = Colors.grey; } return ListTile( contentPadding: EdgeInsets.zero, leading: CircleAvatar( backgroundColor: memberColor.withOpacity(0.2), child: member.iconUrl.isNotEmpty ? ClipOval( child: Image.network( member.iconUrl, width: 40, height: 40, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Text( member.name.isNotEmpty ? member.name[0].toUpperCase() : '?', style: TextStyle( color: memberColor, fontWeight: FontWeight.bold, fontSize: 20, ), ); }, ), ) : (member.name.isNotEmpty ? Text( member.name[0].toUpperCase(), style: TextStyle( color: memberColor, fontWeight: FontWeight.bold, fontSize: 20, ), ) : Icon( Icons.person, color: memberColor, )), ), title: Text( todo.title, style: TextStyle( color: todo.completed ? Colors.white54 : Colors.white, decoration: todo.completed ? TextDecoration.lineThrough : null, decorationColor: Colors.white54, ), ), subtitle: todo.dueDate != null && (todo.dueDate!.hour != 0 || todo.dueDate!.minute != 0) ? Text( DateFormat('HH:mm').format(todo.dueDate!), style: const TextStyle(color: Colors.white54, fontSize: 12), ) : null, trailing: Checkbox( value: todo.completed, onChanged: (val) async { // Toggle completion final updated = TodoItem( id: todo.id, familyMemberId: todo.familyMemberId, title: todo.title, completed: val ?? false, dueDate: todo.dueDate, ); // Optimistic update setState(() { final idx = _todos.indexWhere((t) => t.id == todo.id); if (idx != -1) { _todos[idx] = updated; } }); await Provider.of( context, listen: false, ).updateTodo(updated); // Refresh to ensure sync _fetchData(); }, activeColor: Theme.of(context).colorScheme.secondary, checkColor: Colors.black, ), ); }, ); } }