feat(App): update Finlytic Flutter app UI and blocs
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../models/trade_acceptance_dto.dart';
|
||||
import '../models/trade_model.dart';
|
||||
|
||||
|
||||
class TradeAcceptanceDialog extends StatefulWidget {
|
||||
final TradeModel trade;
|
||||
final ThemePreset theme;
|
||||
final String userId;
|
||||
|
||||
const TradeAcceptanceDialog({
|
||||
super.key,
|
||||
required this.trade,
|
||||
required this.theme,
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TradeAcceptanceDialog> createState() => _TradeAcceptanceDialogState();
|
||||
}
|
||||
|
||||
class _TradeAcceptanceDialogState extends State<TradeAcceptanceDialog> {
|
||||
late TextEditingController _entryPriceCtrl;
|
||||
late TextEditingController _positionSizeCtrl;
|
||||
late TextEditingController _leverageCtrl;
|
||||
late TextEditingController _entryFeeCtrl;
|
||||
late TextEditingController _exitFeeCtrl;
|
||||
late TextEditingController _quantityCtrl;
|
||||
late TextEditingController _stopLossCtrl;
|
||||
late TextEditingController _takeProfitCtrl;
|
||||
late TextEditingController _notesCtrl;
|
||||
bool _isRecurring = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_entryPriceCtrl = TextEditingController(text: widget.trade.entryPrice.toStringAsFixed(2));
|
||||
_positionSizeCtrl = TextEditingController(text: '1000');
|
||||
_leverageCtrl = TextEditingController(text: (widget.trade.maxLeverage > 0 ? widget.trade.maxLeverage : 1).toStringAsFixed(0));
|
||||
_stopLossCtrl = TextEditingController(text: widget.trade.stopLoss.toStringAsFixed(2));
|
||||
_takeProfitCtrl = TextEditingController(text: widget.trade.takeProfit.toStringAsFixed(2));
|
||||
_notesCtrl = TextEditingController();
|
||||
_entryFeeCtrl = TextEditingController(text: '0.00');
|
||||
_exitFeeCtrl = TextEditingController(text: '0.00');
|
||||
double price = widget.trade.entryPrice;
|
||||
_quantityCtrl = TextEditingController(text: (1000 / (price > 0 ? price : 1)).toStringAsFixed(4));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_entryPriceCtrl.dispose();
|
||||
_positionSizeCtrl.dispose();
|
||||
_leverageCtrl.dispose();
|
||||
_stopLossCtrl.dispose();
|
||||
_takeProfitCtrl.dispose();
|
||||
_notesCtrl.dispose();
|
||||
_entryFeeCtrl.dispose();
|
||||
_exitFeeCtrl.dispose();
|
||||
_quantityCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double? _parseNum(String text) {
|
||||
final cleaned = text.replaceAll(',', '.').trim();
|
||||
return double.tryParse(cleaned);
|
||||
}
|
||||
|
||||
void _recalcQuantity() {
|
||||
double posSize = _parseNum(_positionSizeCtrl.text) ?? 0;
|
||||
double price = _parseNum(_entryPriceCtrl.text) ?? 0;
|
||||
if (price > 0) {
|
||||
_quantityCtrl.text = (posSize / price).toStringAsFixed(4);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
insetPadding: const EdgeInsets.all(16),
|
||||
child: Container(
|
||||
width: 450,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.theme.cardSurface,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: widget.theme.glassBorder, width: 1),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.add_shopping_cart, color: widget.theme.primaryColor, size: 28),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Trade Akzeptieren - ${widget.trade.symbol}',
|
||||
style: TextStyle(
|
||||
color: widget.theme.textPrimary,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: Icon(Icons.close, color: widget.theme.textMuted),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildInputField('Kaufkurs / Entry Price', _entryPriceCtrl, Icons.attach_money, onChanged: (_) => _recalcQuantity()),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildInputField('Kapital (Position Size)', _positionSizeCtrl, Icons.account_balance_wallet, onChanged: (_) => _recalcQuantity())),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: _buildInputField('Stückzahl (Quantity)', _quantityCtrl, Icons.format_list_numbered)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildInputField('Stop Loss', _stopLossCtrl, Icons.block)),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: _buildInputField('Take Profit', _takeProfitCtrl, Icons.trending_up)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildInputField('Hebel (Leverage)', _leverageCtrl, Icons.speed)),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: CheckboxListTile(
|
||||
title: Text('Sparplan', style: TextStyle(color: widget.theme.textPrimary, fontSize: 14)),
|
||||
value: _isRecurring,
|
||||
activeColor: widget.theme.primaryColor,
|
||||
checkColor: Colors.white,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_isRecurring = val ?? false;
|
||||
});
|
||||
},
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildInputField('Notizen', _notesCtrl, Icons.edit),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
final dto = TradeAcceptanceDto(
|
||||
userId: widget.userId,
|
||||
tradeId: widget.trade.id,
|
||||
analysisId: widget.trade.analysisId,
|
||||
isin: widget.trade.isin,
|
||||
symbol: widget.trade.symbol,
|
||||
actualEntryPrice: _parseNum(_entryPriceCtrl.text),
|
||||
positionSize: _parseNum(_positionSizeCtrl.text),
|
||||
leverageUsed: _parseNum(_leverageCtrl.text) ?? 1.0,
|
||||
entryFee: _parseNum(_entryFeeCtrl.text) ?? 0.0,
|
||||
exitFee: _parseNum(_exitFeeCtrl.text) ?? 0.0,
|
||||
quantity: _parseNum(_quantityCtrl.text),
|
||||
isRecurring: _isRecurring,
|
||||
executionTimestamp: DateTime.now().toUtc(),
|
||||
signalType: widget.trade.signalType,
|
||||
entryPrice: widget.trade.entryPrice,
|
||||
stopLoss: widget.trade.stopLoss,
|
||||
takeProfit: widget.trade.takeProfit,
|
||||
instrumentType: widget.trade.instrumentType,
|
||||
timeframe: widget.trade.timeframe,
|
||||
reasoning: widget.trade.reasoning,
|
||||
);
|
||||
Navigator.of(context).pop(dto);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: widget.theme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: const Text('Jetzt Ausführen', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInputField(String label, TextEditingController controller, IconData icon, {Function(String)? onChanged}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: TextStyle(color: widget.theme.textSecondary, fontSize: 12)),
|
||||
const SizedBox(height: 6),
|
||||
TextField(
|
||||
controller: controller,
|
||||
style: TextStyle(color: widget.theme.textPrimary),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(icon, color: widget.theme.textMuted, size: 18),
|
||||
filled: true,
|
||||
fillColor: widget.theme.darkBackground.withValues(alpha: 0.5),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user