tane/apps/app_seeds/lib/ui/unread_badge.dart

85 lines
2.3 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import '../di/injector.dart';
import '../services/unread_service.dart';
/// Wraps [child] with a small live unread-count badge. It listens to the
/// app-wide [UnreadService] and rebuilds whenever a count changes: on a new
/// message, or when a chat is opened (marked read). Shows the total across all
/// conversations, or — when [peer] is set — just that peer's count.
///
/// When no unread tracker is available (widget tests, inventory-only builds) or
/// the count is zero, it renders [child] unadorned. Positioning is via Flutter's
/// [Badge], which is directional (top-`end`), so it's correct under RTL.
class UnreadBadge extends StatefulWidget {
const UnreadBadge({
required this.child,
this.peer,
this.service,
super.key,
});
final Widget child;
/// A specific conversation's count; null means the app-wide total.
final String? peer;
/// Overrides the [UnreadService] lookup (for tests). Falls back to `getIt`.
final UnreadService? service;
@override
State<UnreadBadge> createState() => _UnreadBadgeState();
}
class _UnreadBadgeState extends State<UnreadBadge> {
UnreadService? _service;
StreamSubscription<void>? _sub;
int _count = 0;
@override
void initState() {
super.initState();
_service = widget.service ??
(getIt.isRegistered<UnreadService>() ? getIt<UnreadService>() : null);
final service = _service;
if (service != null) {
_refresh();
_sub = service.changes.listen((_) => _refresh());
}
}
@override
void didUpdateWidget(UnreadBadge old) {
super.didUpdateWidget(old);
// A recycled row may now point at a different peer.
if (old.peer != widget.peer) _refresh();
}
Future<void> _refresh() async {
final service = _service;
if (service == null) return;
final peer = widget.peer;
final count = peer == null
? await service.totalUnreadCount()
: await service.unreadCount(peer);
if (mounted) setState(() => _count = count);
}
@override
void dispose() {
_sub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Badge(
isLabelVisible: _count > 0,
label: Text('$_count'),
backgroundColor: Theme.of(context).colorScheme.error,
child: widget.child,
);
}
}