import 'package:flutter/material.dart'; import '../data/hub.dart'; import '../theme/tokens.dart'; import '../widgets/widgets.dart'; class ModulesPage extends StatefulWidget { const ModulesPage({super.key}); @override State createState() => _ModulesPageState(); } class _ModulesPageState extends State { late Future> _future; @override void initState() { super.initState(); _future = HubService.instance.listModules(); } void _refresh() => setState(() { _future = HubService.instance.listModules(); }); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( title: const Text('Modules'), actions: [ IconButton( icon: const Icon(Icons.refresh, size: 18), tooltip: 'Reload', onPressed: _refresh, ), const SizedBox(width: FaiSpace.sm), ], ), body: FutureBuilder>( future: _future, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return FaiEmptyState( icon: Icons.cloud_off_outlined, iconColor: Theme.of(context).colorScheme.error, title: 'Hub unreachable', hint: 'Start the hub with `fai serve`. Studio will retry automatically.', action: FilledButton.tonal( onPressed: _refresh, child: const Text('Retry'), ), ); } final modules = snapshot.data ?? []; if (modules.isEmpty) { return const FaiEmptyState( icon: Icons.extension_outlined, title: 'No modules yet', hint: 'Run `fai install ` or check ~/.fai/modules/.', ); } return ListView.separated( padding: const EdgeInsets.all(FaiSpace.xl), itemCount: modules.length, separatorBuilder: (_, _) => const SizedBox(height: FaiSpace.md), itemBuilder: (context, i) => _ModuleCard(module: modules[i]), ); }, ), ); } } class _ModuleCard extends StatelessWidget { final ModuleSummary module; const _ModuleCard({required this.module}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return FaiCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( module.name, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(width: FaiSpace.sm), FaiPill( label: 'v${module.version}', tone: FaiPillTone.neutral, monospace: true, ), ], ), const SizedBox(height: FaiSpace.md), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 110, child: Text( 'Capabilities', style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, letterSpacing: 0.4, ), ), ), Expanded( child: Wrap( spacing: FaiSpace.xs, runSpacing: FaiSpace.xs, children: module.capabilities .map( (c) => FaiPill( label: c, tone: FaiPillTone.accent, monospace: true, ), ) .toList(), ), ), ], ), ], ), ); } }