326 lines
15 KiB
Dart
326 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
||
import '../../../core/theme/app_theme.dart';
|
||
import '../../../core/widgets/asset_logo_widget.dart';
|
||
import '../../../core/widgets/glass_container.dart';
|
||
import '../../trades/models/trade_model.dart';
|
||
|
||
class ProposalDecisionScreen extends StatelessWidget {
|
||
final TradeProposalModel proposal;
|
||
final VoidCallback? onExecuteBot;
|
||
final VoidCallback? onManualTrade;
|
||
|
||
const ProposalDecisionScreen({
|
||
super.key,
|
||
required this.proposal,
|
||
this.onExecuteBot,
|
||
this.onManualTrade,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final symbol = proposal.symbol.isNotEmpty ? proposal.symbol : 'ASSET';
|
||
final isin = proposal.underlyingIsin;
|
||
final isLong = proposal.isLong;
|
||
final strategyKey = proposal.strategyKey.isNotEmpty ? proposal.strategyKey : 'Unbekannte Strategie';
|
||
final score = proposal.compositeScore;
|
||
final entryPrice = proposal.entryPrice;
|
||
final invalidationPrice = proposal.invalidationPrice;
|
||
|
||
final aiValidation = proposal.aiValidation;
|
||
final hasAiThesisContent = aiValidation != null && aiValidation.hasContent;
|
||
final isRuleBasedApproval = aiValidation != null && !aiValidation.isAiValidated;
|
||
final aiConfidence = aiValidation?.confidence;
|
||
final deriv = proposal.selectedDerivative;
|
||
final hasDerivative = deriv != null;
|
||
|
||
return Scaffold(
|
||
backgroundColor: AppTheme.darkBackground,
|
||
appBar: AppBar(
|
||
backgroundColor: Colors.transparent,
|
||
elevation: 0,
|
||
title: Text('Trade Proposal: $symbol', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
|
||
),
|
||
body: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Top Asset Card
|
||
GlassContainer(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Row(
|
||
children: [
|
||
AssetLogoWidget(
|
||
symbolOrName: symbol,
|
||
imageUrl: '/api/v1/logo/$isin',
|
||
size: 48,
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Text(symbol, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white)),
|
||
const SizedBox(width: 8),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(6),
|
||
color: isLong ? AppTheme.primaryEmerald.withValues(alpha: 0.2) : AppTheme.accentRed.withValues(alpha: 0.2),
|
||
),
|
||
child: Text(
|
||
isLong ? 'LONG' : 'SHORT',
|
||
style: TextStyle(
|
||
color: isLong ? AppTheme.primaryEmerald : AppTheme.accentRed,
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 11,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(isin, style: TextStyle(color: AppTheme.textMuted, fontSize: 12)),
|
||
],
|
||
),
|
||
),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(12),
|
||
color: AppTheme.primaryEmerald.withValues(alpha: 0.15),
|
||
border: Border.all(color: AppTheme.primaryEmerald, width: 1.5),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
Text('${score.toStringAsFixed(0)} Pkt', style: TextStyle(color: AppTheme.primaryEmerald, fontWeight: FontWeight.bold, fontSize: 16)),
|
||
Text('Score', style: TextStyle(color: AppTheme.textMuted, fontSize: 9)),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// AI Thesis & Catalysts Card
|
||
GlassContainer(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.psychology, size: 18, color: Colors.purpleAccent),
|
||
SizedBox(width: 8),
|
||
Text('KI-Guardian Thesis & Validierung', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white)),
|
||
],
|
||
),
|
||
const SizedBox(height: 10),
|
||
if (aiValidation != null)
|
||
Container(
|
||
margin: const EdgeInsets.only(bottom: 10),
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(8),
|
||
color: isRuleBasedApproval
|
||
? Colors.amber.withValues(alpha: 0.12)
|
||
: AppTheme.primaryEmerald.withValues(alpha: 0.12),
|
||
border: Border.all(color: isRuleBasedApproval ? Colors.amber : AppTheme.primaryEmerald),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(
|
||
isRuleBasedApproval ? Icons.rule_outlined : Icons.smart_toy_outlined,
|
||
size: 14,
|
||
color: isRuleBasedApproval ? Colors.amber : AppTheme.primaryEmerald,
|
||
),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
isRuleBasedApproval
|
||
? 'Regelbasierte Freigabe – keine KI-Bewertung durchgeführt'
|
||
: (aiConfidence != null
|
||
? 'KI-validiert · Konfidenz ${(aiConfidence * 100).toStringAsFixed(0)}%'
|
||
: 'KI-validiert · Konfidenz nicht verfügbar'),
|
||
style: TextStyle(
|
||
color: isRuleBasedApproval ? Colors.amber : AppTheme.primaryEmerald,
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
if (!hasAiThesisContent)
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Icon(Icons.info_outline, size: 16, color: AppTheme.textMuted),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
aiValidation == null
|
||
? 'Keine KI-Validierung für diesen Vorschlag verfügbar.'
|
||
: 'Keine weiteren Details zur Freigabe hinterlegt.',
|
||
style: TextStyle(color: AppTheme.textMuted, fontSize: 13, height: 1.4),
|
||
),
|
||
),
|
||
],
|
||
)
|
||
else ...[
|
||
Text(aiValidation.thesisSummary, style: const TextStyle(color: Colors.white70, fontSize: 13, height: 1.4)),
|
||
if (aiValidation.keyCatalysts.isNotEmpty) ...[
|
||
const SizedBox(height: 12),
|
||
Text('Katalysatoren & Stärken ($strategyKey @ €${entryPrice.toStringAsFixed(2)}):', style: TextStyle(color: AppTheme.primaryEmerald, fontSize: 12, fontWeight: FontWeight.bold)),
|
||
const SizedBox(height: 4),
|
||
...aiValidation.keyCatalysts.map((c) => Padding(
|
||
padding: const EdgeInsets.only(bottom: 2),
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.check_circle, size: 12, color: AppTheme.primaryEmerald),
|
||
const SizedBox(width: 6),
|
||
Expanded(child: Text(c, style: TextStyle(color: AppTheme.textMuted, fontSize: 12))),
|
||
],
|
||
),
|
||
)),
|
||
],
|
||
if (aiValidation.identifiedRisks.isNotEmpty) ...[
|
||
const SizedBox(height: 12),
|
||
const Text('Identifizierte Risiken:', style: TextStyle(color: Colors.amber, fontSize: 12, fontWeight: FontWeight.bold)),
|
||
const SizedBox(height: 4),
|
||
...aiValidation.identifiedRisks.map((r) => Padding(
|
||
padding: const EdgeInsets.only(bottom: 2),
|
||
child: Row(
|
||
children: [
|
||
const Icon(Icons.warning_amber, size: 12, color: Colors.amber),
|
||
const SizedBox(width: 6),
|
||
Expanded(child: Text(r, style: TextStyle(color: AppTheme.textMuted, fontSize: 12))),
|
||
],
|
||
),
|
||
)),
|
||
],
|
||
],
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// Knock-Out Derivative & Safety Buffer Card
|
||
GlassContainer(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Row(
|
||
children: [
|
||
Icon(Icons.shield_outlined, size: 18, color: Colors.cyanAccent),
|
||
SizedBox(width: 8),
|
||
Text('Optimaler Knock-Out Schein', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white)),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
if (!hasDerivative)
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Icon(Icons.info_outline, size: 16, color: AppTheme.textMuted),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Text(
|
||
'Kein passendes Knock-Out Produkt für diesen Vorschlag gefunden.',
|
||
style: TextStyle(color: AppTheme.textMuted, fontSize: 13, height: 1.4),
|
||
),
|
||
),
|
||
],
|
||
)
|
||
else ...[
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
_buildDetailCol('Emittent', deriv.issuer.isNotEmpty ? deriv.issuer : '–'),
|
||
_buildDetailCol('Hebel', '${deriv.leverage.toStringAsFixed(1)}x'),
|
||
_buildDetailCol('KO-Barriere', '€${deriv.barrier.toStringAsFixed(2)}'),
|
||
_buildDetailCol('Sicherheitspuffer', '${deriv.safetyBufferPercent.toStringAsFixed(1)}%'),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
LinearProgressIndicator(
|
||
value: (deriv.safetyBufferPercent / 20.0).clamp(0.0, 1.0),
|
||
backgroundColor: Colors.white10,
|
||
valueColor: AlwaysStoppedAnimation<Color>(deriv.safetyBufferPercent >= 5.0 ? AppTheme.primaryEmerald : Colors.amber),
|
||
minHeight: 6,
|
||
borderRadius: BorderRadius.circular(3),
|
||
),
|
||
const SizedBox(height: 6),
|
||
Text(
|
||
'Barriere liegt ${deriv.safetyBufferPercent.toStringAsFixed(1)}% unter dem Chart Stop-Loss (€${invalidationPrice.toStringAsFixed(2)}).',
|
||
style: TextStyle(color: AppTheme.textMuted, fontSize: 10),
|
||
),
|
||
const SizedBox(height: 6),
|
||
Text(
|
||
deriv.derivativeWkn != null ? 'WKN: ${deriv.derivativeWkn}' : 'WKN: nicht verfügbar',
|
||
style: TextStyle(
|
||
color: AppTheme.textMuted,
|
||
fontSize: 10,
|
||
fontStyle: deriv.derivativeWkn != null ? FontStyle.normal : FontStyle.italic,
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 24),
|
||
|
||
// Action Buttons
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: ElevatedButton.icon(
|
||
onPressed: onExecuteBot,
|
||
icon: const Icon(Icons.smart_toy_outlined),
|
||
label: const Text('An Bot übergeben'),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: AppTheme.primaryEmerald,
|
||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: OutlinedButton.icon(
|
||
onPressed: onManualTrade,
|
||
icon: const Icon(Icons.touch_app_outlined),
|
||
label: const Text('Manuell eröffnen'),
|
||
style: OutlinedButton.styleFrom(
|
||
foregroundColor: Colors.white,
|
||
side: const BorderSide(color: Colors.white30),
|
||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 32),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildDetailCol(String label, String val) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 10)),
|
||
const SizedBox(height: 2),
|
||
Text(val, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13)),
|
||
],
|
||
);
|
||
}
|
||
}
|