Files
bvandeusen a7fcafadda feat(flutter): DelayedLoading wrapper for skeleton-with-delay
Renders nothing for the first [delay] (default 200ms) of sustained
loading, then renders the [whileDelayed] child until loading completes.
Mirrors the web useDelayed hook so a fast network response doesn't
flash a skeleton.

Used in the next commit's home screen rewrite; otherwise unused this
slice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:38:42 -04:00

67 lines
1.6 KiB
Dart

import 'dart:async';
import 'package:flutter/widgets.dart';
/// Renders [whileDelayed] only after [isLoading] has been true
/// continuously for [delay], then renders [whenReady] once loading
/// completes. Mirrors the web `useDelayed` hook: a brief loading flash
/// doesn't trigger a skeleton; sustained loading does.
///
/// Once [isLoading] flips back to false, the timer resets.
class DelayedLoading extends StatefulWidget {
const DelayedLoading({
super.key,
required this.isLoading,
required this.whileDelayed,
required this.whenReady,
this.delay = const Duration(milliseconds: 200),
});
final bool isLoading;
final Widget whileDelayed;
final Widget whenReady;
final Duration delay;
@override
State<DelayedLoading> createState() => _DelayedLoadingState();
}
class _DelayedLoadingState extends State<DelayedLoading> {
Timer? _timer;
bool _delayPassed = false;
@override
void initState() {
super.initState();
_maybeStart();
}
@override
void didUpdateWidget(DelayedLoading old) {
super.didUpdateWidget(old);
if (widget.isLoading != old.isLoading) {
_timer?.cancel();
_delayPassed = false;
_maybeStart();
}
}
void _maybeStart() {
if (!widget.isLoading) return;
_timer = Timer(widget.delay, () {
if (mounted) setState(() => _delayPassed = true);
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!widget.isLoading) return widget.whenReady;
return _delayPassed ? widget.whileDelayed : const SizedBox.shrink();
}
}