5385 explore tv static shimmer for loading (#5554)

* glitch static loading prototypes

* feat: shimmer pulse for clickable widget

---------

Co-authored-by: ggurdin <ggurdin@gmail.com>
This commit is contained in:
avashilling 2026-02-09 10:03:05 -05:00 committed by GitHub
parent 27e5387804
commit e83f76b95b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 76 deletions

View file

@ -1,55 +1,126 @@
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:fluffychat/config/app_config.dart';
class ShimmerBackground extends StatelessWidget {
class ShimmerBackground extends StatefulWidget {
final Widget child;
final Color shimmerColor;
final Color? baseColor;
final bool enabled;
final BorderRadius? borderRadius;
final Duration delayBetweenPulses;
const ShimmerBackground({
super.key,
required this.child,
this.shimmerColor = AppConfig.goldLight,
this.baseColor,
this.enabled = true,
this.borderRadius,
this.delayBetweenPulses = Duration.zero,
});
@override
State<ShimmerBackground> createState() => _ShimmerBackgroundState();
}
class _ShimmerBackgroundState extends State<ShimmerBackground>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
Duration pulseDuration = const Duration(milliseconds: 1000);
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: pulseDuration,
vsync: this,
);
_animation = Tween<double>(
begin: 0.0,
end: 0.3,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
if (widget.enabled) {
_startPulsing();
}
}
void _startPulsing() {
if (widget.delayBetweenPulses == Duration.zero) {
_controller.repeat(reverse: true);
} else {
_pulseOnce();
}
}
void _pulseOnce() async {
await _controller.forward();
await _controller.reverse();
if (mounted && widget.enabled) {
await Future.delayed(widget.delayBetweenPulses);
if (mounted && widget.enabled) {
_pulseOnce();
}
}
}
@override
void didUpdateWidget(ShimmerBackground oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.enabled != oldWidget.enabled) {
if (widget.enabled) {
_startPulsing();
} else {
_controller.stop();
_controller.reset();
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!enabled) {
return child;
if (!widget.enabled) {
return widget.child;
}
final borderRadius =
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
return Stack(
children: [
child,
Positioned.fill(
child: IgnorePointer(
child: ClipRRect(
borderRadius: borderRadius,
child: Shimmer.fromColors(
baseColor: baseColor ?? shimmerColor.withValues(alpha: 0.1),
highlightColor: shimmerColor.withValues(alpha: 0.6),
direction: ShimmerDirection.ltr,
child: Container(
decoration: BoxDecoration(
color: shimmerColor.withValues(alpha: 0.3),
borderRadius: borderRadius,
widget.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Stack(
children: [
widget.child,
Positioned.fill(
child: IgnorePointer(
child: ClipRRect(
borderRadius: borderRadius,
child: Container(
decoration: BoxDecoration(
color: widget.shimmerColor
.withValues(alpha: _animation.value),
borderRadius: borderRadius,
),
),
),
),
),
),
),
],
],
);
},
);
}
}

View file

@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
@ -136,49 +134,6 @@ class EmojiChoiceItem extends StatefulWidget {
}
class EmojiChoiceItemState extends State<EmojiChoiceItem> {
bool shimmer = false;
Timer? _shimmerTimer;
@override
void initState() {
super.initState();
_showShimmer();
}
@override
void didUpdateWidget(covariant EmojiChoiceItem oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.emoji != widget.emoji) {
_showShimmer();
}
}
@override
void dispose() {
_shimmerTimer?.cancel();
super.dispose();
}
void _showShimmer() {
if (!widget.showShimmer || !widget.enabled) return;
setState(() => shimmer = true);
_shimmerTimer?.cancel();
_shimmerTimer = Timer(const Duration(milliseconds: 1500), () {
if (mounted) {
setState(() => shimmer = false);
_repeatShimmer();
}
});
}
void _repeatShimmer() {
_shimmerTimer?.cancel();
_shimmerTimer = Timer(const Duration(seconds: 5), () {
if (mounted) _showShimmer();
});
}
@override
Widget build(BuildContext context) {
return HoverBuilder(
@ -191,11 +146,8 @@ class EmojiChoiceItemState extends State<EmojiChoiceItem> {
child: Stack(
children: [
ShimmerBackground(
enabled: shimmer,
shimmerColor: (Theme.of(context).brightness == Brightness.dark)
? Colors.white
: Theme.of(context).colorScheme.primary,
baseColor: Colors.transparent,
enabled: widget.showShimmer && widget.enabled,
delayBetweenPulses: const Duration(seconds: 5),
child: CompositedTransformTarget(
link: MatrixState.pAnyState
.layerLinkAndKey(widget.transformTargetId)