import 'package:flutter/material.dart'; import '../../core/theme/app_colors.dart'; class FeaturePage extends StatelessWidget { final String title; final String subtitle; final Widget child; final Widget? trailing; const FeaturePage({ super.key, required this.title, required this.subtitle, required this.child, this.trailing, }); @override Widget build(BuildContext context) { return SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(16, 14, 16, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TweenAnimationBuilder( tween: Tween(begin: 12, end: 0), duration: const Duration(milliseconds: 360), curve: Curves.easeOutCubic, builder: (_, offset, child) => Opacity( opacity: (1 - offset / 12).clamp(0.0, 1.0), child: Transform.translate( offset: Offset(0, offset), child: child, ), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: Theme.of(context) .textTheme .headlineSmall ?.copyWith( fontWeight: FontWeight.w900, color: AppColors.text, ), ), const SizedBox(height: 2), Text( subtitle, style: const TextStyle( color: AppColors.muted, fontWeight: FontWeight.w500, ), ), ], ), ), if (trailing != null) trailing!, ], ), ), const SizedBox(height: 16), Expanded(child: child), ], ), ), ); } } class FeatureEmptyPanel extends StatelessWidget { final IconData icon; final String title; final String message; final Widget? action; const FeatureEmptyPanel({ super.key, required this.icon, required this.title, required this.message, this.action, }); @override Widget build(BuildContext context) { return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 360), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 72, height: 72, decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8), border: Border.all(color: AppColors.border), ), child: Icon(icon, size: 36, color: AppColors.primary), ), const SizedBox(height: 12), Text( title, textAlign: TextAlign.center, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w800), ), const SizedBox(height: 6), Text( message, textAlign: TextAlign.center, style: const TextStyle(color: AppColors.muted, height: 1.35), ), if (action != null) ...[ const SizedBox(height: 16), action!, ], ], ), ), ); } } class FeatureErrorPanel extends StatelessWidget { final String message; final VoidCallback? onRetry; const FeatureErrorPanel({ super.key, required this.message, this.onRetry, }); @override Widget build(BuildContext context) { return Center( child: Container( width: double.infinity, constraints: const BoxConstraints(maxWidth: 420), padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: const Color(0xFFFEF2F2), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFFECACA)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, color: Color(0xFFDC2626), size: 34), const SizedBox(height: 10), Text( message, textAlign: TextAlign.center, style: const TextStyle(color: Color(0xFF991B1B), height: 1.35), ), if (onRetry != null) ...[ const SizedBox(height: 12), FilledButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh), label: const Text('Coba lagi'), ), ], ], ), ), ); } }