The-Agency/lib/ui/widgets/sidebar/sidebar.dart

187 lines
5.6 KiB
Dart

import "package:provider/provider.dart";
import "package:shadcn_flutter/shadcn_flutter.dart";
import '../../../src/session/session_types.dart';
import '../../providers/chat_provider.dart';
import '../../providers/home_coordinator.dart';
import '../../providers/projects_provider.dart';
import '../../providers/session_provider.dart';
import 'app_logo.dart';
import 'project_section.dart';
import 'thread_button.dart';
class Sidebar extends StatelessWidget {
const Sidebar({super.key});
@override
Widget build(BuildContext context) {
final projectsProvider = context.watch<ProjectsProvider>();
final sessionProvider = context.watch<SessionProvider>();
final chatProvider = context.watch<ChatProvider>();
final coordinator = context.read<HomeCoordinator>();
return Container(
width: 300,
color: Theme.of(context).colorScheme.input.scaleAlpha(0.3),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 100,
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 16),
child: AppLogo(),
),
Divider(),
Gap(16),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: _fixedSection(context, coordinator, chatProvider),
),
Gap(16),
Divider(),
Gap(16),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8),
child: _projectsSection(context, projectsProvider, sessionProvider, coordinator, chatProvider),
),
),
],
),
);
}
Widget _fixedSection(BuildContext context, HomeCoordinator coordinator, ChatProvider chatProvider) {
return Column(
children: [
SizedBox(
width: double.infinity,
child: Button.ghost(
style: ButtonStyle.ghost().copyWith(
padding: (context, state, edgeInsets) {
return EdgeInsets.only(
top: 8,
left: 8,
bottom: 8,
right: 10
);
}
),
onPressed: chatProvider.isLoading ? null : coordinator.createNewChat,
disableFocusOutline: true,
leading: Icon(LucideIcons.squarePen).iconSmall,
alignment: Alignment.centerLeft,
child: Text("New Chat"),
),
),
SizedBox(
width: double.infinity,
child: Button.ghost(
style: ButtonStyle.ghost().copyWith(
padding: (context, state, edgeInsets) {
return EdgeInsets.only(
top: 8,
left: 8,
bottom: 8,
right: 10
);
}
),
onPressed: coordinator.pickProjectDirectory,
disableFocusOutline: true,
leading: Icon(LucideIcons.folderPlus).iconSmall,
alignment: Alignment.centerLeft,
child: Text("New Project"),
),
),
],
);
}
Widget _projectsSection(BuildContext context, ProjectsProvider projectsProvider, SessionProvider sessionProvider, HomeCoordinator coordinator, ChatProvider chatProvider) {
if (projectsProvider.projects.isEmpty) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Text("No projects yet").textSmall.muted,
);
}
// group sessions by working directory
final sessionsByProject = <String, List<SessionSummary>>{};
for (final session in sessionProvider.sessions) {
final dir = session.workingDirectory ?? '';
sessionsByProject.putIfAbsent(dir, () => []).add(session);
}
// sort sessions within each project newest first
final sorted = <String, List<SessionSummary>>{};
sessionsByProject.forEach((dir, sessions) {
sorted[dir] = List<SessionSummary>.from(sessions)
..sort((a, b) => b.updated.compareTo(a.updated));
});
final projects = List.of(projectsProvider.projects)
..sort((a, b) {
final aLatest = sorted[a.workingDirectory]?.firstOrNull?.updated;
final bLatest = sorted[b.workingDirectory]?.firstOrNull?.updated;
if (aLatest == null && bLatest == null) return 0;
if (aLatest == null) return 1;
if (bLatest == null) return -1;
return bLatest.compareTo(aLatest);
});
return ListView(
padding: EdgeInsets.zero,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text("Projects").textMuted,
),
Gap(8),
for (final project in projects) ...[
ProjectSection(
projectLabel: project.name,
children: [
if (sorted[project.workingDirectory]?.isEmpty ?? true)
ThreadButton(
label: "No threads yet",
muted: true,
)
else
for (final session in sorted[project.workingDirectory]!)
ThreadButton(
label: session.name,
lastMessage: session.updated,
selected: sessionProvider.currentSessionId == session.id,
isRunning: chatProvider.isSessionRunning(session.id),
onPressed: () => coordinator.openSession(session),
onDelete: () => coordinator.deleteSession(session),
),
],
),
Gap(2),
],
],
);
}
}