import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/realtime_controller.dart'; /// Subtle, non-blocking connection status indicator. /// Shows in the bottom-right corner when streams are recovering/stale. /// Displays which channels are reconnecting so the user knows what to expect. class ReconnectIndicator extends ConsumerWidget { const ReconnectIndicator({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final ctrl = ref.watch(realtimeControllerProvider); // Hide when not recovering if (!ctrl.isAnyStreamRecovering) { return const SizedBox.shrink(); } // Build a human-readable label for recovering channels. final channels = ctrl.recoveringChannels; final label = channels.length <= 2 ? channels.map(_humanize).join(', ') : '${channels.length} channels'; return Positioned( bottom: 16, right: 16, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, border: Border.all( color: Theme.of(context).colorScheme.outline, width: 1, ), borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(3), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 12, height: 12, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.primary, ), ), ), const SizedBox(width: 8), Flexible( child: Text( 'Reconnecting $label…', style: Theme.of(context).textTheme.labelSmall, overflow: TextOverflow.ellipsis, ), ), ], ), ), ); } /// Converts a channel name like 'task_assignments' to 'task assignments'. static String _humanize(String channel) { // Strip instance suffixes like 'ticket_messages:abc123' final base = channel.contains(':') ? channel.split(':').first : channel; return base.replaceAll('_', ' '); } }