432 lines
14 KiB
Dart
432 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:intl/intl.dart';
|
|
import '../../models/todo_item.dart';
|
|
import '../../models/schedule_item.dart';
|
|
import '../../models/announcement.dart';
|
|
import '../../models/family_member.dart';
|
|
import '../../services/todo_service.dart';
|
|
import '../../services/schedule_service.dart';
|
|
import '../../services/announcement_service.dart';
|
|
import '../../services/family_service.dart';
|
|
|
|
class MobileHomeScreen extends StatefulWidget {
|
|
const MobileHomeScreen({super.key});
|
|
|
|
@override
|
|
State<MobileHomeScreen> createState() => _MobileHomeScreenState();
|
|
}
|
|
|
|
class _MobileHomeScreenState extends State<MobileHomeScreen> {
|
|
int _currentIndex = 0;
|
|
|
|
final List<Widget> _screens = [
|
|
const MobileTodoScreen(),
|
|
const MobileScheduleScreen(),
|
|
const MobileAnnouncementScreen(),
|
|
];
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Bini Family Manager'),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.settings),
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, '/admin');
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: _screens[_currentIndex],
|
|
bottomNavigationBar: BottomNavigationBar(
|
|
currentIndex: _currentIndex,
|
|
onTap: (index) {
|
|
setState(() {
|
|
_currentIndex = index;
|
|
});
|
|
},
|
|
items: const [
|
|
BottomNavigationBarItem(icon: Icon(Icons.check_box), label: 'Todos'),
|
|
BottomNavigationBarItem(
|
|
icon: Icon(Icons.calendar_today),
|
|
label: 'Schedule',
|
|
),
|
|
BottomNavigationBarItem(icon: Icon(Icons.campaign), label: 'Notices'),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MobileTodoScreen extends StatefulWidget {
|
|
const MobileTodoScreen({super.key});
|
|
|
|
@override
|
|
State<MobileTodoScreen> createState() => _MobileTodoScreenState();
|
|
}
|
|
|
|
class _MobileTodoScreenState extends State<MobileTodoScreen> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () => _showAddTodoDialog(context),
|
|
child: const Icon(Icons.add),
|
|
),
|
|
body: FutureBuilder<List<TodoItem>>(
|
|
future: Provider.of<TodoService>(
|
|
context,
|
|
).fetchTodos(), // Fetch all or today? Let's fetch all for manager
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData)
|
|
return const Center(child: CircularProgressIndicator());
|
|
final todos = snapshot.data!;
|
|
return ListView.builder(
|
|
itemCount: todos.length,
|
|
itemBuilder: (context, index) {
|
|
final todo = todos[index];
|
|
return ListTile(
|
|
title: Text(todo.title),
|
|
subtitle: Text(
|
|
todo.dueDate != null
|
|
? DateFormat('MM/dd').format(todo.dueDate!)
|
|
: 'No date',
|
|
),
|
|
trailing: Checkbox(
|
|
value: todo.completed,
|
|
onChanged: (val) async {
|
|
await Provider.of<TodoService>(
|
|
context,
|
|
listen: false,
|
|
).updateTodo(
|
|
TodoItem(
|
|
id: todo.id,
|
|
familyMemberId: todo.familyMemberId,
|
|
title: todo.title,
|
|
completed: val ?? false,
|
|
dueDate: todo.dueDate,
|
|
),
|
|
);
|
|
setState(() {});
|
|
},
|
|
),
|
|
onLongPress: () async {
|
|
await Provider.of<TodoService>(
|
|
context,
|
|
listen: false,
|
|
).deleteTodo(todo.id);
|
|
setState(() {});
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showAddTodoDialog(BuildContext context) async {
|
|
final titleController = TextEditingController();
|
|
final familyMembers = await Provider.of<FamilyService>(
|
|
context,
|
|
listen: false,
|
|
).fetchFamilyMembers();
|
|
String? selectedMemberId =
|
|
familyMembers.isNotEmpty ? familyMembers.first.id : null;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Add Todo'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextField(
|
|
controller: titleController,
|
|
decoration: const InputDecoration(labelText: 'Task'),
|
|
),
|
|
DropdownButtonFormField<String>(
|
|
value: selectedMemberId,
|
|
items: familyMembers
|
|
.map(
|
|
(m) => DropdownMenuItem(value: m.id, child: Text(m.name)),
|
|
)
|
|
.toList(),
|
|
onChanged: (val) => selectedMemberId = val,
|
|
decoration: const InputDecoration(labelText: 'Assign to'),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Cancel'),
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
if (selectedMemberId != null && titleController.text.isNotEmpty) {
|
|
await Provider.of<TodoService>(
|
|
context,
|
|
listen: false,
|
|
).createTodo(
|
|
TodoItem(
|
|
id: '',
|
|
familyMemberId: selectedMemberId!,
|
|
title: titleController.text,
|
|
completed: false,
|
|
dueDate: DateTime.now(),
|
|
),
|
|
);
|
|
if (mounted) {
|
|
Navigator.pop(context);
|
|
setState(() {});
|
|
}
|
|
}
|
|
},
|
|
child: const Text('Add'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MobileScheduleScreen extends StatefulWidget {
|
|
const MobileScheduleScreen({super.key});
|
|
|
|
@override
|
|
State<MobileScheduleScreen> createState() => _MobileScheduleScreenState();
|
|
}
|
|
|
|
class _MobileScheduleScreenState extends State<MobileScheduleScreen> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () => _showAddScheduleDialog(context),
|
|
child: const Icon(Icons.add),
|
|
),
|
|
body: FutureBuilder<List<ScheduleItem>>(
|
|
future: Provider.of<ScheduleService>(context).fetchSchedules(),
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData)
|
|
return const Center(child: CircularProgressIndicator());
|
|
final schedules = snapshot.data!;
|
|
return ListView.builder(
|
|
itemCount: schedules.length,
|
|
itemBuilder: (context, index) {
|
|
final item = schedules[index];
|
|
return ListTile(
|
|
title: Text(item.title),
|
|
subtitle: Text(
|
|
'${DateFormat('MM/dd HH:mm').format(item.startDate)} - ${item.description}',
|
|
),
|
|
onLongPress: () async {
|
|
await Provider.of<ScheduleService>(
|
|
context,
|
|
listen: false,
|
|
).deleteSchedule(item.id);
|
|
setState(() {});
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showAddScheduleDialog(BuildContext context) async {
|
|
final titleController = TextEditingController();
|
|
final descController = TextEditingController();
|
|
final familyMembers = await Provider.of<FamilyService>(
|
|
context,
|
|
listen: false,
|
|
).fetchFamilyMembers();
|
|
String? selectedMemberId =
|
|
familyMembers.isNotEmpty ? familyMembers.first.id : null;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Add Schedule'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextField(
|
|
controller: titleController,
|
|
decoration: const InputDecoration(labelText: 'Title'),
|
|
),
|
|
TextField(
|
|
controller: descController,
|
|
decoration: const InputDecoration(labelText: 'Description'),
|
|
),
|
|
DropdownButtonFormField<String>(
|
|
value: selectedMemberId,
|
|
items: familyMembers
|
|
.map(
|
|
(m) => DropdownMenuItem(value: m.id, child: Text(m.name)),
|
|
)
|
|
.toList(),
|
|
onChanged: (val) => selectedMemberId = val,
|
|
decoration: const InputDecoration(labelText: 'For whom?'),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Cancel'),
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
if (selectedMemberId != null && titleController.text.isNotEmpty) {
|
|
await Provider.of<ScheduleService>(
|
|
context,
|
|
listen: false,
|
|
).createSchedule(
|
|
ScheduleItem(
|
|
id: '',
|
|
title: titleController.text,
|
|
description: descController.text,
|
|
startDate: DateTime.now(),
|
|
endDate: DateTime.now().add(const Duration(hours: 1)),
|
|
familyMemberId: selectedMemberId!,
|
|
isAllDay: false,
|
|
),
|
|
);
|
|
if (mounted) {
|
|
Navigator.pop(context);
|
|
setState(() {});
|
|
}
|
|
}
|
|
},
|
|
child: const Text('Add'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MobileAnnouncementScreen extends StatefulWidget {
|
|
const MobileAnnouncementScreen({super.key});
|
|
|
|
@override
|
|
State<MobileAnnouncementScreen> createState() =>
|
|
_MobileAnnouncementScreenState();
|
|
}
|
|
|
|
class _MobileAnnouncementScreenState extends State<MobileAnnouncementScreen> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () => _showAddAnnouncementDialog(context),
|
|
child: const Icon(Icons.add),
|
|
),
|
|
body: FutureBuilder<List<Announcement>>(
|
|
future: Provider.of<AnnouncementService>(context).fetchAnnouncements(),
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData)
|
|
return const Center(child: CircularProgressIndicator());
|
|
final items = snapshot.data!;
|
|
return ListView.builder(
|
|
itemCount: items.length,
|
|
itemBuilder: (context, index) {
|
|
final item = items[index];
|
|
return ListTile(
|
|
title: Text(item.title),
|
|
subtitle: Text(item.content),
|
|
trailing: Switch(
|
|
value: item.active,
|
|
onChanged: (val) async {
|
|
await Provider.of<AnnouncementService>(
|
|
context,
|
|
listen: false,
|
|
).updateAnnouncement(
|
|
Announcement(
|
|
id: item.id,
|
|
title: item.title,
|
|
content: item.content,
|
|
priority: item.priority,
|
|
active: val,
|
|
),
|
|
);
|
|
setState(() {});
|
|
},
|
|
),
|
|
onLongPress: () async {
|
|
await Provider.of<AnnouncementService>(
|
|
context,
|
|
listen: false,
|
|
).deleteAnnouncement(item.id);
|
|
setState(() {});
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showAddAnnouncementDialog(BuildContext context) {
|
|
final titleController = TextEditingController();
|
|
final contentController = TextEditingController();
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Add Announcement'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextField(
|
|
controller: titleController,
|
|
decoration: const InputDecoration(labelText: 'Title'),
|
|
),
|
|
TextField(
|
|
controller: contentController,
|
|
decoration: const InputDecoration(labelText: 'Content'),
|
|
maxLines: 3,
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Cancel'),
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
if (titleController.text.isNotEmpty) {
|
|
await Provider.of<AnnouncementService>(
|
|
context,
|
|
listen: false,
|
|
).createAnnouncement(
|
|
Announcement(
|
|
id: '',
|
|
title: titleController.text,
|
|
content: contentController.text,
|
|
priority: 1,
|
|
active: true,
|
|
),
|
|
);
|
|
if (mounted) {
|
|
Navigator.pop(context);
|
|
setState(() {});
|
|
}
|
|
}
|
|
},
|
|
child: const Text('Add'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|