// HubLoadErrorView — the one way a page presents "my content // could not be loaded". Pages used to open-code a "hub not // reachable" empty state for ANY failure, contradicting the // sidebar's green connected dot whenever the hub answered but the // RPC failed (an older hub without the RPC, a feature gate, a real // bug). This widget routes the failure through the shared // classification instead: // // * unreachable -> the honest "hub not reachable" state // * unsupported -> "this view needs a newer hub version" // * other -> "could not load" + the friendly error in a // copyable detail box (hard rule: errors are // always copyable) // // The state-matrix suite pins the behaviour across every page. import 'package:flutter/material.dart'; import '../data/friendly_error.dart'; import '../l10n/app_localizations.dart'; import 'chain_empty_state.dart'; import 'chain_error_box.dart'; class HubLoadErrorView extends StatelessWidget { final Object error; /// Re-runs the page's load. Rendered on the unreachable and /// load-failed states when provided. final VoidCallback? onRetry; const HubLoadErrorView({super.key, required this.error, this.onRetry}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final l = AppLocalizations.of(context)!; final retry = onRetry == null ? null : FilledButton.tonal( onPressed: onRetry, child: Text(l.buttonRetry), ); switch (classifyHubLoadError(error)) { case HubLoadIssue.unreachable: return ChainEmptyState( icon: Icons.cloud_off_outlined, iconColor: theme.colorScheme.error, title: l.hubUnreachable, hint: l.hubUnreachableHint, action: retry, ); case HubLoadIssue.unsupported: return ChainEmptyState( icon: Icons.system_update_alt_outlined, title: l.hubViewTooOldTitle, hint: l.hubViewTooOldHint, action: retry, ); case HubLoadIssue.other: return ChainEmptyState( icon: Icons.error_outline, iconColor: theme.colorScheme.error, title: l.hubViewLoadFailedTitle, action: Column( mainAxisSize: MainAxisSize.min, children: [ ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: ChainErrorBox(error: error, isError: true), ), if (retry != null) ...[ const SizedBox(height: 12), retry, ], ], ), ); } } }