import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_user_badge_res.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart'; class SCUserBadgeStrip extends StatefulWidget { const SCUserBadgeStrip({ super.key, this.userId, this.fallbackBadges = const [], this.height, this.badgeHeight, this.longBadgeWidth, this.spacing, this.maxBadges, this.wrap = false, this.reserveSpace = false, this.alignment = WrapAlignment.center, }); final String? userId; final List fallbackBadges; final double? height; final double? badgeHeight; final double? longBadgeWidth; final double? spacing; final int? maxBadges; final bool wrap; final bool reserveSpace; final WrapAlignment alignment; @override State createState() => _SCUserBadgeStripState(); } class _SCUserBadgeStripState extends State { static const Duration _cacheTtl = Duration(minutes: 1); static final Map _badgeFutures = {}; Future? _future; @override void initState() { super.initState(); _future = _resolveFuture(); } @override void didUpdateWidget(covariant SCUserBadgeStrip oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.userId != widget.userId) { _future = _resolveFuture(); } } Future? _resolveFuture() { final userId = _normalizedUserId; if (userId.isEmpty) { return null; } final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? ""; final isCurrentUser = currentUserId.isNotEmpty && userId == currentUserId; final cacheKey = isCurrentUser ? "current:$userId" : "other:$userId"; final now = DateTime.now(); _badgeFutures.removeWhere( (_, entry) => now.difference(entry.createdAt) >= _cacheTtl, ); final cacheEntry = _badgeFutures[cacheKey]; if (cacheEntry != null && now.difference(cacheEntry.createdAt) < _cacheTtl) { return cacheEntry.future; } final future = () async { try { return isCurrentUser ? await SCAccountRepository().currentUserBadges() : await SCAccountRepository().otherUserBadges(userId); } catch (_) { _badgeFutures.remove(cacheKey); return SCUserBadgeRes.empty(userId: userId); } }(); _badgeFutures[cacheKey] = _BadgeFutureCacheEntry(future, now); return future; } String get _normalizedUserId => widget.userId?.trim() ?? ""; @override Widget build(BuildContext context) { final stripHeight = widget.height ?? 25.w; final future = _future; if (future == null) { return _buildBadges(_fallbackBadges, stripHeight); } return FutureBuilder( future: future, builder: (context, snapshot) { final remoteBadges = snapshot.data?.displayBadges ?? const []; final badges = remoteBadges.isNotEmpty ? remoteBadges : _fallbackBadges; return _buildBadges(badges, stripHeight); }, ); } List get _fallbackBadges { return widget.fallbackBadges .where((badge) => _badgeUrl(badge).isNotEmpty) .toList(); } Widget _buildBadges(List rawBadges, double stripHeight) { final badges = widget.maxBadges == null ? rawBadges : rawBadges.take(widget.maxBadges!).toList(); if (badges.isEmpty) { return widget.reserveSpace ? SizedBox(height: stripHeight) : const SizedBox.shrink(); } final badgeHeight = widget.badgeHeight ?? stripHeight; final spacing = widget.spacing ?? 5.w; final children = badges .map((badge) => _buildBadge(badge, badgeHeight: badgeHeight)) .toList(); if (widget.wrap) { return SizedBox( height: stripHeight, child: Center( child: Wrap( alignment: widget.alignment, spacing: spacing, runSpacing: 5.w, children: children, ), ), ); } return SizedBox( height: stripHeight, child: SingleChildScrollView( scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), child: Row(children: _withSpacing(children, spacing)), ), ); } Widget _buildBadge(WearBadge badge, {required double badgeHeight}) { final type = badge.type?.trim().toUpperCase() ?? ""; final sourceType = badge.sourceType?.trim().toUpperCase() ?? ""; final isLongBadge = type == "LONG" || type == "VIP" || sourceType == "VIP"; final width = isLongBadge ? (widget.longBadgeWidth ?? badgeHeight * 2.2) : badgeHeight; return netImage( url: _badgeUrl(badge), width: width, height: badgeHeight, fit: BoxFit.contain, noDefaultImg: true, loadingWidget: const SizedBox.shrink(), errorWidget: const SizedBox.shrink(), ); } String _badgeUrl(WearBadge badge) { return (badge.selectUrl ?? "").trim(); } List _withSpacing(List children, double spacing) { if (children.length < 2) { return children; } final spaced = []; for (var i = 0; i < children.length; i++) { if (i > 0) { spaced.add(SizedBox(width: spacing)); } spaced.add(children[i]); } return spaced; } } class _BadgeFutureCacheEntry { const _BadgeFutureCacheEntry(this.future, this.createdAt); final Future future; final DateTime createdAt; }