@marcos_feitoza/personal-finance-frontend-feature-investments 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,624 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:intl/intl.dart';
3
+ import 'package:personal_finance_frontend_core_services/services/stock_service.dart';
4
+ import 'package:personal_finance_frontend_core_services/services/transaction_service.dart';
5
+ import 'package:personal_finance_frontend_core_ui/widgets/trade_form.dart';
6
+ import 'package:personal_finance_frontend_core_ui/widgets/dividend_log_form.dart';
7
+ import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
8
+ import 'package:provider/provider.dart';
9
+ import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
10
+
11
+ class InvestmentAccountScreen extends StatefulWidget {
12
+ final String accountName;
13
+ final bool showDividends;
14
+
15
+ const InvestmentAccountScreen(
16
+ {Key? key, required this.accountName, this.showDividends = true})
17
+ : super(key: key);
18
+
19
+ @override
20
+ _InvestmentAccountScreenState createState() =>
21
+ _InvestmentAccountScreenState();
22
+ }
23
+
24
+ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
25
+ final TransactionService _transactionService = TransactionService();
26
+ final StockService _stockService = StockService();
27
+
28
+ List<Map<String, dynamic>> _trades = [];
29
+ List<Map<String, dynamic>> _dividends = [];
30
+ List<Map<String, dynamic>> _assets = [];
31
+ List<Map<String, dynamic>> _portfolioSummary = [];
32
+ Map<String, double> _livePrices = {};
33
+ double _cashBalance = 0.0;
34
+ double _accountTotalValue = 0.0;
35
+ double _totalPortfolioBookCost = 0.0;
36
+ bool _isLoading = true;
37
+ bool _isFetchingPrices = false;
38
+ String? _selectedSymbolForFilter;
39
+ String? _token;
40
+
41
+ @override
42
+ void initState() {
43
+ super.initState();
44
+ WidgetsBinding.instance.addPostFrameCallback((_) {
45
+ final authProvider = Provider.of<AuthProvider>(context, listen: false);
46
+ setState(() {
47
+ _token = authProvider.token;
48
+ });
49
+ _fetchData();
50
+ });
51
+ }
52
+
53
+ String currencySymbol = r'$';
54
+
55
+ String _formatCurrency(double value) {
56
+ return '$currencySymbol${value.toStringAsFixed(2)}';
57
+ }
58
+
59
+ String _formatDuration(Duration duration) {
60
+ if (duration.isNegative) return '';
61
+ int days = duration.inDays;
62
+ int years = days ~/ 365;
63
+ days %= 365;
64
+ int months = days ~/ 30;
65
+ days %= 30;
66
+ List<String> parts = [];
67
+ if (years > 0) parts.add('${years}Y');
68
+ if (months > 0) parts.add('${months}M');
69
+ if (days > 0 || (years == 0 && months == 0)) parts.add('${days}D');
70
+ return parts.join(' ');
71
+ }
72
+
73
+ Future<void> _fetchData() async {
74
+ if (_token == null) return;
75
+ setState(() => _isLoading = true);
76
+ try {
77
+ final futures = <Future>[
78
+ _transactionService.getTrades(investmentAccount: widget.accountName, token: _token),
79
+ _transactionService.getAccountBalance(widget.accountName, token: _token),
80
+ _transactionService.getAssets(investmentAccount: widget.accountName, token: _token),
81
+ _transactionService.getTotalPortfolioBookCost(token: _token),
82
+ ];
83
+ if (widget.showDividends) {
84
+ futures.add(_transactionService.getDividends(
85
+ investmentAccount: widget.accountName, token: _token));
86
+ }
87
+ final results = await Future.wait(futures);
88
+
89
+ final trades = results[0] as List<Map<String, dynamic>>;
90
+ final balance = results[1] as double;
91
+ final assets = results[2] as List<Map<String, dynamic>>;
92
+ final totalPortfolioBookCost = results[3] as double;
93
+ final dividends = widget.showDividends
94
+ ? results[4] as List<Map<String, dynamic>>
95
+ : <Map<String, dynamic>>[];
96
+
97
+ final summaryData = _calculatePortfolioSummary(trades, dividends);
98
+
99
+ setState(() {
100
+ _trades = trades;
101
+ _dividends = dividends;
102
+ _cashBalance = balance;
103
+ _assets = assets;
104
+ _portfolioSummary = summaryData['summary'];
105
+ _accountTotalValue = summaryData['total_value'] + _cashBalance;
106
+ _totalPortfolioBookCost = totalPortfolioBookCost;
107
+ });
108
+
109
+ if (mounted) await _fetchLivePrices();
110
+ } catch (e) {
111
+ if (mounted)
112
+ ScaffoldMessenger.of(context)
113
+ .showSnackBar(SnackBar(content: Text('Error fetching data: $e')));
114
+ } finally {
115
+ if (mounted) setState(() => _isLoading = false);
116
+ }
117
+ }
118
+
119
+ Future<void> _fetchLivePrices() async {
120
+ if (_portfolioSummary.isEmpty || !mounted) return;
121
+ setState(() => _isFetchingPrices = true);
122
+
123
+ final symbolsToFetch = _portfolioSummary
124
+ .map((p) => p['symbol'] as String)
125
+ .where((s) => s.isNotEmpty)
126
+ .toList();
127
+
128
+ if (symbolsToFetch.isEmpty) {
129
+ setState(() => _isFetchingPrices = false);
130
+ return;
131
+ }
132
+
133
+ final livePrices = await _stockService.getLivePrices(symbolsToFetch);
134
+
135
+ if (!mounted) return;
136
+
137
+ double newTotalValue = _cashBalance;
138
+ for (final position in _portfolioSummary) {
139
+ final shares = double.parse(position['shares'].toString());
140
+ final livePrice = livePrices[position['symbol']];
141
+ if (livePrice != null) {
142
+ newTotalValue += shares * livePrice;
143
+ } else {
144
+ newTotalValue += (position['total_cost'] as num?)?.toDouble() ?? 0.0;
145
+ }
146
+ }
147
+
148
+ setState(() {
149
+ _livePrices = livePrices;
150
+ _accountTotalValue = newTotalValue;
151
+ _isFetchingPrices = false;
152
+ });
153
+ }
154
+
155
+ Map<String, dynamic> _calculatePortfolioSummary(
156
+ List<Map<String, dynamic>> trades, List<Map<String, dynamic>> dividends) {
157
+ Map<String, dynamic> summary = {};
158
+ double accountPortfolioBookCost = 0;
159
+
160
+ Map<String, double> dividendSummary = {};
161
+ if (widget.showDividends) {
162
+ for (var dividend in dividends) {
163
+ final asset = dividend['asset'];
164
+ if (asset != null && asset['symbol'] != null) {
165
+ String symbol = asset['symbol'];
166
+ double amount = double.parse(dividend['amount'].toString());
167
+ dividendSummary.update(symbol, (value) => value + amount,
168
+ ifAbsent: () => amount);
169
+ }
170
+ }
171
+ }
172
+
173
+ for (var trade in trades) {
174
+ final asset = trade['asset'];
175
+ if (asset == null || asset['symbol'] == null) continue;
176
+ String symbol = asset['symbol'];
177
+
178
+ double shares = double.parse(trade['shares'].toString());
179
+ double price = double.parse(trade['price'].toString());
180
+ String tradeType = trade['trade_type'];
181
+ DateTime tradeDate = DateTime.parse(trade['date']);
182
+
183
+ if (!summary.containsKey(symbol)) {
184
+ summary[symbol] = {
185
+ 'symbol': symbol,
186
+ 'name': asset['name'] ?? symbol,
187
+ 'industry': asset['industry'] ?? 'N/A',
188
+ 'shares': 0.0,
189
+ 'total_cost': 0.0,
190
+ 'total_dividends': dividendSummary[symbol] ?? 0.0,
191
+ 'first_trade_date': tradeDate,
192
+ 'last_activity_date': tradeDate,
193
+ };
194
+ } else {
195
+ if (tradeDate.isBefore(summary[symbol]['first_trade_date'])) {
196
+ summary[symbol]['first_trade_date'] = tradeDate;
197
+ }
198
+ if (tradeDate.isAfter(summary[symbol]['last_activity_date'])) {
199
+ summary[symbol]['last_activity_date'] = tradeDate;
200
+ }
201
+ }
202
+
203
+ if (tradeType == 'buy') {
204
+ summary[symbol]['shares'] += shares;
205
+ summary[symbol]['total_cost'] += shares * price;
206
+ } else if (tradeType == 'sell') {
207
+ double originalShares = summary[symbol]['shares'];
208
+ if (originalShares > 0) {
209
+ double avgPrice = summary[symbol]['total_cost'] / originalShares;
210
+ summary[symbol]['total_cost'] -= shares * avgPrice;
211
+ }
212
+ summary[symbol]['shares'] -= shares;
213
+ }
214
+ }
215
+
216
+ summary.removeWhere((key, value) => value['shares'] < 0.01);
217
+
218
+ accountPortfolioBookCost =
219
+ summary.values.fold(0.0, (sum, item) => sum + item['total_cost']);
220
+
221
+ List<Map<String, dynamic>> result = [];
222
+ summary.forEach((symbol, data) {
223
+ double shares = data['shares'];
224
+ double totalCost = data['total_cost'];
225
+ data['avg_price'] = (shares > 0) ? totalCost / shares : 0.0;
226
+ data['account_allocation'] = (accountPortfolioBookCost > 0)
227
+ ? (totalCost / accountPortfolioBookCost) * 100
228
+ : 0.0;
229
+ result.add(data);
230
+ });
231
+
232
+ return {'summary': result, 'total_value': accountPortfolioBookCost};
233
+ }
234
+
235
+ @override
236
+ Widget build(BuildContext context) {
237
+ return Scaffold(
238
+ appBar: AppBar(
239
+ title: Text('${widget.accountName} Portfolio'),
240
+ actions: [
241
+ Center(
242
+ child: Padding(
243
+ padding: const EdgeInsets.only(right: 16.0),
244
+ child: Column(
245
+ mainAxisAlignment: MainAxisAlignment.center,
246
+ crossAxisAlignment: CrossAxisAlignment.end,
247
+ children: [
248
+ Text(
249
+ 'Total Portfolio: ${_formatCurrency(_accountTotalValue)}',
250
+ style: const TextStyle(
251
+ fontSize: 16, fontWeight: FontWeight.bold)),
252
+ Text('Account Balance: ${_formatCurrency(_cashBalance)}',
253
+ style: const TextStyle(fontSize: 12)),
254
+ ],
255
+ ),
256
+ ),
257
+ ),
258
+ IconButton(
259
+ icon: const Icon(Icons.refresh),
260
+ onPressed: _fetchData,
261
+ tooltip: 'Refresh Data'),
262
+ ],
263
+ ),
264
+ body: _isLoading
265
+ ? const Center(child: CircularProgressIndicator())
266
+ : RefreshIndicator(
267
+ onRefresh: _fetchData,
268
+ child: SingleChildScrollView(
269
+ physics: const AlwaysScrollableScrollPhysics(),
270
+ padding: const EdgeInsets.all(16.0),
271
+ child: Column(
272
+ crossAxisAlignment: CrossAxisAlignment.stretch,
273
+ children: [
274
+ Row(
275
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
276
+ children: [
277
+ Text('Portfolio Summary (Sintético)',
278
+ style: Theme.of(context).textTheme.titleLarge),
279
+ if (_isFetchingPrices)
280
+ const SizedBox(
281
+ height: 20,
282
+ width: 20,
283
+ child:
284
+ CircularProgressIndicator(strokeWidth: 2.0)),
285
+ ],
286
+ ),
287
+ _buildSinteticoTable(),
288
+ const SizedBox(height: 24),
289
+ Row(
290
+ crossAxisAlignment: CrossAxisAlignment.start,
291
+ children: [
292
+ Expanded(
293
+ child: TradeForm(
294
+ accountName: widget.accountName,
295
+ portfolioSummary: _portfolioSummary,
296
+ assets: _assets,
297
+ onTradeCreated: (_) => _fetchData(),
298
+ token: _token,
299
+ )),
300
+ if (widget.showDividends) ...[
301
+ const SizedBox(width: 16),
302
+ Expanded(
303
+ child: DividendLogForm(
304
+ investmentAccount: widget.accountName,
305
+ assets: _assets,
306
+ onDividendLogged: () => _fetchData(),
307
+ token: _token,
308
+ ),
309
+ ),
310
+ ],
311
+ ],
312
+ ),
313
+ const SizedBox(height: 24),
314
+ Text('Trade History (Analítico)',
315
+ style: Theme.of(context).textTheme.titleLarge),
316
+ const SizedBox(height: 8),
317
+ _buildAnaliticoFilter(),
318
+ const SizedBox(height: 8),
319
+ _buildAnaliticoTable(),
320
+ ],
321
+ ),
322
+ ),
323
+ ),
324
+ );
325
+ }
326
+
327
+ Widget _buildAnaliticoFilter() {
328
+ final symbols =
329
+ _portfolioSummary.map((p) => p['symbol'] as String).toSet().toList();
330
+ return Padding(
331
+ padding: const EdgeInsets.only(bottom: 8.0),
332
+ child: AppDropdown<String?>(
333
+ value: _selectedSymbolForFilter,
334
+ hint: 'Filter by Symbol',
335
+ items: [
336
+ const DropdownMenuItem<String?>(
337
+ value: null, child: Text('All Symbols')),
338
+ ...symbols.map((symbol) =>
339
+ DropdownMenuItem<String?>(value: symbol, child: Text(symbol))),
340
+ ],
341
+ onChanged: (String? newValue) =>
342
+ setState(() => _selectedSymbolForFilter = newValue),
343
+ ),
344
+ );
345
+ }
346
+
347
+ Widget _buildSinteticoTable() {
348
+ if (_portfolioSummary.isEmpty)
349
+ return const Center(
350
+ child: Padding(
351
+ padding: EdgeInsets.all(16.0),
352
+ child: Text('No positions held.')));
353
+
354
+ return SingleChildScrollView(
355
+ scrollDirection: Axis.horizontal,
356
+ child: DataTable(
357
+ columnSpacing: 24.0,
358
+ columns: [
359
+ const DataColumn(label: Text('Symbol')),
360
+ const DataColumn(label: Text('Age')),
361
+ const DataColumn(label: Text('Purchased')),
362
+ const DataColumn(label: Text('Shares')),
363
+ const DataColumn(label: Text('Avg Price')),
364
+ const DataColumn(label: Text('Live')),
365
+ const DataColumn(label: Text('Purchased')),
366
+ const DataColumn(label: Text('Allocation')),
367
+ const DataColumn(label: Text('Stocks Allocation')),
368
+ const DataColumn(label: Text('Return')),
369
+ const DataColumn(label: Text('% Return')),
370
+ const DataColumn(label: Text('Book Cost')),
371
+ if (widget.showDividends) ...[
372
+ const DataColumn(label: Text('Dividends')),
373
+ const DataColumn(label: Text('Return + Div')),
374
+ const DataColumn(label: Text('Book Cost + Div')),
375
+ const DataColumn(label: Text('% Return Div')),
376
+ ],
377
+ ],
378
+ rows: _portfolioSummary.map((position) {
379
+ final symbol = position['symbol'] as String;
380
+ final livePrice = _livePrices[symbol];
381
+ final shares = double.parse(position['shares'].toString());
382
+ final avgPrice = double.parse(position['avg_price'].toString());
383
+ final totalPurchased =
384
+ double.parse(position['total_cost'].toString());
385
+ final accountAllocation =
386
+ (position['account_allocation'] as num?)?.toDouble() ?? 0.0;
387
+ final stocksAllocation = (_totalPortfolioBookCost > 0)
388
+ ? (totalPurchased / _totalPortfolioBookCost) * 100
389
+ : 0.0;
390
+ final totalDividends =
391
+ (position['total_dividends'] as num?)?.toDouble() ?? 0.0;
392
+ final firstActivity = position['first_trade_date'] as DateTime?;
393
+ final positionAge = firstActivity != null
394
+ ? DateTime.now().difference(firstActivity)
395
+ : null;
396
+
397
+ double? totalReturnValue,
398
+ percentageReturn,
399
+ bookCost,
400
+ returnPlusDividend,
401
+ bookCostPlusDividend,
402
+ percentageReturnDividend;
403
+
404
+ if (livePrice != null && shares > 0) {
405
+ final currentMarketValue = livePrice * shares;
406
+ totalReturnValue = currentMarketValue - totalPurchased;
407
+ bookCost = currentMarketValue;
408
+ if (totalPurchased > 0)
409
+ percentageReturn = (totalReturnValue / totalPurchased) * 100;
410
+ if (widget.showDividends) {
411
+ returnPlusDividend = totalReturnValue + totalDividends;
412
+ bookCostPlusDividend = bookCost + totalDividends;
413
+ if (totalPurchased > 0)
414
+ percentageReturnDividend =
415
+ (returnPlusDividend / totalPurchased) * 100;
416
+ }
417
+ }
418
+
419
+ final returnColor =
420
+ (totalReturnValue ?? 0) >= 0 ? Colors.green : Colors.red;
421
+ final returnWithDivColor =
422
+ (returnPlusDividend ?? 0) >= 0 ? Colors.green : Colors.red;
423
+
424
+ return DataRow(cells: [
425
+ DataCell(Text(symbol)),
426
+ DataCell(Text(
427
+ positionAge != null ? _formatDuration(positionAge) : 'N/A')),
428
+ DataCell(Text(position['last_activity_date'] != null
429
+ ? DateFormat('yyyy-MM-dd')
430
+ .format(position['last_activity_date'])
431
+ : 'N/A')),
432
+ DataCell(Text(shares.toStringAsFixed(4))),
433
+ DataCell(Text(_formatCurrency(avgPrice))),
434
+ DataCell(livePrice != null
435
+ ? Text(_formatCurrency(livePrice))
436
+ : const Text('N/A')),
437
+ DataCell(Text(_formatCurrency(totalPurchased))),
438
+ DataCell(Text('${accountAllocation.toStringAsFixed(2)}%')),
439
+ DataCell(Text('${stocksAllocation.toStringAsFixed(2)}%')),
440
+ DataCell(totalReturnValue != null
441
+ ? Text(_formatCurrency(totalReturnValue),
442
+ style: TextStyle(color: returnColor))
443
+ : const Text('N/A')),
444
+ DataCell(percentageReturn != null
445
+ ? Text('${percentageReturn.toStringAsFixed(2)}%',
446
+ style: TextStyle(color: returnColor))
447
+ : const Text('N/A')),
448
+ DataCell(bookCost != null
449
+ ? Text(_formatCurrency(bookCost))
450
+ : const Text('N/A')),
451
+ if (widget.showDividends) ...[
452
+ DataCell(Text(_formatCurrency(totalDividends))),
453
+ DataCell(returnPlusDividend != null
454
+ ? Text(_formatCurrency(returnPlusDividend),
455
+ style: TextStyle(color: returnWithDivColor))
456
+ : const Text('N/A')),
457
+ DataCell(bookCostPlusDividend != null
458
+ ? Text(_formatCurrency(bookCostPlusDividend))
459
+ : const Text('N/A')),
460
+ DataCell(percentageReturnDividend != null
461
+ ? Text('${percentageReturnDividend.toStringAsFixed(2)}%',
462
+ style: TextStyle(color: returnWithDivColor))
463
+ : const Text('N/A')),
464
+ ]
465
+ ]);
466
+ }).toList(),
467
+ ),
468
+ );
469
+ }
470
+
471
+ Widget _buildAnaliticoTable() {
472
+ if (_trades.isEmpty)
473
+ return const Center(
474
+ child: Padding(
475
+ padding: EdgeInsets.all(16.0), child: Text('No trades found.')));
476
+
477
+ final List<Map<String, dynamic>> filteredTrades =
478
+ _selectedSymbolForFilter == null
479
+ ? _trades
480
+ : _trades
481
+ .where((trade) =>
482
+ trade['asset']?['symbol'] == _selectedSymbolForFilter)
483
+ .toList();
484
+
485
+ if (filteredTrades.isEmpty)
486
+ return const Center(
487
+ child: Padding(
488
+ padding: EdgeInsets.all(16.0),
489
+ child: Text('No trades match the selected symbol.')));
490
+
491
+ final sortedTrades = List<Map<String, dynamic>>.from(filteredTrades)
492
+ ..sort((a, b) {
493
+ final idA = int.tryParse(a['id']?.toString() ?? '');
494
+ final idB = int.tryParse(b['id']?.toString() ?? '');
495
+ if (idA != null && idB != null) return idB.compareTo(idA);
496
+ try {
497
+ return DateTime.parse(b['date'] as String)
498
+ .compareTo(DateTime.parse(a['date'] as String));
499
+ } catch (e) {
500
+ return 0;
501
+ }
502
+ });
503
+
504
+ return DataTable(
505
+ columns: const [
506
+ DataColumn(label: Text('Date')),
507
+ DataColumn(label: Text('Trade Age')),
508
+ DataColumn(label: Text('Symbol')),
509
+ DataColumn(label: Text('Type')),
510
+ DataColumn(label: Text('Shares')),
511
+ DataColumn(label: Text('Price')),
512
+ DataColumn(label: Text('Live')),
513
+ DataColumn(label: Text('Total')),
514
+ DataColumn(label: Text('Return')),
515
+ DataColumn(label: Text('% Return')),
516
+ DataColumn(label: Text('Book Cost')),
517
+ DataColumn(label: Text('Actions')), // New column for actions
518
+ ],
519
+ rows: sortedTrades.map((trade) {
520
+ final double shares = double.parse(trade['shares'].toString());
521
+ final double price = double.parse(trade['price'].toString());
522
+ final double total = shares * price;
523
+ final tradeDate = DateTime.parse(trade['date'] as String);
524
+ final tradeAge = DateTime.now().difference(tradeDate);
525
+ final symbol = trade['asset']?['symbol'] ?? '';
526
+ final livePrice = _livePrices[symbol];
527
+ final int tradeId = trade['id'] as int; // Get trade ID
528
+
529
+ double? tradeReturnValue, tradePercentageReturn, tradeBookCost;
530
+
531
+ if (livePrice != null && shares > 0 && trade['trade_type'] == 'buy') {
532
+ final currentMarketValue = livePrice * shares;
533
+ tradeReturnValue = currentMarketValue - total;
534
+ tradeBookCost = currentMarketValue;
535
+ if (total > 0)
536
+ tradePercentageReturn = (tradeReturnValue / total) * 100;
537
+ }
538
+
539
+ final returnColor =
540
+ (tradeReturnValue ?? 0) >= 0 ? Colors.green : Colors.red;
541
+
542
+ return DataRow(cells: [
543
+ DataCell(Text(DateFormat('yyyy-MM-dd').format(tradeDate))),
544
+ DataCell(Text(_formatDuration(tradeAge))),
545
+ DataCell(Text(symbol)),
546
+ DataCell(Text(trade['trade_type'] ?? 'N/A')),
547
+ DataCell(Text(shares.toString())),
548
+ DataCell(Text(_formatCurrency(price))),
549
+ DataCell(livePrice != null
550
+ ? Text(_formatCurrency(livePrice))
551
+ : const Text('N/A')),
552
+ DataCell(Text(_formatCurrency(total))),
553
+ DataCell(tradeReturnValue != null
554
+ ? Text(_formatCurrency(tradeReturnValue),
555
+ style: TextStyle(color: returnColor))
556
+ : const Text('N/A')),
557
+ DataCell(tradePercentageReturn != null
558
+ ? Text('${tradePercentageReturn.toStringAsFixed(2)}%',
559
+ style: TextStyle(color: returnColor))
560
+ : const Text('N/A')),
561
+ DataCell(tradeBookCost != null
562
+ ? Text(_formatCurrency(tradeBookCost),
563
+ style: TextStyle(color: returnColor))
564
+ : const Text('N/A')),
565
+ DataCell(
566
+ IconButton(
567
+ icon: const Icon(Icons.delete, color: Colors.red),
568
+ onPressed: () => _confirmAndDeleteTrade(tradeId),
569
+ tooltip: 'Delete Trade',
570
+ ),
571
+ ),
572
+ ]);
573
+ }).toList(),
574
+ );
575
+ }
576
+
577
+ Future<void> _confirmAndDeleteTrade(int tradeId) async {
578
+ final bool? confirm = await showDialog<bool>(
579
+ context: context,
580
+ builder: (BuildContext context) {
581
+ return AlertDialog(
582
+ title: const Text('Confirm Deletion'),
583
+ content: const Text('Are you sure you want to delete this trade? This action cannot be undone.'),
584
+ actions: <Widget>[
585
+ TextButton(
586
+ onPressed: () => Navigator.of(context).pop(false),
587
+ child: const Text('Cancel'),
588
+ ),
589
+ TextButton(
590
+ onPressed: () => Navigator.of(context).pop(true),
591
+ child: const Text('Delete'),
592
+ ),
593
+ ],
594
+ );
595
+ },
596
+ );
597
+
598
+ if (confirm == true) {
599
+ if (_token == null) {
600
+ ScaffoldMessenger.of(context).showSnackBar(
601
+ const SnackBar(content: Text('Authentication token not available.')),
602
+ );
603
+ return;
604
+ }
605
+ try {
606
+ final success = await _transactionService.deleteTrade(tradeId, token: _token);
607
+ if (success) {
608
+ ScaffoldMessenger.of(context).showSnackBar(
609
+ const SnackBar(content: Text('Trade deleted successfully.')),
610
+ );
611
+ _fetchData(); // Refresh data after deletion
612
+ } else {
613
+ ScaffoldMessenger.of(context).showSnackBar(
614
+ const SnackBar(content: Text('Failed to delete trade.')),
615
+ );
616
+ }
617
+ } catch (e) {
618
+ ScaffoldMessenger.of(context).showSnackBar(
619
+ SnackBar(content: Text('Error deleting trade: $e')),
620
+ );
621
+ }
622
+ }
623
+ }
624
+ }