import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../models/bible_verse.dart'; import '../../models/family_member.dart'; import '../../models/photo.dart'; import '../../models/announcement.dart'; import '../../models/schedule_item.dart'; import '../../services/bible_verse_service.dart'; import '../../services/family_service.dart'; import '../../services/photo_service.dart'; import '../../services/announcement_service.dart'; import '../../services/schedule_service.dart'; import '../../models/todo_item.dart'; import '../../services/todo_service.dart'; class AdminScreen extends StatefulWidget { const AdminScreen({super.key}); @override State createState() => _AdminScreenState(); } class _AdminScreenState extends State { @override Widget build(BuildContext context) { return DefaultTabController( length: 6, child: Scaffold( appBar: AppBar( title: const Text('Admin Settings'), bottom: const TabBar( isScrollable: true, tabs: [ Tab(text: 'Family Members'), Tab(text: 'Photos'), Tab(text: 'Bible Verses'), Tab(text: 'Announcements'), Tab(text: 'Schedules'), Tab(text: 'Todos'), ], ), ), body: const TabBarView( children: [ FamilyManagerTab(), PhotoManagerTab(), BibleVerseManagerTab(), AnnouncementManagerTab(), ScheduleManagerTab(), TodoManagerTab(), ], ), ), ); } } class FamilyManagerTab extends StatefulWidget { const FamilyManagerTab({super.key}); @override State createState() => _FamilyManagerTabState(); } class _FamilyManagerTabState extends State { // Rainbow palette colors final List _palette = const [ Color(0xFFFF0000), // Red Color(0xFFFF7F00), // Orange Color(0xFFFFFF00), // Yellow Color(0xFF00FF00), // Green Color(0xFF0000FF), // Blue Color(0xFF4B0082), // Indigo Color(0xFF9400D3), // Violet ]; String _colorToHex(Color color) { return '#${color.value.toRadixString(16).padLeft(8, '0').substring(2).toUpperCase()}'; } Color _hexToColor(String hex) { try { String cleanHex = hex.replaceAll('#', '').replaceAll('0x', ''); if (cleanHex.length == 6) { cleanHex = 'FF$cleanHex'; } if (cleanHex.length == 8) { return Color(int.parse(cleanHex, radix: 16)); } return Colors.grey; } catch (_) { return Colors.grey; } } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () => _showAddMemberDialog(context), child: const Icon(Icons.add), ), body: FutureBuilder>( future: Provider.of(context).fetchFamilyMembers(), builder: (context, snapshot) { if (!snapshot.hasData) return const Center(child: CircularProgressIndicator()); final members = snapshot.data!; return ListView.builder( itemCount: members.length, itemBuilder: (context, index) { final member = members[index]; Color memberColor = _hexToColor(member.color); return ListTile( leading: CircleAvatar( backgroundColor: memberColor, child: member.iconUrl.isNotEmpty ? ClipOval( child: Image.network( member.iconUrl, width: 40, height: 40, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Text( member.name.isNotEmpty ? member.name[0].toUpperCase() : '?', style: const TextStyle(fontSize: 20), ), ), ) : Text( member.name.isNotEmpty ? member.name[0].toUpperCase() : '?', style: const TextStyle(fontSize: 20), ), ), title: Text(member.name), subtitle: Text('Order: ${member.order}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () => _showEditMemberDialog(context, member), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () async { await Provider.of( context, listen: false, ).deleteFamilyMember(member.id); setState(() {}); }, ), ], ), ); }, ); }, ), ); } void _showAddMemberDialog(BuildContext context) { final nameController = TextEditingController(); final orderController = TextEditingController(text: '1'); Color selectedColor = _palette[0]; Uint8List? selectedIconBytes; String? selectedIconName; bool isUploading = false; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: const Text('Add Family Member'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: nameController, decoration: const InputDecoration(labelText: 'Name'), ), const SizedBox(height: 16), const Text('Icon', style: TextStyle(color: Colors.grey)), const SizedBox(height: 8), Row( children: [ CircleAvatar( backgroundColor: selectedColor, child: selectedIconBytes != null ? ClipOval( child: Image.memory( selectedIconBytes!, width: 40, height: 40, fit: BoxFit.cover, ), ) : const Icon(Icons.person, color: Colors.white), ), const SizedBox(width: 16), ElevatedButton.icon( onPressed: () async { final result = await FilePicker.platform.pickFiles( withData: true, type: FileType.image, ); if (result != null && result.files.isNotEmpty) { setDialogState(() { selectedIconBytes = result.files.first.bytes; selectedIconName = result.files.first.name; }); } }, icon: const Icon(Icons.image), label: const Text('Pick Image'), ), ], ), if (selectedIconName != null) Padding( padding: const EdgeInsets.only(top: 8), child: Text( selectedIconName!, style: const TextStyle(fontSize: 12, color: Colors.grey), ), ), const SizedBox(height: 16), const Text('Color', style: TextStyle(color: Colors.grey)), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: _palette.map((color) { final isSelected = color.value == selectedColor.value; return GestureDetector( onTap: () { setDialogState(() { selectedColor = color; }); }, child: Container( width: 40, height: 40, decoration: BoxDecoration( color: color, shape: BoxShape.circle, border: isSelected ? Border.all(color: Colors.white, width: 3) : null, boxShadow: [ if (isSelected) const BoxShadow( color: Colors.black26, blurRadius: 4, spreadRadius: 2, ) ], ), child: isSelected ? const Icon(Icons.check, color: Colors.white) : null, ), ); }).toList(), ), const SizedBox(height: 16), TextField( controller: orderController, decoration: const InputDecoration(labelText: 'Order'), keyboardType: TextInputType.number, ), if (isUploading) const Padding( padding: EdgeInsets.only(top: 16), child: Center(child: CircularProgressIndicator()), ), ], ), ), actions: [ TextButton( onPressed: isUploading ? null : () => Navigator.pop(context), child: const Text('Cancel'), ), TextButton( onPressed: isUploading ? null : () async { if (nameController.text.isNotEmpty) { setDialogState(() => isUploading = true); String iconUrl = ''; if (selectedIconBytes != null && selectedIconName != null) { try { iconUrl = await Provider.of( context, listen: false, ).uploadFamilyIcon( bytes: selectedIconBytes!, filename: selectedIconName!, ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to upload icon: $e')), ); setDialogState(() => isUploading = false); return; } } await Provider.of( context, listen: false, ).createFamilyMember( FamilyMember( id: '', name: nameController.text, emoji: '', // Legacy field iconUrl: iconUrl, color: _colorToHex(selectedColor), order: int.tryParse(orderController.text) ?? 1, ), ); if (mounted) { Navigator.pop(context); setState(() {}); } } }, child: const Text('Add'), ), ], ), ), ); } void _showEditMemberDialog(BuildContext context, FamilyMember member) { final nameController = TextEditingController(text: member.name); final orderController = TextEditingController(text: member.order.toString()); Color selectedColor = _hexToColor(member.color); if (!_palette.any((c) => c.value == selectedColor.value)) { // If current color is not in palette, try to match closest or add it, // but for now let's just default to first palette color if invalid, // or keep it if we want to support custom legacy colors. // The requirement says "replace hex color input with rainbow palette selection". // Let's assume we map to the palette or keep custom if it exists. // But for better UX let's just keep it as is visually, but if they pick a new one it changes. } Uint8List? selectedIconBytes; String? selectedIconName; bool isUploading = false; ImageProvider? previewImage; if (member.iconUrl.isNotEmpty) { previewImage = NetworkImage(member.iconUrl); } showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: const Text('Edit Family Member'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: nameController, decoration: const InputDecoration(labelText: 'Name'), ), const SizedBox(height: 16), const Text('Icon', style: TextStyle(color: Colors.grey)), const SizedBox(height: 8), Row( children: [ CircleAvatar( backgroundColor: selectedColor, child: selectedIconBytes != null ? ClipOval( child: Image.memory( selectedIconBytes!, width: 40, height: 40, fit: BoxFit.cover, ), ) : (previewImage != null ? ClipOval( child: Image( image: previewImage!, width: 40, height: 40, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Text( member.name.isNotEmpty ? member.name[0] : '?', ), ), ) : Text(member.name.isNotEmpty ? member.name[0] : '?')), ), const SizedBox(width: 16), ElevatedButton.icon( onPressed: () async { final result = await FilePicker.platform.pickFiles( withData: true, type: FileType.image, ); if (result != null && result.files.isNotEmpty) { setDialogState(() { selectedIconBytes = result.files.first.bytes; selectedIconName = result.files.first.name; previewImage = selectedIconBytes == null ? previewImage : MemoryImage(selectedIconBytes!); }); } }, icon: const Icon(Icons.image), label: const Text('Change Icon'), ), ], ), const SizedBox(height: 16), const Text('Color', style: TextStyle(color: Colors.grey)), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: _palette.map((color) { final isSelected = color.value == selectedColor.value; return GestureDetector( onTap: () { setDialogState(() { selectedColor = color; }); }, child: Container( width: 40, height: 40, decoration: BoxDecoration( color: color, shape: BoxShape.circle, border: isSelected ? Border.all(color: Colors.white, width: 3) : null, boxShadow: [ if (isSelected) const BoxShadow( color: Colors.black26, blurRadius: 4, spreadRadius: 2, ) ], ), child: isSelected ? const Icon(Icons.check, color: Colors.white) : null, ), ); }).toList(), ), const SizedBox(height: 16), TextField( controller: orderController, decoration: const InputDecoration(labelText: 'Order'), keyboardType: TextInputType.number, ), if (isUploading) const Padding( padding: EdgeInsets.only(top: 16), child: Center(child: CircularProgressIndicator()), ), ], ), ), actions: [ TextButton( onPressed: isUploading ? null : () => Navigator.pop(context), child: const Text('Cancel'), ), TextButton( onPressed: isUploading ? null : () async { if (nameController.text.isNotEmpty) { setDialogState(() => isUploading = true); String iconUrl = member.iconUrl; if (selectedIconBytes != null && selectedIconName != null) { try { iconUrl = await Provider.of( context, listen: false, ).uploadFamilyIcon( bytes: selectedIconBytes!, filename: selectedIconName!, ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to upload icon: $e')), ); setDialogState(() => isUploading = false); return; } } await Provider.of( context, listen: false, ).updateFamilyMember( FamilyMember( id: member.id, name: nameController.text, emoji: member .emoji, // Keep existing emoji or clear it? Let's keep it to be safe iconUrl: iconUrl, color: _colorToHex(selectedColor), order: int.tryParse(orderController.text) ?? 1, ), ); if (mounted) { Navigator.pop(context); setState(() {}); } } }, child: const Text('Save'), ), ], ), ), ); } } class PhotoManagerTab extends StatefulWidget { const PhotoManagerTab({super.key}); @override State createState() => _PhotoManagerTabState(); } class _PhotoManagerTabState extends State { Widget _buildGooglePhotosHeader() { return FutureBuilder( future: Provider.of(context, listen: false).getGoogleStatus(), builder: (context, snapshot) { final isConnected = snapshot.data ?? false; return Container( padding: const EdgeInsets.all(16), margin: const EdgeInsets.all(8), decoration: BoxDecoration( color: isConnected ? Colors.green.withOpacity(0.1) : Colors.blue.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: isConnected ? Colors.green : Colors.blue, width: 1, ), ), child: Row( children: [ Icon( isConnected ? Icons.cloud_done : Icons.cloud_off, color: isConnected ? Colors.green : Colors.blue, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( isConnected ? 'Google Photos Connected' : 'Google Photos Not Connected', style: const TextStyle(fontWeight: FontWeight.bold), ), Text( isConnected ? 'Photos will be synced automatically.' : 'Connect to sync your Google Photos albums.', style: TextStyle(color: Colors.grey[600], fontSize: 12), ), ], ), ), ElevatedButton( onPressed: () async { if (isConnected) { await Provider.of(context, listen: false).disconnectGoogle(); setState(() {}); } else { try { final url = await Provider.of(context, listen: false).getGoogleAuthUrl(); final uri = Uri.parse(url); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to connect: $e')), ); } } }, style: ElevatedButton.styleFrom( backgroundColor: isConnected ? Colors.red.withOpacity(0.1) : null, foregroundColor: isConnected ? Colors.red : null, ), child: Text(isConnected ? 'Disconnect' : 'Connect'), ), ], ), ); }, ); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () => _showAddPhotoDialog(context), child: const Icon(Icons.add_a_photo), ), body: Column( children: [ _buildGooglePhotosHeader(), Expanded( child: FutureBuilder>( future: Provider.of(context).fetchPhotos(), builder: (context, snapshot) { if (!snapshot.hasData) return const Center(child: CircularProgressIndicator()); final photos = snapshot.data!; return GridView.builder( padding: const EdgeInsets.all(8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 8, mainAxisSpacing: 8, ), itemCount: photos.length, itemBuilder: (context, index) { final photo = photos[index]; return GridTile( footer: GridTileBar( backgroundColor: Colors.black54, title: Text(photo.caption), trailing: IconButton( icon: const Icon(Icons.delete, color: Colors.white), onPressed: () async { await Provider.of( context, listen: false, ).deletePhoto(photo.id); setState(() {}); }, ), ), child: photo.url.startsWith('http') ? Image.network( photo.url, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Center(child: Icon(Icons.broken_image)), ) : const Center(child: Icon(Icons.photo)), ); }, ); }, ), ), ], ), ); } void _showAddPhotoDialog(BuildContext context) { final urlController = TextEditingController(); final captionController = TextEditingController(); Uint8List? selectedFileBytes; String? selectedFileName; bool isUploading = false; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: const Text('Add Photo'), content: Column( mainAxisSize: MainAxisSize.min, children: [ ElevatedButton.icon( onPressed: () async { final result = await FilePicker.platform.pickFiles( withData: true, type: FileType.image, ); if (result != null && result.files.isNotEmpty) { setDialogState(() { selectedFileBytes = result.files.first.bytes; selectedFileName = result.files.first.name; // Clear URL if file is selected to avoid confusion urlController.clear(); }); } }, icon: const Icon(Icons.upload_file), label: const Text('Pick Local Image'), ), if (selectedFileName != null) Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( 'Selected: $selectedFileName', style: const TextStyle(fontWeight: FontWeight.bold), ), ), const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text('- OR -', style: TextStyle(color: Colors.grey)), ), TextField( controller: urlController, decoration: const InputDecoration(labelText: 'Image URL'), enabled: selectedFileBytes == null, ), TextField( controller: captionController, decoration: const InputDecoration(labelText: 'Caption'), ), if (isUploading) const Padding( padding: EdgeInsets.only(top: 16), child: CircularProgressIndicator(), ), ], ), actions: [ TextButton( onPressed: isUploading ? null : () => Navigator.pop(context), child: const Text('Cancel'), ), TextButton( onPressed: isUploading ? null : () async { if (selectedFileBytes != null || urlController.text.isNotEmpty) { setDialogState(() { isUploading = true; }); try { final photoService = Provider.of(context, listen: false); if (selectedFileBytes != null) { await photoService.uploadPhotoBytes( bytes: selectedFileBytes!, filename: selectedFileName!, caption: captionController.text, ); } else { await photoService.createPhoto( Photo( id: '', url: urlController.text, caption: captionController.text, active: true, ), ); } if (mounted) { Navigator.pop(context); setState(() {}); } } catch (e) { setDialogState(() { isUploading = false; }); // Optionally show error if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error: $e')), ); } } } }, child: const Text('Add'), ), ], ), ), ); } } class BibleVerseManagerTab extends StatefulWidget { const BibleVerseManagerTab({super.key}); @override State createState() => _BibleVerseManagerTabState(); } class _BibleVerseManagerTabState extends State { @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () => _showAddVerseDialog(context), child: const Icon(Icons.menu_book), ), body: FutureBuilder>( future: Provider.of(context).fetchVerses(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final verses = snapshot.data!; if (verses.isEmpty) { return const Center( child: Text( 'No verses added yet', style: TextStyle(color: Colors.grey), ), ); } return ListView.builder( itemCount: verses.length, itemBuilder: (context, index) { final verse = verses[index]; return ListTile( onTap: () => _showEditVerseDialog(context, verse), title: Text(verse.reference), subtitle: Text( verse.text, maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (verse.date != null && verse.date!.isNotEmpty) Text( verse.date!, style: const TextStyle(fontSize: 12, color: Colors.grey), ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () => _showEditVerseDialog(context, verse), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () async { await Provider.of( context, listen: false, ).deleteVerse(verse.id); setState(() {}); }, ), ], ), ); }, ); }, ), ); } void _showAddVerseDialog(BuildContext context) { _showVerseDialog(context, null); } void _showEditVerseDialog(BuildContext context, BibleVerse verse) { _showVerseDialog(context, verse); } void _showVerseDialog(BuildContext context, BibleVerse? verse) { final isEditing = verse != null; final textController = TextEditingController(text: verse?.text ?? ''); final referenceController = TextEditingController(text: verse?.reference ?? ''); final dateController = TextEditingController(text: verse?.date ?? ''); bool isActive = verse?.active ?? true; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: Text(isEditing ? 'Edit Bible Verse' : 'Add Bible Verse'), content: SizedBox( width: 600, // Increased width child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: referenceController, decoration: const InputDecoration( labelText: 'Reference (e.g., Psalms 23:1)', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), TextField( controller: textController, decoration: const InputDecoration( labelText: 'Verse Text (Korean)', hintText: 'Enter verse text here. Use \\n for line breaks.', border: OutlineInputBorder(), alignLabelWithHint: true, ), maxLines: 8, // Increased maxLines ), const SizedBox(height: 16), TextField( controller: dateController, readOnly: true, decoration: const InputDecoration( labelText: 'Date (YYYY-MM-DD) - Optional', hintText: 'Click to select date', border: OutlineInputBorder(), suffixIcon: Icon(Icons.calendar_today), ), onTap: () async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2101), ); if (picked != null) { setDialogState(() { dateController.text = picked.toIso8601String().split('T')[0]; }); } }, ), const SizedBox(height: 8), SwitchListTile( title: const Text('Active'), value: isActive, onChanged: (value) { setDialogState(() { isActive = value; }); }, ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), onPressed: () async { if (textController.text.isNotEmpty && referenceController.text.isNotEmpty) { final newVerse = BibleVerse( id: verse?.id ?? '', text: textController.text, reference: referenceController.text, date: dateController.text.isEmpty ? null : dateController.text, active: isActive, ); if (isEditing) { await Provider.of( context, listen: false, ).updateVerse(newVerse); } else { await Provider.of( context, listen: false, ).createVerse(newVerse); } if (mounted) { Navigator.pop(context); setState(() {}); } } }, child: Text(isEditing ? 'Save Verse' : 'Add Verse'), ), ], ), ), ); } } class AnnouncementManagerTab extends StatefulWidget { const AnnouncementManagerTab({super.key}); @override State createState() => _AnnouncementManagerTabState(); } class _AnnouncementManagerTabState extends State { @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () => _showAddAnnouncementDialog(context), child: const Icon(Icons.campaign), ), body: FutureBuilder>( future: Provider.of(context).fetchAnnouncements(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final announcements = snapshot.data!; if (announcements.isEmpty) { return const Center( child: Text( 'No announcements added yet', style: TextStyle(color: Colors.grey), ), ); } return ListView.builder( itemCount: announcements.length, itemBuilder: (context, index) { final announcement = announcements[index]; return ListTile( onTap: () => _showEditAnnouncementDialog(context, announcement), title: Text(announcement.title), subtitle: Text( announcement.content, maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (announcement.priority > 0) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), margin: const EdgeInsets.only(right: 8), decoration: BoxDecoration( color: Colors.orange, borderRadius: BorderRadius.circular(4), ), child: Text( 'P${announcement.priority}', style: const TextStyle( fontSize: 10, color: Colors.white, ), ), ), Icon( announcement.active ? Icons.check_circle : Icons.cancel, color: announcement.active ? Colors.green : Colors.grey, size: 16, ), IconButton( icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () => _showEditAnnouncementDialog(context, announcement), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () async { await Provider.of( context, listen: false, ).deleteAnnouncement(announcement.id); setState(() {}); }, ), ], ), ); }, ); }, ), ); } void _showAddAnnouncementDialog(BuildContext context) { _showAnnouncementDialog(context, null); } void _showEditAnnouncementDialog( BuildContext context, Announcement announcement) { _showAnnouncementDialog(context, announcement); } void _showAnnouncementDialog(BuildContext context, Announcement? announcement) { final isEditing = announcement != null; final titleController = TextEditingController(text: announcement?.title ?? ''); final contentController = TextEditingController(text: announcement?.content ?? ''); final priorityController = TextEditingController(text: announcement?.priority.toString() ?? '0'); bool isActive = announcement?.active ?? true; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: Text(isEditing ? 'Edit Announcement' : 'Add Announcement'), content: SizedBox( width: 600, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: titleController, decoration: const InputDecoration( labelText: 'Title', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), TextField( controller: contentController, decoration: const InputDecoration( labelText: 'Content', border: OutlineInputBorder(), ), maxLines: 3, ), const SizedBox(height: 16), TextField( controller: priorityController, decoration: const InputDecoration( labelText: 'Priority (0-10)', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, ), const SizedBox(height: 8), SwitchListTile( title: const Text('Active'), value: isActive, onChanged: (value) { setDialogState(() { isActive = value; }); }, ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), onPressed: () async { if (titleController.text.isNotEmpty) { final newAnnouncement = Announcement( id: announcement?.id ?? '', title: titleController.text, content: contentController.text, priority: int.tryParse(priorityController.text) ?? 0, active: isActive, ); if (isEditing) { await Provider.of( context, listen: false, ).updateAnnouncement(newAnnouncement); } else { await Provider.of( context, listen: false, ).createAnnouncement(newAnnouncement); } if (mounted) { Navigator.pop(context); setState(() {}); } } }, child: Text(isEditing ? 'Save Announcement' : 'Add Announcement'), ), ], ), ), ); } } class ScheduleManagerTab extends StatefulWidget { const ScheduleManagerTab({super.key}); @override State createState() => _ScheduleManagerTabState(); } class _ScheduleManagerTabState extends State { @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () => _showAddScheduleDialog(context), child: const Icon(Icons.calendar_today), ), body: FutureBuilder>( future: Provider.of(context).fetchSchedules(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final schedules = snapshot.data!; if (schedules.isEmpty) { return const Center( child: Text( 'No schedules added yet', style: TextStyle(color: Colors.grey), ), ); } return ListView.builder( itemCount: schedules.length, itemBuilder: (context, index) { final schedule = schedules[index]; return ListTile( onTap: () => _showEditScheduleDialog(context, schedule), title: Text(schedule.title), subtitle: Text( '${schedule.description}\n${schedule.startDate.toIso8601String().split('T')[0]} ~ ${schedule.endDate.toIso8601String().split('T')[0]}', maxLines: 2, overflow: TextOverflow.ellipsis, ), isThreeLine: true, trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () => _showEditScheduleDialog(context, schedule), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () async { await Provider.of( context, listen: false, ).deleteSchedule(schedule.id); setState(() {}); }, ), ], ), ); }, ); }, ), ); } void _showAddScheduleDialog(BuildContext context) { _showScheduleDialog(context, null); } void _showEditScheduleDialog(BuildContext context, ScheduleItem schedule) { _showScheduleDialog(context, schedule); } void _showScheduleDialog(BuildContext context, ScheduleItem? schedule) { final isEditing = schedule != null; final titleController = TextEditingController(text: schedule?.title ?? ''); final descriptionController = TextEditingController(text: schedule?.description ?? ''); final startController = TextEditingController( text: schedule?.startDate.toIso8601String().split('T')[0] ?? DateTime.now().toIso8601String().split('T')[0], ); final endController = TextEditingController( text: schedule?.endDate.toIso8601String().split('T')[0] ?? DateTime.now().toIso8601String().split('T')[0], ); const bool isAllDay = true; // Forcing all day as requested String? selectedFamilyMemberId = schedule?.familyMemberId; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: Text(isEditing ? 'Edit Schedule' : 'Add Schedule'), content: SizedBox( width: 600, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: titleController, decoration: const InputDecoration( labelText: 'Title', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), TextField( controller: descriptionController, decoration: const InputDecoration( labelText: 'Description', border: OutlineInputBorder(), ), maxLines: 2, ), const SizedBox(height: 16), TextField( controller: startController, readOnly: true, decoration: const InputDecoration( labelText: 'Start Date (YYYY-MM-DD)', suffixIcon: Icon(Icons.calendar_today), border: OutlineInputBorder(), ), onTap: () async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.tryParse(startController.text) ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2101), ); if (picked != null) { setDialogState(() { startController.text = picked.toIso8601String().split('T')[0]; }); } }, ), const SizedBox(height: 16), TextField( controller: endController, readOnly: true, decoration: const InputDecoration( labelText: 'End Date (YYYY-MM-DD)', suffixIcon: Icon(Icons.calendar_today), border: OutlineInputBorder(), ), onTap: () async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.tryParse(endController.text) ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2101), ); if (picked != null) { setDialogState(() { endController.text = picked.toIso8601String().split('T')[0]; }); } }, ), const SizedBox(height: 8), // All Day toggle removed as requested const SizedBox(height: 8), FutureBuilder>( future: Provider.of(context, listen: false) .fetchFamilyMembers(), builder: (context, snapshot) { if (!snapshot.hasData) return const SizedBox(); final members = snapshot.data!; return DropdownButtonFormField( value: selectedFamilyMemberId, decoration: const InputDecoration( labelText: 'Family Member', border: OutlineInputBorder(), ), items: members.map((member) { return DropdownMenuItem( value: member.id, child: Text(member.name), ); }).toList(), onChanged: (value) { setDialogState(() { selectedFamilyMemberId = value; }); }, ); }, ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: () async { if (titleController.text.isNotEmpty) { final startDate = DateTime.tryParse(startController.text) ?? DateTime.now(); final endDate = DateTime.tryParse(endController.text) ?? DateTime.now(); final newSchedule = ScheduleItem( id: schedule?.id ?? '', title: titleController.text, description: descriptionController.text, startDate: startDate, endDate: endDate, familyMemberId: selectedFamilyMemberId ?? '', isAllDay: isAllDay, ); if (isEditing) { await Provider.of( context, listen: false, ).updateSchedule(newSchedule); } else { await Provider.of( context, listen: false, ).createSchedule(newSchedule); } if (mounted) { Navigator.pop(context); setState(() {}); } } }, child: Text(isEditing ? 'Save Schedule' : 'Add Schedule'), ), ], ), ), ); } } class TodoManagerTab extends StatefulWidget { const TodoManagerTab({super.key}); @override State createState() => _TodoManagerTabState(); } class _TodoManagerTabState extends State { @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () => _showAddTodoDialog(context), child: const Icon(Icons.check_box), ), body: FutureBuilder>( future: Provider.of(context).fetchTodos(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); } final todos = snapshot.data!; if (todos.isEmpty) { return const Center( child: Text( 'No todos added yet', style: TextStyle(color: Colors.grey), ), ); } return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return ListTile( onTap: () => _showEditTodoDialog(context, todo), leading: Checkbox( value: todo.completed, onChanged: (val) async { await Provider.of( context, listen: false, ).updateTodo( TodoItem( id: todo.id, familyMemberId: todo.familyMemberId, title: todo.title, completed: val ?? false, dueDate: todo.dueDate, ), ); setState(() {}); }, ), title: Text( todo.title, style: TextStyle( decoration: todo.completed ? TextDecoration.lineThrough : null, ), ), subtitle: todo.dueDate != null ? Builder( builder: (context) { final dueDate = todo.dueDate!; return Text( dueDate.toIso8601String().split('T')[0], style: const TextStyle(fontSize: 12, color: Colors.grey), ); }, ) : null, trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () => _showEditTodoDialog(context, todo), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () async { await Provider.of( context, listen: false, ).deleteTodo(todo.id); setState(() {}); }, ), ], ), ); }, ); }, ), ); } void _showAddTodoDialog(BuildContext context) { _showTodoDialog(context, null); } void _showEditTodoDialog(BuildContext context, TodoItem todo) { _showTodoDialog(context, todo); } void _showTodoDialog(BuildContext context, TodoItem? todo) { final isEditing = todo != null; final titleController = TextEditingController(text: todo?.title ?? ''); final todoDueDate = todo?.dueDate; final dateController = TextEditingController( text: todoDueDate != null ? todoDueDate.toIso8601String().split('T')[0] : '', ); final timeController = TextEditingController( text: todoDueDate != null ? TimeOfDay.fromDateTime(todoDueDate).format(context) : '', ); String? selectedFamilyMemberId = todo?.familyMemberId; bool isCompleted = todo?.completed ?? false; TimeOfDay? selectedTime = todoDueDate != null ? TimeOfDay.fromDateTime(todoDueDate) : null; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( title: Text(isEditing ? 'Edit Todo' : 'Add Todo'), content: SizedBox( width: 600, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: titleController, decoration: const InputDecoration( labelText: 'Title', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), TextField( controller: dateController, readOnly: true, decoration: const InputDecoration( labelText: 'Due Date', hintText: 'Select date', suffixIcon: Icon(Icons.calendar_today), border: OutlineInputBorder(), ), onTap: () async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.tryParse(dateController.text) ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2101), ); if (picked != null) { setDialogState(() { dateController.text = picked.toIso8601String().split('T')[0]; }); } }, ), const SizedBox(height: 16), TextField( controller: timeController, readOnly: true, decoration: const InputDecoration( labelText: 'Time (Optional)', hintText: 'Select time', suffixIcon: Icon(Icons.access_time), border: OutlineInputBorder(), ), onTap: () async { final TimeOfDay? picked = await showTimePicker( context: context, initialTime: selectedTime ?? TimeOfDay.now(), ); if (picked != null) { setDialogState(() { selectedTime = picked; timeController.text = picked.format(context); }); } }, ), const SizedBox(height: 16), FutureBuilder>( future: Provider.of(context, listen: false) .fetchFamilyMembers(), builder: (context, snapshot) { if (!snapshot.hasData) return const SizedBox(); final members = snapshot.data!; return DropdownButtonFormField( value: selectedFamilyMemberId, decoration: const InputDecoration( labelText: 'Family Member', border: OutlineInputBorder(), ), items: members.map((member) { return DropdownMenuItem( value: member.id, child: Text(member.name), ); }).toList(), onChanged: (value) { setDialogState(() { selectedFamilyMemberId = value; }); }, ); }, ), if (isEditing) SwitchListTile( title: const Text('Completed'), value: isCompleted, onChanged: (value) { setDialogState(() { isCompleted = value; }); }, ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: () async { if (titleController.text.isNotEmpty && selectedFamilyMemberId != null) { DateTime? fullDate; if (dateController.text.isNotEmpty) { final baseDate = DateTime.tryParse(dateController.text); if (baseDate != null) { fullDate = baseDate; if (selectedTime != null) { fullDate = DateTime( fullDate.year, fullDate.month, fullDate.day, selectedTime!.hour, selectedTime!.minute, ); } } } final newItem = TodoItem( id: todo?.id ?? '', familyMemberId: selectedFamilyMemberId!, title: titleController.text, completed: isCompleted, dueDate: fullDate, ); if (isEditing) { await Provider.of( context, listen: false, ).updateTodo(newItem); } else { await Provider.of( context, listen: false, ).createTodo(newItem); } if (mounted) { Navigator.pop(context); setState(() {}); } } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Title and Family Member are required')), ); } }, child: Text(isEditing ? 'Save Todo' : 'Add Todo'), ), ], ), ), ); } }