Files
base_flutter/lib/features/todos/presentation/screens/home_screen.dart
Phuoc Nguyen 38a33743e6 fix todo
2025-10-03 17:54:39 +07:00

328 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/todo_providers.dart';
class TodoScreen extends ConsumerStatefulWidget {
const TodoScreen({super.key});
@override
ConsumerState<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends ConsumerState<TodoScreen> {
String _searchQuery = '';
final TextEditingController _searchController = TextEditingController();
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
Future<void> _refreshTodos() async {
await ref.read(todosProvider.notifier).refresh();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final todosAsync = ref.watch(todosProvider);
final filteredTodos = ref.watch(filteredTodosProvider(_searchQuery));
return Scaffold(
appBar: AppBar(
title: const Text('My Todos'),
elevation: 0,
backgroundColor: colorScheme.surfaceVariant,
foregroundColor: colorScheme.onSurfaceVariant,
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () {
// Handle logout - will be connected to auth logic later
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Logout functionality will be implemented'),
),
);
},
),
],
),
body: Column(
children: [
// Search Bar
Container(
padding: const EdgeInsets.all(16.0),
color: colorScheme.surfaceVariant,
child: TextField(
controller: _searchController,
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
decoration: InputDecoration(
hintText: 'Search todos...',
prefixIcon: const Icon(Icons.search),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
setState(() {
_searchQuery = '';
});
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
filled: true,
fillColor: colorScheme.surface,
),
),
),
// Todos List
Expanded(
child: todosAsync.when(
data: (todos) {
if (todos.isEmpty) {
return _buildEmptyState();
}
if (filteredTodos.isEmpty) {
return _buildNoResultsState();
}
return RefreshIndicator(
onRefresh: _refreshTodos,
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: filteredTodos.length,
itemBuilder: (context, index) {
final todo = filteredTodos[index];
return _buildTodoCard(todo);
},
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: colorScheme.error,
),
const SizedBox(height: 16),
Text(
'Error loading todos',
style: theme.textTheme.headlineSmall?.copyWith(
color: colorScheme.error,
),
),
const SizedBox(height: 8),
Text(
error.toString(),
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _refreshTodos,
icon: const Icon(Icons.refresh),
label: const Text('Retry'),
),
],
),
),
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Handle add new todo
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Add todo functionality will be implemented'),
),
);
},
icon: const Icon(Icons.add),
label: const Text('Add Todo'),
),
);
}
Widget _buildTodoCard(todo) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final isCompleted = todo.completed;
return Card(
margin: const EdgeInsets.only(bottom: 8.0),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
leading: Checkbox(
value: isCompleted,
onChanged: (_) => ref.read(todosProvider.notifier).toggleTodo(todo.id),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
title: Text(
todo.title,
style: theme.textTheme.bodyLarge?.copyWith(
decoration: isCompleted ? TextDecoration.lineThrough : null,
color: isCompleted
? colorScheme.onSurfaceVariant
: colorScheme.onSurface,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (todo.description != null && todo.description!.isNotEmpty)
Text(
todo.description!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'ID: ${todo.id}',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant.withOpacity(0.7),
fontSize: 11,
),
),
],
),
trailing: PopupMenuButton<String>(
onSelected: (value) {
switch (value) {
case 'edit':
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Edit functionality will be implemented'),
),
);
break;
case 'delete':
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Delete functionality will be implemented'),
),
);
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'edit',
child: Row(
children: [
Icon(Icons.edit),
SizedBox(width: 8),
Text('Edit'),
],
),
),
const PopupMenuItem(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete),
SizedBox(width: 8),
Text('Delete'),
],
),
),
],
),
),
);
}
Widget _buildEmptyState() {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.task_outlined,
size: 64,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No todos yet',
style: theme.textTheme.headlineSmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Add your first todo to get started!',
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
);
}
Widget _buildNoResultsState() {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off,
size: 64,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'No todos found',
style: theme.textTheme.headlineSmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Try adjusting your search terms',
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
);
}
}