Files
Finlytic/FinlyticApp/lib/features/asset_detail/views/tabs/fundamentals_tab.dart
T

226 lines
8.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/widgets/glass_container.dart';
import '../../../../core/widgets/shimmer_loading.dart';
import '../../bloc/fundamentals/asset_fundamentals_bloc.dart';
import '../../bloc/fundamentals/asset_fundamentals_event.dart';
import '../../bloc/fundamentals/asset_fundamentals_state.dart';
import '../../widgets/fundamentals/analyst_price_target_card.dart';
import '../../widgets/fundamentals/fundamental_category_panels.dart';
import '../../widgets/fundamentals/company_profile_section.dart';
class FundamentalsTab extends StatelessWidget {
final String isin;
final String? symbol;
final bool isEmbedded;
const FundamentalsTab({
super.key,
this.symbol,
this.isEmbedded = false,
required this.isin,
});
String _getCurrencySymbol(String? ticker) {
if (ticker == null || ticker.isEmpty) return '';
final t = ticker.toUpperCase();
if (t.endsWith('.DE') || t.endsWith('.F') || t.endsWith('.VI') || t.endsWith('.PA') || t.endsWith('.AS') || t.endsWith('.MC') || t.endsWith('.MI')) {
return '';
}
if (t.endsWith('.L')) return '£';
if (t.endsWith('.TO') || t.endsWith('.V')) return 'CA\$';
return '\$';
}
String _getCurrencyCode(String? ticker) {
if (ticker == null || ticker.isEmpty) return 'EUR';
final t = ticker.toUpperCase();
if (t.endsWith('.DE') || t.endsWith('.F') || t.endsWith('.VI') || t.endsWith('.PA') || t.endsWith('.AS') || t.endsWith('.MC') || t.endsWith('.MI')) {
return 'EUR';
}
if (t.endsWith('.L')) return 'GBP';
if (t.endsWith('.TO') || t.endsWith('.V')) return 'CAD';
return 'USD';
}
@override
Widget build(BuildContext context) {
return BlocBuilder<AssetFundamentalsBloc, AssetFundamentalsState>(
builder: (context, state) {
if (state is AssetFundamentalsLoading) {
return _buildFundamentalsShimmer(context);
}
if (state is AssetFundamentalsError) {
return Center(
child: GlassContainer(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, color: AppTheme.accentRed, size: 48),
const SizedBox(height: 12),
Text('Fehler beim Laden der Fundamentaldaten: ${state.message}', style: const TextStyle(color: Colors.white70)),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () => context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(isin, ticker: symbol, forceRefresh: true)),
icon: const Icon(Icons.refresh),
label: const Text('Erneut versuchen'),
),
],
),
),
);
}
if (state is AssetFundamentalsLoaded && state.data != null) {
final data = state.data!;
final sym = _getCurrencySymbol(data.ticker);
final curCode = _getCurrencyCode(data.ticker);
return SingleChildScrollView(
physics: isEmbedded ? const NeverScrollableScrollPhysics() : null,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 1. Analyst Forecasts & Price Targets Header Card
AnalystPriceTargetCard(data: data, currencySymbol: sym),
const SizedBox(height: 20),
// 2. Responsive Side-by-Side Category List Panels (Valuation, Profitability, Dividends)
FundamentalCategoryPanels(data: data, currencySymbol: sym, currencyCode: curCode),
const SizedBox(height: 20),
// 3. Company Description & Detailed Executive Board
_buildSectionHeader('Unternehmensprofil & Führungskräfte', Icons.business_outlined),
const SizedBox(height: 12),
CompanyProfileSection(data: data, currencySymbol: sym),
],
),
);
}
return _buildEmptyState(context);
},
);
}
Widget _buildSectionHeader(String title, IconData icon) {
return Row(
children: [
Icon(icon, color: AppTheme.primaryEmerald, size: 20),
const SizedBox(width: 8),
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),
],
);
}
Widget _buildEmptyState(BuildContext context) {
return Center(
child: GlassContainer(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.analytics_outlined, color: AppTheme.textMuted, size: 48),
const SizedBox(height: 12),
const Text('Keine Fundamentaldaten verfügbar.', style: TextStyle(color: Colors.white70)),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () => context.read<AssetFundamentalsBloc>().add(LoadAssetFundamentals(isin, ticker: symbol, forceRefresh: true)),
icon: const Icon(Icons.refresh),
label: const Text('Aktualisieren'),
),
],
),
),
);
}
Widget _buildFundamentalsShimmer(BuildContext context) {
final isDesktop = MediaQuery.of(context).size.width >= 1050;
final isTablet = MediaQuery.of(context).size.width >= 680 && MediaQuery.of(context).size.width < 1050;
Widget panelShimmer() {
return GlassContainer(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerLoading(width: 180, height: 18, borderRadius: 6),
const SizedBox(height: 12),
const Divider(color: Colors.white10, height: 1),
const SizedBox(height: 8),
for (int i = 0; i < 9; i++) ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
ShimmerLoading(width: 100, height: 14, borderRadius: 4),
ShimmerLoading(width: 60, height: 14, borderRadius: 4),
],
),
),
],
],
),
);
}
return SingleChildScrollView(
physics: isEmbedded ? const NeverScrollableScrollPhysics() : null,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerLoading(width: double.infinity, height: 86, borderRadius: 16),
const SizedBox(height: 20),
if (isDesktop)
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: panelShimmer()),
const SizedBox(width: 14),
Expanded(child: panelShimmer()),
const SizedBox(width: 14),
Expanded(child: panelShimmer()),
],
)
else if (isTablet)
Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: panelShimmer()),
const SizedBox(width: 12),
Expanded(child: panelShimmer()),
],
),
const SizedBox(height: 12),
panelShimmer(),
],
)
else
Column(
children: [
panelShimmer(),
const SizedBox(height: 12),
panelShimmer(),
const SizedBox(height: 12),
panelShimmer(),
],
),
const SizedBox(height: 20),
const ShimmerLoading(width: 220, height: 20, borderRadius: 6),
const SizedBox(height: 12),
const ShimmerLoading(width: double.infinity, height: 140, borderRadius: 16),
],
),
);
}
}