import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tatlock_ui/features/front_hall/data/models/system_stats_model.dart'; import 'package:tatlock_ui/features/front_hall/presentation/providers/system_stats_provider.dart'; import 'package:tatlock_ui/shared/theme/stoplight_colors.dart'; import 'package:tatlock_ui/shared/widgets/widgets.dart'; import 'package:tatlock_ui/version.g.dart'; /// Dashboard content shown in Front Hall when mode is dashboard. /// /// Displays system stats with gauges, weather, air quality, and version info. /// Auto-refreshes system stats every 30 seconds. class DashboardContent extends ConsumerStatefulWidget { const DashboardContent({super.key}); @override ConsumerState createState() => _DashboardContentState(); } class _DashboardContentState extends ConsumerState { Timer? _refreshTimer; @override void initState() { super.initState(); _refreshTimer = Timer.periodic( const Duration(seconds: 30), (_) => ref.invalidate(systemStatsProvider), ); } @override void dispose() { _refreshTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final systemStatsAsync = ref.watch(systemStatsProvider); return ListView( padding: const EdgeInsets.all(16), children: [ // Welcome card Card( child: Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.waving_hand, size: 32, color: colorScheme.primary, ), const SizedBox(width: 12), Flexible( child: Text( 'Welcome to Tatlock', style: Theme.of(context).textTheme.headlineSmall, ), ), ], ), const SizedBox(height: 12), Text( 'Your homelab dashboard is ready.', style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), ), ), const SizedBox(height: 16), // System Stats - Gauges _SectionHeader(title: 'System Stats', icon: Icons.monitor_heart), const SizedBox(height: 8), systemStatsAsync.when( data: (stats) => _SystemStatsCard(stats: stats), loading: () => const Card( child: Padding( padding: EdgeInsets.all(32), child: Center(child: CircularProgressIndicator()), ), ), error: (error, _) => Card( child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon(Icons.error_outline, color: colorScheme.error), const SizedBox(width: 12), Expanded( child: Text( 'Failed to load system stats', style: TextStyle(color: colorScheme.error), ), ), IconButton( icon: const Icon(Icons.refresh), onPressed: () => ref.invalidate(systemStatsProvider), ), ], ), ), ), ), const SizedBox(height: 24), // Environment - Weather & Air Quality _SectionHeader(title: 'Environment', icon: Icons.eco), const SizedBox(height: 8), LayoutBuilder( builder: (context, constraints) { // Responsive layout: side-by-side on wider screens if (constraints.maxWidth > 500) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: WeatherWidget()), const SizedBox(width: 12), Expanded(child: AirQualityWidget()), ], ); } // Stack on narrow screens return Column( children: [ WeatherWidget(), const SizedBox(height: 12), AirQualityWidget(), ], ); }, ), const SizedBox(height: 24), // Version info Center( child: Text( '${AppVersion.name} v${AppVersion.fullVersion}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colorScheme.outline, ), ), ), ], ); } } /// Section header with icon and title. class _SectionHeader extends StatelessWidget { const _SectionHeader({ required this.title, required this.icon, }); final String title; final IconData icon; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Row( children: [ Icon( icon, size: 18, color: colorScheme.onSurfaceVariant, ), const SizedBox(width: 8), Text( title, style: Theme.of(context).textTheme.titleSmall?.copyWith( color: colorScheme.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), ], ); } } /// Card displaying system stats with gauges. class _SystemStatsCard extends StatelessWidget { const _SystemStatsCard({required this.stats}); final SystemStats stats; @override Widget build(BuildContext context) { // Build gauges list: CPU, Memory, GPU (if available), then all disks final gauges = [ GaugeData( value: stats.cpu.usagePercent / 100, label: 'CPU', icon: Icons.memory, color: StoplightColors.forPercent(stats.cpu.usagePercent), ), GaugeData( value: stats.memory.usagePercent / 100, label: 'RAM', icon: Icons.storage, color: StoplightColors.forPercent(stats.memory.usagePercent), ), if (stats.gpu.available && stats.gpu.usagePercent != null) GaugeData( value: stats.gpu.usagePercent! / 100, label: 'VRAM', icon: Icons.videocam, color: StoplightColors.forPercent(stats.gpu.usagePercent!), ), // Add a gauge for each disk ...stats.disks.map( (disk) => GaugeData( value: disk.usagePercent / 100, label: _formatDiskLabel(disk), icon: Icons.disc_full, color: StoplightColors.forPercent(disk.usagePercent), ), ), ]; return Card( child: Padding( padding: const EdgeInsets.all(24), child: GaugeRow( gaugeSize: 120, gauges: gauges, ), ), ); } /// Formats disk label from mount point. String _formatDiskLabel(DiskStats disk) { final mount = disk.mountPoint; if (mount == '/') return 'Root'; if (mount == '/hostfs') return 'Host'; if (mount.startsWith('/hostfs/')) return mount.substring(8); if (mount.startsWith('/mnt/')) return mount.substring(5); if (mount.startsWith('/media/')) return mount.substring(7); // Return last path segment final parts = mount.split('/'); return parts.isNotEmpty ? parts.last : mount; } }