import 'package:flutter/cupertino.dart'; import 'package:please_pay_me/theme/tokens.dart'; /// Pulsing placeholder block used while content loads. class AppSkeleton extends StatefulWidget { const AppSkeleton({ super.key, this.width, this.height = 16, this.radius = AppRadii.sm, }); const AppSkeleton.circle({super.key, required double size}) : width = size, height = size, radius = AppRadii.capsule; final double? width; final double height; final double radius; @override State createState() => _AppSkeletonState(); } class _AppSkeletonState extends State with SingleTickerProviderStateMixin { late final AnimationController _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 1100), )..repeat(reverse: true); @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return FadeTransition( opacity: Tween(begin: 0.45, end: 1).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ), child: Container( width: widget.width, height: widget.height, decoration: BoxDecoration( color: AppColors.of(context, AppColors.systemGray5), borderRadius: BorderRadius.circular(widget.radius), ), ), ); } } /// Skeleton shaped like an [AppListTile] row. class AppSkeletonRow extends StatelessWidget { const AppSkeletonRow({super.key, this.hasLeading = true}); final bool hasLeading; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.gutter, vertical: AppSpacing.s3, ), child: Row( children: [ if (hasLeading) ...[ const AppSkeleton.circle(size: 29), const SizedBox(width: AppSpacing.s3), ], const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AppSkeleton(width: 140, height: 15), SizedBox(height: AppSpacing.s2), AppSkeleton(width: 90, height: 12), ], ), ), const AppSkeleton(width: 56, height: 15), ], ), ); } }