235 lines
6.3 KiB
Dart
235 lines
6.3 KiB
Dart
import "package:go_router/go_router.dart";
|
|
import "package:provider/provider.dart";
|
|
import "package:shadcn_flutter/shadcn_flutter.dart";
|
|
|
|
import "../../../src/project_store.dart";
|
|
import "../../providers/chat_provider.dart";
|
|
import "../../providers/home_coordinator.dart";
|
|
import "../../providers/projects_provider.dart";
|
|
import "../../widgets/agents/agents_pane.dart";
|
|
import "../../widgets/chat/chat_box.dart";
|
|
import "../../widgets/chat/chat_view.dart";
|
|
import "../../widgets/common/footer_bar.dart";
|
|
import "../../widgets/sidebar/sidebar.dart";
|
|
|
|
class NewHomeScreen extends StatefulWidget {
|
|
const NewHomeScreen({super.key});
|
|
|
|
@override
|
|
State<NewHomeScreen> createState() => _NewHomeScreenState();
|
|
}
|
|
|
|
class _NewHomeScreenState extends State<NewHomeScreen> {
|
|
|
|
final ScrollController _chatScrollController = ScrollController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
context.read<HomeCoordinator>().addListener(_onCoordinatorChanged);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
context.read<HomeCoordinator>().removeListener(_onCoordinatorChanged);
|
|
_chatScrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onCoordinatorChanged() {
|
|
final coordinator = context.read<HomeCoordinator>();
|
|
final err = coordinator.error;
|
|
if (err != null) {
|
|
coordinator.clearError();
|
|
_showError(err);
|
|
}
|
|
}
|
|
|
|
Future<void> _showError(String message) {
|
|
return showDialog<void>(
|
|
context: context,
|
|
builder: (dialogContext) => AlertDialog(
|
|
title: const Text("Heads up"),
|
|
content: Text(message),
|
|
actions: [
|
|
Button.primary(
|
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
|
child: const Text("OK"),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
child: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Row(
|
|
children: [
|
|
|
|
Sidebar(),
|
|
|
|
Gap(1),
|
|
|
|
VerticalDivider(),
|
|
|
|
Expanded(
|
|
child: Stack(
|
|
children: [
|
|
|
|
_ChatArea(scrollController: _chatScrollController),
|
|
|
|
Positioned(
|
|
top: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
width: 12,
|
|
child: FullHeightScrollbar(controller: _chatScrollController),
|
|
),
|
|
|
|
],
|
|
),
|
|
),
|
|
|
|
AgentsPane(),
|
|
|
|
],
|
|
),
|
|
),
|
|
|
|
FooterBar(),
|
|
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class _ChatArea extends StatelessWidget {
|
|
|
|
final ScrollController scrollController;
|
|
|
|
const _ChatArea({required this.scrollController});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final chatProvider = context.watch<ChatProvider>();
|
|
|
|
return Container(
|
|
alignment: Alignment.center,
|
|
padding: const EdgeInsets.all(16),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 600),
|
|
child: Column(
|
|
children: [
|
|
|
|
Expanded(
|
|
child: chatProvider.messages.isEmpty
|
|
? _EmptyChatState()
|
|
: ChatView(scrollController: scrollController),
|
|
),
|
|
|
|
ChatBox(),
|
|
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class _EmptyChatState extends StatelessWidget {
|
|
|
|
const _EmptyChatState();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final projectsProvider = context.watch<ProjectsProvider>();
|
|
final projects = projectsProvider.projects;
|
|
final selected = projectsProvider.selectedProject;
|
|
final coordinator = context.read<HomeCoordinator>();
|
|
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
|
|
const Icon(LucideIcons.messagesSquare, size: 28),
|
|
const Gap(16),
|
|
Text(
|
|
"Ask the agency anything",
|
|
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w700),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const Gap(8),
|
|
Text(
|
|
"Select a project and thread from the sidebar, or start a new chat.",
|
|
textAlign: TextAlign.center,
|
|
).textSmall.muted,
|
|
|
|
const Gap(24),
|
|
|
|
Select<ProjectRecord>(
|
|
itemBuilder: (context, item) => Text(item.name),
|
|
popup: SelectPopup.builder(
|
|
searchPlaceholder: const Text("Search projects"),
|
|
builder: (context, searchQuery) {
|
|
final filtered = searchQuery == null || searchQuery.isEmpty
|
|
? projects
|
|
: projects.where((p) =>
|
|
p.name.toLowerCase().contains(searchQuery.toLowerCase()) ||
|
|
p.workingDirectory.toLowerCase().contains(searchQuery.toLowerCase())
|
|
).toList();
|
|
|
|
return SelectItemList(
|
|
children: [
|
|
for (final project in filtered)
|
|
SelectItemButton(
|
|
value: project,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(project.name),
|
|
Text(project.workingDirectory).textSmall.muted,
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
onChanged: (project) {
|
|
if (project != null) coordinator.selectProject(project);
|
|
},
|
|
constraints: const BoxConstraints(minWidth: 240),
|
|
value: selected,
|
|
placeholder: const Text("Select a project"),
|
|
),
|
|
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
abstract class HomeScreenRoute {
|
|
static const path = '/';
|
|
static const name = 'home';
|
|
|
|
static GoRoute get route => GoRoute(
|
|
path: path,
|
|
name: name,
|
|
builder: (context, state) => const NewHomeScreen(),
|
|
);
|
|
}
|