import 'package:flutter/material.dart'; /// An asset's picture: the issuer's logo from the broker, and while it loads or when the /// broker has none, a circle in the brand colour with the icon of the asset class. /// /// The logo URL and colour come from the API as they are; a failed load (offline, a logo /// the CDN has since dropped) quietly falls back rather than showing a broken image. class AssetIcon extends StatelessWidget { const AssetIcon({ super.key, required this.assetClass, this.logoUrl, this.logoColor, this.size = 32, }); final String? assetClass; final String? logoUrl; final String? logoColor; final double size; @override Widget build(BuildContext context) { final url = logoUrl; final fallback = _Fallback( assetClass: assetClass, logoColor: logoColor, size: size, ); return SizedBox.square( dimension: size, child: ClipOval( child: url == null ? fallback : Image.network( url, width: size, height: size, fit: BoxFit.cover, filterQuality: FilterQuality.medium, gaplessPlayback: true, errorBuilder: (_, _, _) => fallback, loadingBuilder: (_, child, progress) => progress == null ? child : fallback, ), ), ); } } class _Fallback extends StatelessWidget { const _Fallback({ required this.assetClass, required this.logoColor, required this.size, }); final String? assetClass; final String? logoColor; final double size; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final brand = parseBrandColor(logoColor); final background = brand ?? scheme.surfaceContainerHigh; final foreground = brand == null ? scheme.onSurfaceVariant : (ThemeData.estimateBrightnessForColor(brand) == Brightness.dark ? Colors.white : Colors.black87); return ColoredBox( color: background, child: Icon( assetClassIcon(assetClass), size: size * 0.55, color: foreground, ), ); } } /// `#21A038` (or `#AA21A038`) as a colour; null for anything else, so a malformed value /// from the broker degrades to the neutral fallback instead of throwing. Color? parseBrandColor(String? hex) { if (hex == null) return null; final digits = hex.startsWith('#') ? hex.substring(1) : hex; if (digits.length != 6 && digits.length != 8) return null; final value = int.tryParse(digits, radix: 16); if (value == null) return null; return Color(digits.length == 6 ? 0xFF000000 | value : value); } IconData assetClassIcon(String? assetClass) => switch (assetClass) { 'share' => Icons.trending_up, 'bond' => Icons.receipt_long_outlined, 'etf' || 'fund' => Icons.pie_chart_outline, 'currency' => Icons.currency_exchange, 'index' => Icons.show_chart, 'deposit' => Icons.savings_outlined, 'real_estate' => Icons.home_outlined, 'crypto' => Icons.currency_bitcoin, _ => Icons.circle_outlined, };