247 lines
10 KiB
Dart
247 lines
10 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 '../../../../core/widgets/status_badge.dart';
|
|
import '../../../trades/models/trade_model.dart';
|
|
import '../../../trades/widgets/trade_execution_cockpit.dart';
|
|
import '../../../trades/widgets/trade_closing_cockpit.dart';
|
|
import '../../bloc/trades/asset_trades_bloc.dart';
|
|
import '../../bloc/trades/asset_trades_event.dart';
|
|
import '../../bloc/trades/asset_trades_state.dart';
|
|
import '../../widgets/trades/manual_analysis_dialog.dart';
|
|
import '../../widgets/trades/asset_trade_item_card.dart';
|
|
|
|
class TradesTab extends StatefulWidget {
|
|
final String symbol;
|
|
const TradesTab({super.key, required this.symbol});
|
|
|
|
@override
|
|
State<TradesTab> createState() => _TradesTabState();
|
|
}
|
|
|
|
class _TradesTabState extends State<TradesTab> {
|
|
bool _justTriggeredAnalysis = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
context.read<AssetTradesBloc>().add(LoadAssetTrades(widget.symbol));
|
|
}
|
|
|
|
void _showEditTradeExecutionDialog(BuildContext context, TradeModel trade, {bool isActive = false}) {
|
|
final tradesBloc = context.read<AssetTradesBloc>();
|
|
|
|
TradeExecutionCockpit.show(
|
|
context,
|
|
trade: trade,
|
|
defaultSymbol: widget.symbol,
|
|
isActive: isActive,
|
|
onAccept: (dto) {
|
|
tradesBloc.add(AcceptTradeEvent(dto, widget.symbol));
|
|
final tId = trade.id;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(isActive ? 'Einstellungen für Trade $tId gespeichert!' : 'Trade $tId angenommen & Position eröffnet!'),
|
|
backgroundColor: AppTheme.primaryEmerald,
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
},
|
|
onReject: (tId) {
|
|
tradesBloc.add(RejectTradeEvent(tId, widget.symbol));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Trade $tId abgelehnt.'),
|
|
backgroundColor: AppTheme.textSecondary,
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return BlocConsumer<AssetTradesBloc, AssetTradesState>(
|
|
listener: (context, state) {
|
|
if (_justTriggeredAnalysis && state is AssetTradesLoaded) {
|
|
final List<TradeModel> tradesList = state.data;
|
|
if (tradesList.isNotEmpty) {
|
|
_justTriggeredAnalysis = false;
|
|
final latestTrade = tradesList.first;
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_showEditTradeExecutionDialog(context, latestTrade);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
builder: (context, state) {
|
|
final List<TradeModel> tradesList = (state is AssetTradesLoaded) ? state.data : [];
|
|
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
GlassContainer(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('Trade & Signal Management', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15, color: Colors.white)),
|
|
const SizedBox(height: 4),
|
|
Text('KI-gestützte technische & fundamentale Trade-Analyse anfordern', style: TextStyle(color: AppTheme.textMuted, fontSize: 12), maxLines: 1, overflow: TextOverflow.ellipsis),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
StatusBadge(label: widget.symbol, color: AppTheme.accentCyan),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () {
|
|
ManualAnalysisDialog.show(
|
|
context,
|
|
symbol: widget.symbol,
|
|
initialRiskScore: 50.0,
|
|
onTrigger: (payload) {
|
|
setState(() => _justTriggeredAnalysis = true);
|
|
context.read<AssetTradesBloc>().add(TriggerManualAnalysis(widget.symbol, payload: payload));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('KI-Analyse für ${widget.symbol} abgeschlossen. Trade-Cockpit öffnet sich...'),
|
|
backgroundColor: AppTheme.accentCyan,
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
icon: const Icon(Icons.auto_awesome, size: 18),
|
|
label: const Text('KI-Analyse Starten', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppTheme.accentCyan,
|
|
foregroundColor: Colors.black,
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
if (state is AssetTradesLoading)
|
|
_buildTradesShimmer(context)
|
|
else if (state is AssetTradesError)
|
|
GlassContainer(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Text('Fehler: ${state.message}', style: TextStyle(color: AppTheme.accentRed)),
|
|
)
|
|
else if (state is AssetTradesLoaded) ...[
|
|
_buildTradeList(
|
|
'Aktive Trade-Signale & Positionen',
|
|
tradesList.where((t) {
|
|
final s = t.status.toUpperCase();
|
|
return s == 'ACTIVE' || s == 'PENDING' || s == 'PROPOSED';
|
|
}).toList(),
|
|
),
|
|
const SizedBox(height: 20),
|
|
_buildTradeList(
|
|
'Historische Trades & KI-Bewertungen',
|
|
tradesList.where((t) {
|
|
final s = t.status.toUpperCase();
|
|
return s == 'CLOSED' || s == 'REJECTED' || (s != 'ACTIVE' && s != 'PENDING' && s != 'PROPOSED');
|
|
}).toList(),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildTradesShimmer(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const ShimmerLoading(width: 260, height: 20, borderRadius: 6),
|
|
const SizedBox(height: 12),
|
|
for (int i = 0; i < 3; i++) ...[
|
|
const ShimmerLoading(width: double.infinity, height: 105, borderRadius: 14),
|
|
const SizedBox(height: 12),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildTradeList(String title, List<TradeModel> trades) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),
|
|
const SizedBox(height: 10),
|
|
if (trades.isEmpty)
|
|
GlassContainer(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Center(
|
|
child: Text('Keine Trades in dieser Kategorie vorhanden.', style: TextStyle(color: AppTheme.textMuted, fontSize: 12)),
|
|
),
|
|
)
|
|
else
|
|
ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: trades.length,
|
|
itemBuilder: (context, index) {
|
|
final trade = trades[index];
|
|
final s = trade.status.toUpperCase();
|
|
final isActive = s == 'ACTIVE';
|
|
|
|
return AssetTradeItemCard(
|
|
trade: trade,
|
|
defaultSymbol: widget.symbol,
|
|
onAccept: () => _showEditTradeExecutionDialog(context, trade),
|
|
onSettings: () => _showEditTradeExecutionDialog(context, trade, isActive: true),
|
|
onClose: isActive
|
|
? () {
|
|
TradeClosingCockpit.show(
|
|
context,
|
|
trade: trade,
|
|
defaultSymbol: widget.symbol,
|
|
onClose: (dto) {
|
|
final isinVal = trade.isin.isNotEmpty ? trade.isin : widget.symbol;
|
|
context.read<AssetTradesBloc>().add(CloseTradeEvent(trade.id, isinVal, dto.userExitPrice));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Trade ${trade.id} geschlossen! Realisierter Ausstiegskurs: €${dto.userExitPrice.toStringAsFixed(2)}'),
|
|
backgroundColor: AppTheme.primaryEmerald,
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
: null,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|