feat(trades): add live execution cockpit, closing cockpit, calculation cards and precision trade settings

This commit is contained in:
2026-08-15 19:30:25 +02:00
parent 882d24a316
commit 34fa774cbf
31 changed files with 4235 additions and 630 deletions
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/network/api_client.dart';
import '../../favorites/cubit/favorites_cubit.dart';
import '../bloc/fundamentals/asset_fundamentals_bloc.dart';
import '../bloc/fundamentals/asset_fundamentals_event.dart';
import '../bloc/technical/asset_technical_bloc.dart';
@@ -8,6 +9,7 @@ import '../bloc/technical/asset_technical_event.dart';
import '../bloc/trades/asset_trades_bloc.dart';
import '../bloc/trades/asset_trades_event.dart';
import '../repositories/asset_repository.dart';
import '../utils/ticker_resolver.dart';
import 'layouts/asset_page_desktop_layout.dart';
import 'layouts/asset_page_mobile_layout.dart';
@@ -29,39 +31,49 @@ class AssetDetailScreen extends StatelessWidget {
Widget build(BuildContext context) {
final repository = AssetRepository(apiClient: apiClient);
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AssetFundamentalsBloc(repository: repository)
..add(LoadAssetFundamentals(isin, ticker: symbol)),
),
BlocProvider(
create: (context) => AssetTechnicalBloc(repository: repository)
..add(LoadAssetTechnical(isin, ticker: symbol)),
),
BlocProvider(
create: (context) => AssetTradesBloc(repository: repository)
..add(LoadAssetTrades(isin)),
),
],
child: Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 900) {
return AssetPageDesktopLayout(
isin: isin,
name: name,
selectedTicker: symbol,
);
}
return AssetPageMobileLayout(
isin: isin,
name: name,
selectedTicker: symbol,
);
},
),
),
return BlocBuilder<FavoritesCubit, FavoritesState>(
builder: (context, favState) {
final initialTicker = TickerResolver.resolve(
isin: isin,
candidateSymbol: symbol,
favoriteDetails: favState.favoriteDetails,
);
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AssetFundamentalsBloc(repository: repository)
..add(LoadAssetFundamentals(isin, ticker: initialTicker)),
),
BlocProvider(
create: (context) => AssetTechnicalBloc(repository: repository)
..add(LoadAssetTechnical(isin, ticker: initialTicker)),
),
BlocProvider(
create: (context) => AssetTradesBloc(repository: repository)
..add(LoadAssetTrades(isin)),
),
],
child: Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 900) {
return AssetPageDesktopLayout(
isin: isin,
name: name,
selectedTicker: initialTicker ?? symbol,
);
}
return AssetPageMobileLayout(
isin: isin,
name: name,
selectedTicker: initialTicker ?? symbol,
);
},
),
),
);
},
);
}
}
@@ -5,13 +5,12 @@ 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_dialog.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/live_trade_settings_dialog.dart';
import '../../widgets/trades/manual_analysis_dialog.dart';
import '../../widgets/trades/close_trade_dialog.dart';
import '../../widgets/trades/asset_trade_item_card.dart';
class TradesTab extends StatefulWidget {
@@ -24,13 +23,6 @@ class TradesTab extends StatefulWidget {
class _TradesTabState extends State<TradesTab> {
bool _justTriggeredAnalysis = false;
LiveTradeSettings _settings = const LiveTradeSettings(
defaultPositionSize: 2500.0,
defaultLeverage: 5.0,
defaultRiskScore: 50.0,
defaultOrderFee: 1.0,
autoAcceptSignals: false,
);
@override
void initState() {
@@ -41,7 +33,7 @@ class _TradesTabState extends State<TradesTab> {
void _showEditTradeExecutionDialog(BuildContext context, TradeModel trade, {bool isActive = false}) {
final tradesBloc = context.read<AssetTradesBloc>();
TradeExecutionDialog.show(
TradeExecutionCockpit.show(
context,
trade: trade,
defaultSymbol: widget.symbol,
@@ -116,55 +108,36 @@ class _TradesTabState extends State<TradesTab> {
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
ManualAnalysisDialog.show(
context,
symbol: widget.symbol,
initialRiskScore: _settings.defaultRiskScore,
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} gestartet. Trade-Ausführungsdialog öffnet sich in Kürze...'),
backgroundColor: AppTheme.accentCyan,
behavior: SnackBarBehavior.floating,
),
);
},
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('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(10)),
),
),
);
},
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(width: 10),
IconButton.filledTonal(
onPressed: () {
LiveTradeSettingsDialog.show(
context,
currentSettings: _settings,
onSave: (newSettings) => setState(() => _settings = newSettings),
);
},
icon: const Icon(Icons.settings, color: Colors.white),
tooltip: 'Live Trade Einstellungen',
style: IconButton.styleFrom(
backgroundColor: AppTheme.glassSurface,
padding: const EdgeInsets.all(14),
),
),
],
),
),
],
),
@@ -245,7 +218,7 @@ class _TradesTabState extends State<TradesTab> {
onSettings: () => _showEditTradeExecutionDialog(context, trade, isActive: true),
onClose: isActive
? () {
CloseTradeDialog.show(
TradeClosingCockpit.show(
context,
trade: trade,
defaultSymbol: widget.symbol,
@@ -254,7 +227,7 @@ class _TradesTabState extends State<TradesTab> {
context.read<AssetTradesBloc>().add(CloseTradeEvent(trade.id, isinVal, dto.userExitPrice));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Trade ${trade.id} geschlossen! Ausstiegskurs: €${dto.userExitPrice.toStringAsFixed(2)}'),
content: Text('Trade ${trade.id} geschlossen! Realisierter Ausstiegskurs: €${dto.userExitPrice.toStringAsFixed(2)}'),
backgroundColor: AppTheme.primaryEmerald,
behavior: SnackBarBehavior.floating,
),
@@ -270,3 +243,4 @@ class _TradesTabState extends State<TradesTab> {
);
}
}