feat(asset_detail): modular fundamentals sections, executive salaries and logo resolution

This commit is contained in:
2026-08-15 01:03:30 +02:00
parent 15f8f7896e
commit 1ccb6b613f
21 changed files with 2011 additions and 2081 deletions
@@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/widgets/asset_logo_widget.dart';
import '../../../../shared/widgets/favorite_star_button.dart';
import '../../bloc/header/asset_header_bloc.dart';
import '../../bloc/header/asset_header_state.dart';
import '../../bloc/fundamentals/asset_fundamentals_bloc.dart';
import '../../bloc/fundamentals/asset_fundamentals_state.dart';
import '../../bloc/technical/asset_technical_bloc.dart';
import '../../bloc/technical/asset_technical_state.dart';
import '../../models/asset_model.dart';
import '../../models/fundamental_data_model.dart';
import 'package:url_launcher/url_launcher.dart';
class AssetHeroHeader extends StatelessWidget {
@@ -20,35 +20,31 @@ class AssetHeroHeader extends StatelessWidget {
const AssetHeroHeader({
super.key,
this.onExchangeChanged,
this.onForceRefresh, required this.isin, required this.name, this.symbol,
this.onForceRefresh,
required this.isin,
required this.name,
this.symbol,
});
@override
Widget build(BuildContext context) {
final theme = AppTheme.activePreset;
return BlocBuilder<AssetHeaderBloc, AssetHeaderState>(
builder: (context, state) {
double? price;
String currency = 'EUR';
List<AssetTickerOption> tickerOptions = [
AssetTickerOption(ticker: 'Loading', exchange: 'Loading', tradingCurrency: '', currentPrice: 0.0)
return BlocBuilder<AssetFundamentalsBloc, AssetFundamentalsState>(
builder: (context, fundState) {
String displayName = name;
final String? logoUrl = isin.isNotEmpty ? '/api/v1/logo/$isin' : null;
List<TickerModel> tickerOptions = [
TickerModel(ticker: symbol ?? 'Loading', exchange: 'Loading', tradingCurrency: '', currentPrice: null)
];
AssetModel? asset;
if (state is AssetHeaderLoaded) {
asset = state.data;
} else if (state is AssetHeaderLoading) {
asset = state.previousData;
}
if (asset != null) {
//name = asset.name.isNotEmpty ? asset.name : symbol;
currency = asset.currency.isNotEmpty ? asset.currency : 'EUR';
price = asset.currentPrice;
if (asset.tickers.isNotEmpty) {
tickerOptions = asset.tickers;
if (fundState is AssetFundamentalsLoaded && fundState.data != null) {
final data = fundState.data!;
if (data.companyName.isNotEmpty) {
displayName = data.companyName;
}
if (data.availableTickers.isNotEmpty) {
tickerOptions = data.availableTickers;
}
}
@@ -88,14 +84,14 @@ class AssetHeroHeader extends StatelessWidget {
),
const SizedBox(width: 8),
],
AssetLogoWidget(symbolOrName: isin, imageUrl: asset?.image, size: 48),
AssetLogoWidget(symbolOrName: isin, imageUrl: logoUrl, size: 48),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
name,
displayName,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w900,
@@ -138,7 +134,7 @@ class AssetHeroHeader extends StatelessWidget {
onPressed: onForceRefresh,
),
const SizedBox(width: 8),
FavoriteStarButton(symbol: symbol, identifier: isin, name: name),
FavoriteStarButton(symbol: symbol, identifier: isin, name: displayName),
],
),
],
@@ -150,10 +146,8 @@ class AssetHeroHeader extends StatelessWidget {
children: [
BlocBuilder<AssetTechnicalBloc, AssetTechnicalState>(
builder: (context, taState) {
double? livePrice = price;
String liveCurrency = selectedOption.tradingCurrency.isNotEmpty
? selectedOption.tradingCurrency
: currency;
double? livePrice = selectedOption.currentPrice;
String liveCurrency = selectedOption.tradingCurrency ?? 'EUR';
if (taState is AssetTechnicalLoaded && taState.data != null) {
if (taState.data!.candles.isNotEmpty) {
@@ -219,12 +213,12 @@ class AssetHeroHeader extends StatelessWidget {
(t) => t.ticker == newTicker,
orElse: () => tickerOptions.first,
);
onExchangeChanged!(opt.exchange, opt.ticker);
onExchangeChanged!(opt.exchange ?? 'Unknown', opt.ticker);
}
},
itemBuilder: (context) {
return tickerOptions.map((opt) {
final ex = opt.exchange;
final ex = opt.exchange ?? 'Unknown';
final tick = opt.ticker;
final label = '$tick ($ex)';
final isSelected = tick == symbol || ex == symbol;
@@ -263,7 +257,7 @@ class AssetHeroHeader extends StatelessWidget {
Icon(Icons.business, size: 14, color: theme.accentColor),
const SizedBox(width: 6),
Text(
'${selectedOption.ticker} (${selectedOption.exchange})',
'${selectedOption.ticker} (${selectedOption.exchange ?? 'Unknown'})',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
@@ -285,3 +279,4 @@ class AssetHeroHeader extends StatelessWidget {
);
}
}