refactor: save current workspace state including FinlyticAnalyzer fixes, FinlyticApp trade route alignment, and DTO audit documentation

This commit is contained in:
2026-08-12 18:30:42 +02:00
parent a9553e9fbf
commit 3d8af3940b
163 changed files with 3421 additions and 1751 deletions
@@ -1,98 +0,0 @@
import '../network/api_client.dart';
/// Asset utility helpers to map ISIN codes, symbols, and company names to logos and details.
class AssetUtils {
static final Map<String, String> _isinToNameMap = {
'US0378331005': 'Apple Inc.',
'US5949181045': 'Microsoft Corp.',
'US0231351067': 'Amazon.com Inc.',
'US67066G1040': 'NVIDIA Corp.',
'US88160R1014': 'Tesla Inc.',
'US02079K3059': 'Alphabet Inc.',
'US30303M1027': 'Meta Platforms',
'DE0007164600': 'SAP SE',
'DE0007236101': 'Siemens AG',
'DE0008469008': 'Allianz SE',
'FR0004125920': 'Amundi',
};
static final Map<String, String> _nameToIsinMap = {
'APPLE INC.': 'US0378331005',
'APPLE': 'US0378331005',
'MICROSOFT CORP.': 'US5949181045',
'MICROSOFT': 'US5949181045',
'AMAZON.COM INC.': 'US0231351067',
'AMAZON': 'US0231351067',
'NVIDIA CORP.': 'US67066G1040',
'NVIDIA': 'US67066G1040',
'TESLA INC.': 'US88160R1014',
'TESLA': 'US88160R1014',
'ALPHABET INC.': 'US02079K3059',
'ALPHABET': 'US02079K3059',
'META PLATFORMS': 'US30303M1027',
'META': 'US30303M1027',
'SAP SE': 'DE0007164600',
'SAP': 'DE0007164600',
'SIEMENS AG': 'DE0007236101',
'SIEMENS': 'DE0007236101',
'ALLIANZ SE': 'DE0008469008',
'ALLIANZ': 'DE0008469008',
'AMUNDI': 'FR0004125920',
};
static final Map<String, String> _imageMap = {};
/// Registers an ISIN, Name, and optional Logo Image URL.
static void registerAsset(String isin, String name, [String? imageUrl]) {
final cleanIsin = isin.trim().toUpperCase();
final cleanName = name.trim();
if (cleanIsin.isNotEmpty && cleanName.isNotEmpty) {
_isinToNameMap[cleanIsin] = cleanName;
_nameToIsinMap[cleanName.toUpperCase()] = cleanIsin;
}
if (imageUrl != null && imageUrl.isNotEmpty) {
final resolved = resolveUrl(imageUrl);
if (cleanIsin.isNotEmpty) _imageMap[cleanIsin] = resolved;
if (cleanName.isNotEmpty) _imageMap[cleanName.toUpperCase()] = resolved;
}
}
/// Resolves relative logo URLs (/api/logo/...) to complete backend endpoints.
static String resolveUrl(String url) {
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
if (url.startsWith('/')) {
return '${ApiClient.baseUrl}$url';
}
return '${ApiClient.baseUrl}/$url';
}
/// Resolves an ISIN or symbol to readable asset name.
static String getAssetName(String isinOrSymbol) {
final key = isinOrSymbol.trim().toUpperCase();
if (_isinToNameMap.containsKey(key)) {
return _isinToNameMap[key]!;
}
return isinOrSymbol;
}
/// Resolves an asset name or symbol to ISIN.
static String? getIsin(String nameOrSymbol) {
final key = nameOrSymbol.trim().toUpperCase();
if (_isinToNameMap.containsKey(key)) return key;
return _nameToIsinMap[key];
}
/// Returns official local backend logo URL for given symbol/name/ISIN.
static String? getLogoUrl(String symbolOrName) {
final key = symbolOrName.trim().toUpperCase();
if (_imageMap.containsKey(key)) return resolveUrl(_imageMap[key]!);
final isin = getIsin(key) ?? (key.length == 12 ? key : null);
if (isin != null && isin.length == 12) {
return '${ApiClient.baseUrl}/api/logo/$isin';
}
return null;
}
}
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../network/api_client.dart';
import '../theme/app_theme.dart';
import '../utils/asset_utils.dart';
/// Reusable performance-optimized Asset Logo Widget supporting SVG, PNG, gradient fallbacks, and Hero transitions.
class AssetLogoWidget extends StatelessWidget {
@@ -15,24 +15,29 @@ class AssetLogoWidget extends StatelessWidget {
const AssetLogoWidget({
super.key,
required this.symbolOrName,
this.imageUrl,
required this.imageUrl,
this.size = 32,
this.enableHero = true,
});
@override
Widget build(BuildContext context) {
final rawUrl = imageUrl ?? AssetUtils.getLogoUrl(symbolOrName);
final logoUrl = rawUrl != null && rawUrl.isNotEmpty ? AssetUtils.resolveUrl(rawUrl) : null;
// Resolve relative URLs (e.g. /api/v1/logo/...) to include host and port (e.g. http://localhost:5000)
String? resolveUrl(String? url) {
if (url == null || url.isEmpty) return null;
if (url.startsWith('http://') || url.startsWith('https://')) return url;
return url.startsWith('/') ? '${ApiClient.baseUrl}$url' : '${ApiClient.baseUrl}/$url';
}
final image = resolveUrl(imageUrl);
final initial = symbolOrName.isNotEmpty ? symbolOrName[0].toUpperCase() : 'A';
final colors = _getGradientColors(initial);
Widget content;
if (logoUrl != null && logoUrl.isNotEmpty && !_failedUrls.contains(logoUrl)) {
final isSvg = logoUrl.toLowerCase().endsWith('.svg') ||
logoUrl.contains('traderepublic.com') ||
logoUrl.contains('/api/logo/');
if (image != null && image.isNotEmpty && !_failedUrls.contains(image)) {
final isSvg = image.toLowerCase().endsWith('.svg') ||
image.contains('/api/v1/logo/');
content = ClipRRect(
borderRadius: BorderRadius.circular(size * 0.3),
@@ -50,26 +55,26 @@ class AssetLogoWidget extends StatelessWidget {
padding: EdgeInsets.all(size * 0.1),
child: isSvg
? SvgPicture.network(
logoUrl,
width: size,
height: size,
fit: BoxFit.contain,
placeholderBuilder: (context) => _buildFallback(initial, colors),
errorBuilder: (context, error, stackTrace) {
_failedUrls.add(logoUrl);
return _buildFallback(initial, colors);
},
)
image,
width: size,
height: size,
fit: BoxFit.contain,
placeholderBuilder: (context) => _buildFallback(initial, colors),
errorBuilder: (context, error, stackTrace) {
_failedUrls.add(image);
return _buildFallback(initial, colors);
},
)
: Image.network(
logoUrl,
width: size,
height: size,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
_failedUrls.add(logoUrl);
return _buildFallback(initial, colors);
},
),
image,
width: size,
height: size,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
_failedUrls.add(image);
return _buildFallback(initial, colors);
},
),
),
);
} else {
@@ -78,7 +83,7 @@ class AssetLogoWidget extends StatelessWidget {
if (enableHero && symbolOrName.isNotEmpty) {
return Hero(
tag: 'asset_logo_$symbolOrName',
tag: 'asset_logo_${symbolOrName}_$size',
child: content,
);
}
@@ -127,4 +132,4 @@ class AssetLogoWidget extends StatelessWidget {
return [AppTheme.activePreset.accentColor, const Color(0xFF3B82F6)];
}
}
}
}