/// Renders the Read State View widget for the FocusFlow Flutter UI. library; import 'package:flutter/material.dart'; import '../controllers/scheduler_read_controller.dart'; /// Renders loading, empty, failure, and data states from a read controller. class ReadStateView extends StatelessWidget { /// Creates a read-state view. const ReadStateView({ required this.controller, required this.title, required this.emptyTitle, required this.emptyMessage, required this.dataBuilder, super.key, }); /// Controller that owns the current read state. final UiReadController controller; /// Name of the surface being loaded, used in failure copy. final String title; /// Title shown when the read succeeds with empty content. final String emptyTitle; /// Message shown when the read succeeds with empty content. final String emptyMessage; /// Builds the populated content for a read value. final Widget Function(BuildContext context, T value) dataBuilder; @override Widget build(BuildContext context) { return AnimatedBuilder( animation: controller, builder: (context, _) { return switch (controller.state) { SchedulerReadLoading() => const Center( child: CircularProgressIndicator(), ), SchedulerReadData(value: final value) => dataBuilder( context, value, ), SchedulerReadEmpty(value: final value) => EmptyStatePanel( title: emptyTitle, message: emptyMessage, child: dataBuilder(context, value), ), SchedulerReadFailure(failure: final failure, error: final error) => FailureStatePanel( title: 'Could not load $title', code: failure?.detailCode ?? failure?.code.name ?? 'unexpected', technicalDetail: error?.toString(), onRetry: controller.retry, ), }; }, ); } } /// Standard empty-state panel with optional surrounding content. class EmptyStatePanel extends StatelessWidget { /// Creates an empty-state panel. const EmptyStatePanel({ required this.title, required this.message, this.child, super.key, }); /// Primary empty-state title. final String title; /// Supporting empty-state message. final String message; /// Optional content shown alongside the empty-state panel. final Widget? child; @override Widget build(BuildContext context) { final panel = Padding( padding: const EdgeInsets.all(16), child: Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 8), Text(message), ], ), ), ), ); final content = child; if (content == null) { return Center(child: panel); } return Column( children: [ Expanded(child: content), panel, ], ); } } /// Standard failure panel with a retry action. class FailureStatePanel extends StatelessWidget { /// Creates a failure-state panel. const FailureStatePanel({ required this.title, required this.code, required this.onRetry, this.technicalDetail, super.key, }); /// Primary failure title. final String title; /// Displayable failure code. final String code; /// Optional technical detail for unexpected errors. final String? technicalDetail; /// Callback invoked when the user retries the read. final Future Function() onRetry; @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(16), child: Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 8), Text('Status: $code'), if (technicalDetail != null) ...[ const SizedBox(height: 8), Text(technicalDetail!), ], const SizedBox(height: 12), FilledButton.icon( onPressed: () async { await onRetry(); }, icon: const Icon(Icons.refresh), label: const Text('Retry'), ), ], ), ), ), ), ); } }