Files
multi-agent-mux/.agents/skills/multi-agent-mux-ui/apps/mam_desktop/lib/main.dart
T

157 lines
4.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mam_core/mam_core.dart';
import 'src/providers/session_providers.dart';
import 'src/theme/app_theme.dart';
import 'src/widgets/detail_pane.dart';
import 'src/widgets/session_table.dart';
import 'src/widgets/stale_banner.dart';
void main() {
runApp(const ProviderScope(child: MamDesktopApp()));
}
class MamDesktopApp extends StatelessWidget {
const MamDesktopApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'multi-agent-mux',
debugShowCheckedModeBanner: false,
theme: buildAppTheme(),
home: const DashboardScreen(),
);
}
}
class DashboardScreen extends ConsumerWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final pollAsync = ref.watch(sessionsPollProvider);
return Scaffold(
body: DecoratedBox(
decoration: const BoxDecoration(gradient: backgroundGradient),
child: SafeArea(
child: pollAsync.when(
data: (poll) => _DashboardBody(poll: poll),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stackTrace) => _ErrorScreen(error: error),
),
),
),
);
}
}
class _ErrorScreen extends StatelessWidget {
final Object error;
const _ErrorScreen({required this.error});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, color: AppColors.danger, size: 40),
const SizedBox(height: 12),
Text(
'Failed to start status polling:\n$error',
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.danger),
),
],
),
),
);
}
}
class _DashboardBody extends ConsumerWidget {
final SessionsPoll poll;
const _DashboardBody({required this.poll});
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedName = ref.watch(selectedSessionNameProvider);
final sessions = poll.snapshot?.sessions ?? const <SessionRow>[];
final matches = sessions.where((s) => s.name == selectedName);
final selectedRow = matches.isEmpty ? null : matches.first;
return Column(
children: [
_TopBar(poll: poll),
StaleBanner(poll: poll),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(16),
child: SessionTable(
sessions: sessions,
selectedName: selectedName,
onSelect: (name) =>
ref.read(selectedSessionNameProvider.notifier).state =
name,
),
),
),
SizedBox(width: 380, child: DetailPane(session: selectedRow)),
],
),
),
],
);
}
}
class _TopBar extends StatelessWidget {
final SessionsPoll poll;
const _TopBar({required this.poll});
@override
Widget build(BuildContext context) {
final count = poll.snapshot?.sessions.length ?? 0;
return Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
child: Row(
children: [
const Text(
'multi-agent-mux',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.accent.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(999),
),
child: Text(
'$count session${count == 1 ? '' : 's'}',
style: const TextStyle(
color: AppColors.accent2,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
}