Add new features and update configurations for improved functionality
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
import "package:shadcn_flutter/shadcn_flutter.dart";
|
||||
|
||||
import "../../../../src/project_store.dart";
|
||||
import "../../../../src/session/session_types.dart";
|
||||
import "../../../providers/projects_provider.dart";
|
||||
import "../../../providers/session_provider.dart";
|
||||
|
||||
class ThreadsSection extends StatelessWidget {
|
||||
const ThreadsSection({
|
||||
required this.projectsProvider,
|
||||
required this.sessionProvider,
|
||||
required this.sessionsByProject,
|
||||
required this.onOpenSession,
|
||||
required this.onSelectProject,
|
||||
required this.onDeleteSession,
|
||||
});
|
||||
|
||||
final ProjectsProvider projectsProvider;
|
||||
final SessionProvider sessionProvider;
|
||||
final Map<String, List<SessionSummary>> sessionsByProject;
|
||||
final ValueChanged<SessionSummary> onOpenSession;
|
||||
final ValueChanged<ProjectRecord> onSelectProject;
|
||||
final ValueChanged<SessionSummary> onDeleteSession;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Sort sessions by update time (newest first) within each project
|
||||
final sortedSessionsByProject = <String, List<SessionSummary>>{};
|
||||
sessionsByProject.forEach((workingDirectory, sessions) {
|
||||
final sortedSessions = List<SessionSummary>.from(sessions)
|
||||
..sort((a, b) => b.updated.compareTo(a.updated));
|
||||
sortedSessionsByProject[workingDirectory] = sortedSessions;
|
||||
});
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 12),
|
||||
children: [
|
||||
if (projectsProvider.projects.isEmpty)
|
||||
const _SidebarHint(text: "No projects yet")
|
||||
else
|
||||
for (final project in projectsProvider.projects) ...[
|
||||
// Project header
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button.ghost(
|
||||
onPressed: () => onSelectProject(project),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Text(
|
||||
project.name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.mutedForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Project sessions
|
||||
if (sortedSessionsByProject[project.workingDirectory]?.isEmpty ??
|
||||
true)
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 4, 8, 8),
|
||||
child: _SidebarHint(text: "No threads yet"),
|
||||
)
|
||||
else
|
||||
for (final session
|
||||
in sortedSessionsByProject[project.workingDirectory]!)
|
||||
_SidebarSessionTile(
|
||||
session: session,
|
||||
isSelected: sessionProvider.currentSessionId == session.id,
|
||||
onTap: () => onOpenSession(session),
|
||||
onDelete: () => onDeleteSession(session),
|
||||
),
|
||||
const Divider(height: 16),
|
||||
],
|
||||
// Handle sessions that don't belong to any current project
|
||||
if (sortedSessionsByProject.keys.any(
|
||||
(key) => !projectsProvider.projects.any(
|
||||
(project) => project.workingDirectory == key,
|
||||
),
|
||||
)) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 8, 8, 4),
|
||||
child: Text(
|
||||
"Sessions Without Projects",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.mutedForeground,
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final entry in sortedSessionsByProject.entries)
|
||||
if (!projectsProvider.projects.any(
|
||||
(project) => project.workingDirectory == entry.key,
|
||||
) &&
|
||||
entry.key.isNotEmpty)
|
||||
for (final session in entry.value)
|
||||
_SidebarSessionTile(
|
||||
session: session,
|
||||
isSelected: sessionProvider.currentSessionId == session.id,
|
||||
onTap: () => onOpenSession(session),
|
||||
onDelete: () => onDeleteSession(session),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SidebarHint extends StatelessWidget {
|
||||
const _SidebarHint({required this.text});
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
child: Text(text).textSmall.muted,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SidebarSessionTile extends StatelessWidget {
|
||||
const _SidebarSessionTile({
|
||||
required this.session,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
required this.onDelete,
|
||||
});
|
||||
|
||||
final SessionSummary session;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback onDelete;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ContextMenu(
|
||||
items: [
|
||||
MenuButton(
|
||||
onPressed: (context) {
|
||||
onDelete();
|
||||
},
|
||||
child: const Text("Delete"),
|
||||
),
|
||||
],
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Button(
|
||||
style: isSelected ? ButtonStyle.secondary() : ButtonStyle.ghost(),
|
||||
child: Text(
|
||||
session.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w400, fontSize: 13),
|
||||
).textSmall,
|
||||
trailing: Text(
|
||||
_formatRelativeTime(session.updated),
|
||||
style: TextStyle(fontWeight: FontWeight.w400, fontSize: 13),
|
||||
).muted.textSmall,
|
||||
onPressed: () {
|
||||
onTap();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _formatRelativeTime(DateTime timestamp) {
|
||||
final difference = DateTime.now().toUtc().difference(timestamp.toUtc());
|
||||
|
||||
if (difference.inMinutes < 1) {
|
||||
return "just now";
|
||||
}
|
||||
if (difference.inHours < 1) {
|
||||
return "${difference.inMinutes}m";
|
||||
}
|
||||
if (difference.inDays < 1) {
|
||||
return "${difference.inHours}h";
|
||||
}
|
||||
if (difference.inDays < 7) {
|
||||
return "${difference.inDays}d";
|
||||
}
|
||||
|
||||
final month = timestamp.month.toString().padLeft(2, "0");
|
||||
final day = timestamp.day.toString().padLeft(2, "0");
|
||||
return "${timestamp.year}-$month-$day";
|
||||
}
|
||||
Reference in New Issue
Block a user