Files
2026-09-21 04:06:43 +03:00

91 lines
2.3 KiB
Dart

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<AppSkeleton> createState() => _AppSkeletonState();
}
class _AppSkeletonState extends State<AppSkeleton> 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<double>(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),
],
),
);
}
}