feat(knowledge): add ProjectsScreen and ProjectEditScreen, sort projects by updated_at

This commit is contained in:
2026-04-04 23:56:18 -04:00
parent a50193dbc0
commit e39d31fe43
5 changed files with 269 additions and 5 deletions
+89
View File
@@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../data/models/project.dart';
import '../../providers/projects_provider.dart';
class ProjectsScreen extends ConsumerWidget {
const ProjectsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final projectsAsync = ref.watch(projectsProvider);
return Scaffold(
appBar: AppBar(title: const Text('Projects')),
body: projectsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('Error: $e')),
data: (projects) => projects.isEmpty
? const Center(child: Text('No projects yet.'))
: RefreshIndicator(
onRefresh: () async => ref.invalidate(projectsProvider),
child: ListView.separated(
itemCount: projects.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (_, i) => _ProjectCard(project: projects[i]),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.push('/projects/new'),
tooltip: 'New project',
child: const Icon(Icons.add),
),
);
}
}
class _ProjectCard extends StatelessWidget {
final Project project;
const _ProjectCard({required this.project});
Color _statusColor(BuildContext context) => switch (project.status) {
'completed' => Colors.blue,
'archived' => Colors.grey,
_ => Theme.of(context).colorScheme.primary,
};
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(project.title),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (project.description?.isNotEmpty == true)
Text(
project.description!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (project.goal?.isNotEmpty == true)
Text(
project.goal!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(fontStyle: FontStyle.italic),
),
],
),
trailing: Chip(
label: Text(
project.status,
style: TextStyle(
fontSize: 11,
color: _statusColor(context),
),
),
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onTap: () => context.push('/projects/${project.id}/tasks'),
);
}
}