import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../providers/realtime_controller.dart'; import '../providers/sync_queue_provider.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(); } final pendingCount = ref.watch(pendingSyncCountProvider); // Build a human-readable label for recovering channels. final channels = ctrl.recoveringChannels; final channelLabel = channels.length <= 2 ? channels.map(_humanize).join(', ') : '${channels.length} channels'; final syncSuffix = pendingCount > 0 ? ' — syncing $pendingCount item${pendingCount == 1 ? '' : 's'}' : ''; final label = 'Reconnecting $channelLabel$syncSuffix…'; 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( 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('_', ' '); } }