import 'package:flutter/material.dart'; enum SkeletonType { feed, reels, explore, messages, profile, generic } class SkeletonScreen extends StatefulWidget { final SkeletonType skeletonType; const SkeletonScreen({super.key, this.skeletonType = SkeletonType.generic}); @override State createState() => _SkeletonScreenState(); } class _SkeletonScreenState extends State with SingleTickerProviderStateMixin { late AnimationController _shimmerController; late Animation _shimmerAnimation; @override void initState() { super.initState(); _shimmerController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, )..repeat(); _shimmerAnimation = Tween( begin: -1.0, end: 2.0, ).animate(_shimmerController); } @override void dispose() { _shimmerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final baseColor = theme.colorScheme.surfaceContainerHighest.withValues( alpha: theme.brightness == Brightness.dark ? 0.25 : 0.4, ); final highlightColor = theme.colorScheme.onSurface.withValues(alpha: 0.08); return Container( color: theme.scaffoldBackgroundColor, width: double.infinity, height: double.infinity, child: AnimatedBuilder( animation: _shimmerAnimation, builder: (context, child) { return ShaderMask( shaderCallback: (rect) { return LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [baseColor, highlightColor, baseColor], stops: const [0.1, 0.3, 0.6], transform: _SlidingGradientTransform( slidePercent: _shimmerAnimation.value, ), ).createShader(rect); }, blendMode: BlendMode.srcATop, child: _buildSkeletonContent(context), ); }, ), ); } Widget _buildSkeletonContent(BuildContext context) { switch (widget.skeletonType) { case SkeletonType.feed: return _buildFeedSkeleton(context); case SkeletonType.reels: return _buildReelsSkeleton(context); case SkeletonType.explore: return _buildExploreSkeleton(context); case SkeletonType.messages: return _buildMessagesSkeleton(context); case SkeletonType.profile: return _buildProfileSkeleton(context); case SkeletonType.generic: return _buildGenericSkeleton(context); } } Widget _buildFeedSkeleton(BuildContext context) { final width = MediaQuery.of(context).size.width; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 80, padding: const EdgeInsets.symmetric(vertical: 8), child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: 6, itemBuilder: (context, index) => Padding( padding: const EdgeInsets.only(right: 12), child: Column( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(28), ), ), const SizedBox(height: 4), Container( width: 32, height: 8, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ), ), ), Expanded( child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 0), itemCount: 3, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), ), const SizedBox(width: 8), Container( width: 80, height: 12, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ), Container( width: double.infinity, height: width, color: Colors.white, ), Padding( padding: const EdgeInsets.all(12), child: Row( children: List.generate( 3, (i) => Padding( padding: const EdgeInsets.only(right: 16), child: Container( width: 24, height: 24, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), ), ), ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Container( width: width * 0.7, height: 10, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ), ], ), ); }, ), ), ], ); } Widget _buildReelsSkeleton(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 120, height: 200, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), ), const SizedBox(height: 24), Container( width: 150, height: 16, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 12), Container( width: 100, height: 12, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ); } Widget _buildExploreSkeleton(BuildContext context) { return Padding( padding: const EdgeInsets.all(2), child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 2, mainAxisSpacing: 2, ), itemCount: 15, itemBuilder: (context, index) { return Container(color: Colors.white); }, ), ); } Widget _buildMessagesSkeleton(BuildContext context) { return ListView.builder( itemCount: 8, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(28), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 100, height: 14, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 6), Container( width: 150, height: 12, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ), ], ), ); }, ); } Widget _buildProfileSkeleton(BuildContext context) { return SingleChildScrollView( child: Column( children: [ const SizedBox(height: 24), Container( width: 96, height: 96, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(48), ), ), const SizedBox(height: 16), Container( width: 120, height: 16, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate( 3, (index) => Column( children: [ Container( width: 40, height: 20, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 4), Container( width: 50, height: 10, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ), ), const SizedBox(height: 24), GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 2, mainAxisSpacing: 2, ), itemCount: 9, itemBuilder: (context, index) { return Container(color: Colors.white); }, ), ], ), ); } Widget _buildGenericSkeleton(BuildContext context) { final width = MediaQuery.of(context).size.width; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: List.generate( 6, (index) => Padding( padding: const EdgeInsets.only(right: 12), child: Column( children: [ Container( width: 60, height: 60, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(30), ), ), const SizedBox(height: 8), Container( width: 40, height: 8, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ), ), ), ), const SizedBox(height: 16), Expanded( child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: 3, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.only(bottom: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), ), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: width * 0.4, height: 10, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 6), Container( width: width * 0.25, height: 8, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ], ), const SizedBox(height: 12), Container( width: double.infinity, height: width * 1.1, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), ), ), const SizedBox(height: 12), Container( width: width * 0.7, height: 10, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 6), Container( width: width * 0.5, height: 8, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), ), ), ], ), ); }, ), ), ], ); } } class _SlidingGradientTransform extends GradientTransform { final double slidePercent; const _SlidingGradientTransform({required this.slidePercent}); @override Matrix4 transform(Rect bounds, {TextDirection? textDirection}) { return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0); } } SkeletonType getSkeletonTypeFromUrl(String url) { final parsed = Uri.tryParse(url); if (parsed == null) return SkeletonType.generic; final path = parsed.path.toLowerCase(); if (path.startsWith('/reels') || path.startsWith('/reel/')) { return SkeletonType.reels; } else if (path.startsWith('/explore')) { return SkeletonType.explore; } else if (path.startsWith('/messages') || path.startsWith('/inbox')) { return SkeletonType.messages; } else if (path.startsWith('/') && !path.startsWith('/accounts')) { if (path.split('/').length <= 2) { return SkeletonType.feed; } return SkeletonType.profile; } return SkeletonType.generic; }