import 'package:flutter/material.dart'; /// Reusable scaffold for entity pages (create, view, edit). /// /// Provides consistent layout across all Control Room entity pages: /// - AppBar with back button, title, and customizable actions /// - Loading, error, and content states /// - Consistent padding and styling /// /// Usage: /// ```dart /// EntityPageScaffold( /// title: 'Proxy Host Details', /// onBack: () => Navigator.pop(context), /// actions: [ /// IconButton(icon: Icon(Icons.edit), onPressed: onEdit), /// ], /// child: MyContent(), /// ) /// ``` class EntityPageScaffold extends StatelessWidget { const EntityPageScaffold({ super.key, required this.title, required this.child, this.onBack, this.actions, this.floatingActionButton, }); final String title; final Widget child; final VoidCallback? onBack; final List? actions; final Widget? floatingActionButton; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: onBack != null ? IconButton( icon: const Icon(Icons.arrow_back), onPressed: onBack, ) : null, title: Text(title), actions: actions, ), body: child, floatingActionButton: floatingActionButton, ); } } /// Async content wrapper with loading, error, and data states. /// /// Use with Riverpod AsyncValue for consistent loading/error handling. class EntityAsyncContent extends StatelessWidget { const EntityAsyncContent({ super.key, required this.isLoading, required this.error, required this.data, required this.onRetry, required this.builder, }); final bool isLoading; final Object? error; final T? data; final VoidCallback onRetry; final Widget Function(T data) builder; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; if (isLoading) { return const Center(child: CircularProgressIndicator()); } if (error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline, size: 48, color: colorScheme.error), const SizedBox(height: 16), const Text('Failed to load'), const SizedBox(height: 8), Text( error.toString(), style: TextStyle(color: colorScheme.outline), textAlign: TextAlign.center, ), const SizedBox(height: 16), FilledButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh), label: const Text('Retry'), ), ], ), ); } if (data != null) { return builder(data as T); } return const SizedBox.shrink(); } } /// Section header for entity detail pages. /// /// Consistent styling for grouping related fields. class EntitySection extends StatelessWidget { const EntitySection({ super.key, required this.title, required this.icon, required this.child, this.trailing, }); final String title; final IconData icon; final Widget child; final Widget? trailing; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, size: 20, color: colorScheme.primary), const SizedBox(width: 8), Expanded( child: Text( title, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: colorScheme.primary, fontWeight: FontWeight.w600, ), ), ), if (trailing != null) trailing!, ], ), const SizedBox(height: 8), child, ], ); } } /// Row displaying a boolean setting with label and indicator. class EntitySettingRow extends StatelessWidget { const EntitySettingRow({ super.key, required this.label, required this.value, this.subtitle, }); final String label; final bool value; final String? subtitle; @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label), if (subtitle != null) Text( subtitle!, style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.outline, ), ), ], ), ), Icon( value ? Icons.check_circle : Icons.cancel, color: value ? Colors.green : Colors.grey, size: 20, ), ], ); } } /// Row displaying a key-value pair. class EntityInfoRow extends StatelessWidget { const EntityInfoRow({ super.key, required this.label, required this.value, this.monospace = false, }); final String label; final String value; final bool monospace; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 120, child: Text( label, style: TextStyle(color: colorScheme.outline), ), ), Expanded( child: Text( value, style: monospace ? const TextStyle(fontFamily: 'monospace') : null, ), ), ], ); } } /// Enum for entity page modes. enum EntityPageMode { create, view, edit, } /// Mixin for pages that support view/edit mode toggle. /// /// Provides standard mode management for entity detail pages. mixin EntityPageModeMixin on State { EntityPageMode _mode = EntityPageMode.view; EntityPageMode get mode => _mode; set mode(EntityPageMode value) => _mode = value; bool get isViewing => _mode == EntityPageMode.view; bool get isEditing => _mode == EntityPageMode.edit; bool get isCreating => _mode == EntityPageMode.create; void setMode(EntityPageMode newMode) { setState(() => _mode = newMode); } void startEditing() => setMode(EntityPageMode.edit); void stopEditing() => setMode(EntityPageMode.view); }