Files

326 lines
15 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)),
],
);
}
}