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: LayoutBuilder( builder: (context, constraints) { final short = constraints.maxHeight < 520; final compact = constraints.maxWidth < 420 || short; final wide = constraints.maxWidth >= 900; final horizontal = compact ? 12.0 : 20.0; return Padding( padding: EdgeInsets.fromLTRB( horizontal, short ? 8 : 12, horizontal, short ? 10 : 14, ), child: Center( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: wide ? 1160 : double.infinity, ), 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: compact ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _FeatureHeading( title: title, subtitle: subtitle, compact: compact, ), if (trailing != null) ...[ const SizedBox(height: 10), Align( alignment: Alignment.centerLeft, child: trailing!, ), ], ], ) : Row( children: [ Expanded( child: _FeatureHeading( title: title, subtitle: subtitle, compact: compact, ), ), if (trailing != null) trailing!, ], ), ), SizedBox(height: short ? 8 : (compact ? 12 : 16)), Expanded( child: child, ), ], ), ), ), ); }, ), ); } } class _FeatureHeading extends StatelessWidget { final String title; final String subtitle; final bool compact; const _FeatureHeading({ required this.title, required this.subtitle, required this.compact, }); @override Widget build(BuildContext context) { final short = MediaQuery.sizeOf(context).height < 520; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, maxLines: short ? 1 : 2, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontSize: compact ? 22 : null, fontWeight: FontWeight.w900, color: AppColors.text, ), ), const SizedBox(height: 2), Text( subtitle, maxLines: short ? 1 : (compact ? 2 : 3), overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.muted, fontWeight: FontWeight.w500, height: 1.25, ), ), ], ); } } 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'), ), ], ], ), ), ); } }