import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../core/network/api_client.dart'; import '../../../core/network/signalr_service.dart'; import '../../../core/theme/app_theme.dart'; import '../../auth/bloc/auth_bloc.dart'; import '../bloc/trade_bloc.dart'; import '../bloc/trade_event.dart'; import '../bloc/trade_state.dart'; import '../models/trade_model.dart'; import '../models/trade_acceptance_dto.dart'; import '../repositories/trade_repository.dart'; import '../widgets/trade_card.dart'; import '../widgets/proposed_auto_trades_card.dart'; import '../widgets/trade_acceptance_dialog.dart'; import '../widgets/trade_execution_dialog.dart'; import '../widgets/trade_performance_bar.dart'; class TradesFeedScreen extends StatelessWidget { final ApiClient apiClient; final SignalRService signalRService; const TradesFeedScreen({ super.key, required this.apiClient, required this.signalRService, }); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => TradeBloc( repository: TradeRepository(apiClient: apiClient), )..add(const FetchTrades()), child: const _TradesFeedScreenContent(), ); } } class _TradesFeedScreenContent extends StatefulWidget { const _TradesFeedScreenContent(); @override State<_TradesFeedScreenContent> createState() => _TradesFeedScreenContentState(); } class _TradesFeedScreenContentState extends State<_TradesFeedScreenContent> { String _selectedFilter = 'Offen'; String _searchQuery = ''; final TextEditingController _searchCtrl = TextEditingController(); @override void dispose() { _searchCtrl.dispose(); super.dispose(); } Future _handleAcceptProposal(BuildContext context, TradeModel trade) async { final authState = context.read().state; final currentUserId = (authState is Authenticated) ? authState.user.userId : 'default_user'; final result = await showDialog( context: context, builder: (ctx) => TradeAcceptanceDialog( trade: trade, theme: AppTheme.darkClassic, userId: currentUserId, ), ); if (result != null && mounted) { context.read().add(AcceptTradeProposalEvent(result)); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Trade wird in dein Portfolio übernommen...'), backgroundColor: AppTheme.primaryEmerald, ), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.darkBackground, body: SafeArea( child: RefreshIndicator( onRefresh: () async { context.read().add(const FetchTrades()); }, color: AppTheme.primaryEmerald, backgroundColor: AppTheme.cardSurface, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Live Portfolio & Trading', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white), ), const SizedBox(height: 2), Text( 'KI-Erkennungen, Vorschläge & Aktive Positionen', style: TextStyle(color: AppTheme.textMuted, fontSize: 12), ), ], ), IconButton( onPressed: () => context.read().add(const FetchTrades()), icon: const Icon(Icons.refresh, color: Colors.white70), tooltip: 'Trades Aktualisieren', ), ], ), const SizedBox(height: 16), Expanded( child: BlocBuilder( builder: (context, state) { if (state is TradeLoading) { return Center(child: CircularProgressIndicator(color: AppTheme.primaryEmerald)); } if (state is TradeError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48, color: AppTheme.accentRed), const SizedBox(height: 12), Text(state.message, style: TextStyle(color: AppTheme.textMuted)), const SizedBox(height: 16), ElevatedButton( onPressed: () => context.read().add(const FetchTrades()), style: ElevatedButton.styleFrom(backgroundColor: AppTheme.primaryEmerald), child: const Text('Erneut Versuchen'), ), ], ), ); } if (state is TradeLoaded) { final allTrades = state.trades; final proposals = allTrades.where((t) => t.isProposed).toList(); final activeTrades = allTrades.where((t) => t.isActive).toList(); final closedTrades = allTrades.where((t) => t.isClosed).toList(); final rejectedTrades = allTrades.where((t) => t.isRejected).toList(); List filteredList = allTrades; if (_selectedFilter == 'Alle') { filteredList = allTrades.where((t) => !t.isProposed).toList(); } else if (_selectedFilter == 'Offen') { filteredList = activeTrades; } else if (_selectedFilter == 'Vorschläge') { filteredList = proposals; } else if (_selectedFilter == 'Geschlossen') { filteredList = closedTrades; } else if (_selectedFilter == 'Abgelehnt') { filteredList = rejectedTrades; } if (_searchQuery.trim().isNotEmpty) { final q = _searchQuery.toLowerCase().trim(); filteredList = filteredList.where((t) => t.symbol.toLowerCase().contains(q) || t.isin.toLowerCase().contains(q) || t.companyName.toLowerCase().contains(q)).toList(); } return ListView( children: [ TradePerformanceBar( activeTrades: activeTrades, allTrades: allTrades, proposals: proposals, ), ProposedAutoTradesCard( proposals: proposals, onAcceptProposal: (trade) => _handleAcceptProposal(context, trade), ), Row( children: [ Expanded( child: TextField( controller: _searchCtrl, onChanged: (val) => setState(() => _searchQuery = val), style: const TextStyle(color: Colors.white, fontSize: 13), decoration: InputDecoration( hintText: 'Suche nach Symbol, ISIN oder Name...', hintStyle: TextStyle(color: AppTheme.textMuted, fontSize: 13), prefixIcon: const Icon(Icons.search, size: 18, color: Colors.white54), filled: true, fillColor: Colors.white.withValues(alpha: 0.05), contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.1))), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.1))), ), ), ), ], ), const SizedBox(height: 12), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _filterChip('Alle', allTrades.length), _filterChip('Offen', activeTrades.length), _filterChip('Vorschläge', proposals.length), _filterChip('Abgelehnt', rejectedTrades.length), _filterChip('Geschlossen', closedTrades.length), ], ), ), const SizedBox(height: 16), if (filteredList.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 40), child: Center( child: Column( children: [ Icon(Icons.inbox, size: 40, color: AppTheme.textMuted), const SizedBox(height: 8), Text('Keine Trades in der Kategorie "$_selectedFilter" gefunden.', style: TextStyle(color: AppTheme.textMuted, fontSize: 13)), ], ), ), ) else ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: filteredList.length, itemBuilder: (context, index) { final trade = filteredList[index]; return TradeCard( trade: trade, onAccept: () => _handleAcceptProposal(context, trade), onSettings: () => _showTradeSettingsDialog(context, trade), onClose: () { context.read().add(CloseTrade(trade.id)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Position wird geschlossen...')), ); }, ); }, ), ], ); } return const SizedBox.shrink(); }, ), ), ], ), ), ), ), ); } Widget _filterChip(String label, int count) { final isSelected = _selectedFilter == label; return GestureDetector( onTap: () => setState(() => _selectedFilter = label), child: Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: isSelected ? AppTheme.primaryEmerald : Colors.white.withValues(alpha: 0.06), borderRadius: BorderRadius.circular(20), border: Border.all(color: isSelected ? AppTheme.primaryEmerald : Colors.white.withValues(alpha: 0.12)), ), child: Row( children: [ Text( label, style: TextStyle( color: isSelected ? Colors.black : Colors.white, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontSize: 12, ), ), const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: isSelected ? Colors.black.withValues(alpha: 0.2) : Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(10), ), child: Text( '$count', style: TextStyle( color: isSelected ? Colors.black : Colors.white70, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ], ), ), ); } void _showTradeSettingsDialog(BuildContext context, TradeModel trade) { final tradeBloc = context.read(); TradeExecutionDialog.show( context, trade: trade, defaultSymbol: trade.symbol, isActive: true, onAccept: (dto) { tradeBloc.add(AcceptTradeProposalEvent(dto)); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Einstellungen für ${trade.symbol} gespeichert.'), backgroundColor: AppTheme.primaryEmerald, ), ); }, ); } }