@marcos_feitoza/personal-finance-frontend-feature-investments 1.0.1 → 1.1.1

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [1.1.1](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/compare/v1.1.0...v1.1.1) (2025-12-02)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add AppDialogs.showDeleteConfirmationDialog ([29c41f8](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/29c41f80530906111efe5fac1c3c493c78a29a62))
7
+
8
+ # [1.1.0](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/compare/v1.0.1...v1.1.0) (2025-11-28)
9
+
10
+
11
+ ### Features
12
+
13
+ * add confirmation dialog for deletion operations ([2972ee1](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/2972ee1e7abf6bb1c183554867006ccd1a9a0909))
14
+ * update readme.me ([56c5303](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/56c5303681a6110d25d600b11047be38b52f0cad))
15
+
1
16
  ## [1.0.1](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/compare/v1.0.0...v1.0.1) (2025-11-27)
2
17
 
3
18
 
package/README.md CHANGED
@@ -1,39 +1,34 @@
1
- <!--
2
- This README describes the package. If you publish this package to pub.dev,
3
- this README's contents appear on the landing page for your package.
1
+ # Feature: Investimentos (Frontend Flutter)
4
2
 
5
- For information about how to write a good package README, see the guide for
6
- [writing package pages](https://dart.dev/tools/pub/writing-package-pages).
3
+ Este pacote contém a implementação das telas para visualização e gerenciamento de todas as contas de investimento na aplicação Personal Finance Frontend.
7
4
 
8
- For general information about developing packages, see the Dart guide for
9
- [creating packages](https://dart.dev/guides/libraries/create-packages)
10
- and the Flutter guide for
11
- [developing packages and plugins](https://flutter.dev/to/develop-packages).
12
- -->
5
+ ## Propósito
13
6
 
14
- TODO: Put a short description of the package here that helps potential users
15
- know whether this package might be useful for them.
7
+ O objetivo deste pacote é fornecer ao usuário uma interface completa para acompanhar a performance de seus investimentos, registrar novas transações (compra/venda, dividendos) e gerenciar suas contribuições RRSP.
16
8
 
17
- ## Features
9
+ ## Conteúdo Principal
18
10
 
19
- TODO: List what your package can do. Maybe include images, gifs, or videos.
11
+ - **`lib/screens/investment_account_screen.dart`**: Tela genérica para contas de investimento (ex: TFSA, Non-Registered, RRSP Wealthsimple), mostrando resumo e histórico de trades.
12
+ - **`lib/screens/crypto_account_screen.dart`**: Tela específica para contas de criptomoedas, mostrando resumo e histórico de trades de cripto.
13
+ - **`lib/screens/rrsp_sun_life_screen.dart`**: Tela específica para a conta RRSP Sun Life, focando em histórico de contribuições.
20
14
 
21
- ## Getting started
15
+ ---
22
16
 
23
- TODO: List prerequisites and provide or point to information on how to
24
- start using the package.
17
+ ## Como Usar (Instalação como Dependência)
25
18
 
26
- ## Usage
19
+ Este pacote é uma dependência local para a aplicação principal (`personal-finance-frontend`).
27
20
 
28
- TODO: Include short and useful examples for package users. Add longer examples
29
- to `/example` folder.
21
+ No `pubspec.yaml` da aplicação principal, adicione a seguinte linha em `dependencies`:
30
22
 
31
- ```dart
32
- const like = 'sample';
23
+ ```yaml
24
+ personal_finance_frontend_feature_investments:
25
+ path: ../personal-finance-frontend-feature-investments
33
26
  ```
34
27
 
35
- ## Additional information
28
+ ## Features
36
29
 
37
- TODO: Tell users more about the package: where to find more information, how to
38
- contribute to the package, how to file issues, what response they can expect
39
- from the package authors, and more.
30
+ - **Resumo do Portfólio (Sintético)**: Visão geral da performance dos ativos em cada conta de investimento, com métricas como "Book Cost", "Market Value", "Unrealized P/L", "Total Return" e percentuais de alocação.
31
+ - **Histórico de Trades (Analítico)**: Listagem detalhada de todas as operações de compra e venda por conta.
32
+ - **Registro de Dividendos**: Formulário para registrar dividendos recebidos.
33
+ - **Acompanhamento de Contribuições RRSP**: Histórico e resumo das contribuições para contas RRSP.
34
+ - **Exclusão Suave (Soft Delete)**: Funcionalidade para deletar trades e contribuições de forma segura, marcando-os como excluídos em vez de remover permanentemente.
@@ -6,6 +6,7 @@ import 'package:personal_finance_frontend_core_ui/widgets/crypto_trade_form.dart
6
6
  import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
7
7
  import 'package:provider/provider.dart';
8
8
  import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
9
+ import 'package:personal_finance_frontend_core_ui/utils/app_dialogs.dart';
9
10
 
10
11
  class CryptoAccountScreen extends StatefulWidget {
11
12
  final String accountName;
@@ -58,7 +59,7 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
58
59
  _transactionService.getTrades(investmentAccount: widget.accountName, token: _token),
59
60
  _transactionService.getAccountBalance(widget.accountName, token: _token),
60
61
  _transactionService.getAssets(investmentAccount: widget.accountName, token: _token),
61
- _transactionService.getTotalPortfolioBookCost(token: _token),
62
+ _transactionService.getTotalPortfolioBookCost(token: _token), // Fetch total cost
62
63
  ];
63
64
  final results = await Future.wait(futures);
64
65
 
@@ -67,21 +68,18 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
67
68
  final assets = results[2] as List<Map<String, dynamic>>;
68
69
  final totalPortfolioBookCost = results[3] as double;
69
70
 
70
- // Initial calculation without live prices
71
- final initialSummaryData = _calculatePortfolioSummary(trades, totalPortfolioBookCost);
71
+ final summaryData = _calculatePortfolioSummary(trades, totalPortfolioBookCost);
72
72
 
73
73
  setState(() {
74
74
  _trades = trades;
75
75
  _cashBalance = balance;
76
76
  _assets = assets;
77
- _portfolioSummary = initialSummaryData['summary'];
78
- _accountTotalValue = initialSummaryData['total_value'] + _cashBalance;
77
+ _portfolioSummary = summaryData['summary'];
78
+ _accountTotalValue = summaryData['total_value'] + _cashBalance;
79
79
  _totalPortfolioBookCost = totalPortfolioBookCost;
80
80
  });
81
81
 
82
- // Fetch live prices and recalculate summary
83
- if (mounted) await _fetchLivePricesAndRecalculate();
84
-
82
+ if (mounted) await _fetchLivePrices();
85
83
  } catch (e) {
86
84
  if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error fetching data: $e')));
87
85
  } finally {
@@ -89,7 +87,7 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
89
87
  }
90
88
  }
91
89
 
92
- Future<void> _fetchLivePricesAndRecalculate() async {
90
+ Future<void> _fetchLivePrices() async {
93
91
  if (_portfolioSummary.isEmpty || !mounted) return;
94
92
  setState(() => _isFetchingPrices = true);
95
93
 
@@ -104,22 +102,28 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
104
102
 
105
103
  if (!mounted) return;
106
104
 
107
- // Recalculate summary with live prices
108
- final summaryDataWithLivePrices = _calculatePortfolioSummary(_trades, _totalPortfolioBookCost, livePrices: livePrices);
109
-
110
- double newTotalMarketValue = summaryDataWithLivePrices['total_value'];
105
+ double newTotalValue = _cashBalance;
106
+ for (final position in _portfolioSummary) {
107
+ final shares = double.parse(position['shares'].toString());
108
+ final idForLookup = (position['id_crypto'] as String? ?? position['symbol'] as String).toLowerCase();
109
+ final livePrice = livePrices[idForLookup];
110
+ if (livePrice != null) {
111
+ newTotalValue += shares * livePrice;
112
+ } else {
113
+ newTotalValue += (position['total_cost'] as num?)?.toDouble() ?? 0.0;
114
+ }
115
+ }
111
116
 
112
117
  setState(() {
113
118
  _livePrices = livePrices;
114
- _portfolioSummary = summaryDataWithLivePrices['summary'];
115
- _accountTotalValue = newTotalMarketValue + _cashBalance;
119
+ _accountTotalValue = newTotalValue;
116
120
  _isFetchingPrices = false;
117
121
  });
118
122
  }
119
123
 
120
- Map<String, dynamic> _calculatePortfolioSummary(List<Map<String, dynamic>> trades, double totalPortfolioBookCost, {Map<String, double>? livePrices}) {
124
+ Map<String, dynamic> _calculatePortfolioSummary(List<Map<String, dynamic>> trades, double totalPortfolioBookCost) {
121
125
  Map<String, dynamic> summary = {};
122
- livePrices ??= _livePrices; // Use state's live prices if not provided
126
+ double accountPortfolioBookCost = 0;
123
127
 
124
128
  for (var trade in trades) {
125
129
  final asset = trade['asset'];
@@ -137,50 +141,37 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
137
141
  'id_crypto': idCrypto,
138
142
  'name': asset['name'] ?? symbol,
139
143
  'shares': 0.0,
140
- 'book_cost': 0.0,
144
+ 'total_cost': 0.0,
141
145
  };
142
146
  }
143
147
 
144
148
  if (tradeType == 'buy') {
145
149
  summary[symbol]['shares'] += shares;
146
- summary[symbol]['book_cost'] += shares * price;
150
+ summary[symbol]['total_cost'] += shares * price;
147
151
  } else if (tradeType == 'sell') {
148
152
  double originalShares = summary[symbol]['shares'];
149
153
  if (originalShares > 0) {
150
- double avgPrice = summary[symbol]['book_cost'] / originalShares;
151
- summary[symbol]['book_cost'] -= shares * avgPrice;
154
+ double avgPrice = summary[symbol]['total_cost'] / originalShares;
155
+ summary[symbol]['total_cost'] -= shares * avgPrice;
152
156
  }
153
157
  summary[symbol]['shares'] -= shares;
154
158
  }
155
159
  }
156
160
 
157
161
  summary.removeWhere((key, value) => value['shares'] < 0.01);
162
+ accountPortfolioBookCost = summary.values.fold(0.0, (sum, item) => sum + item['total_cost']);
158
163
 
159
- double accountTotalBookCost = summary.values.fold(0.0, (sum, item) => sum + item['book_cost']);
160
-
161
164
  List<Map<String, dynamic>> result = [];
162
165
  summary.forEach((symbol, data) {
163
166
  double shares = data['shares'];
164
- double bookCost = data['book_cost'];
165
-
166
- data['avg_price'] = (shares > 0) ? bookCost / shares : 0.0;
167
- data['account_allocation_percent'] = (accountTotalBookCost > 0) ? (bookCost / accountTotalBookCost) * 100 : 0.0;
168
- data['portfolio_allocation_percent'] = (totalPortfolioBookCost > 0) ? (bookCost / totalPortfolioBookCost) * 100 : 0.0;
169
-
170
- final idForLookup = (data['id_crypto'] as String? ?? data['symbol'] as String).toLowerCase();
171
- final livePrice = livePrices![idForLookup];
172
- double marketValue = livePrice != null ? shares * livePrice : bookCost;
173
- double unrealizedPL = marketValue - bookCost;
174
-
175
- data['market_value'] = marketValue;
176
- data['unrealized_pl'] = unrealizedPL;
177
- data['percent_pl'] = (bookCost > 0) ? (unrealizedPL / bookCost) * 100 : 0.0;
178
-
167
+ double totalCost = data['total_cost'];
168
+ data['avg_price'] = (shares > 0) ? totalCost / shares : 0.0;
169
+ data['account_allocation'] = (accountPortfolioBookCost > 0) ? (totalCost / accountPortfolioBookCost) * 100 : 0.0;
170
+ data['stocks_allocation'] = (totalPortfolioBookCost > 0) ? (totalCost / totalPortfolioBookCost) * 100 : 0.0;
179
171
  result.add(data);
180
172
  });
181
173
 
182
- double accountTotalMarketValue = result.fold(0.0, (sum, item) => sum + item['market_value']);
183
- return {'summary': result, 'total_value': accountTotalMarketValue};
174
+ return {'summary': result, 'total_value': accountPortfolioBookCost};
184
175
  }
185
176
 
186
177
 
@@ -249,43 +240,48 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
249
240
  child: DataTable(
250
241
  columnSpacing: 24.0,
251
242
  columns: const [
243
+ DataColumn(label: Text('Name')),
252
244
  DataColumn(label: Text('Symbol')),
253
- DataColumn(label: Text('Shares')),
245
+ DataColumn(label: Text('Quantity')),
254
246
  DataColumn(label: Text('Avg Price')),
247
+ DataColumn(label: Text('Live')),
255
248
  DataColumn(label: Text('Book Cost')),
256
- DataColumn(label: Text('Live Price')),
257
- DataColumn(label: Text('Market Value')),
258
- DataColumn(label: Text('Unrealized P/L')),
259
- DataColumn(label: Text('% P/L')),
260
- DataColumn(label: Text('Account %')),
261
- DataColumn(label: Text('Portfolio %')),
249
+ DataColumn(label: Text('Allocation')),
250
+ DataColumn(label: Text('Stocks Allocation')),
251
+ DataColumn(label: Text('Return')),
252
+ DataColumn(label: Text('% Return')),
262
253
  ],
263
254
  rows: _portfolioSummary.map((position) {
264
- final String symbol = position['symbol'];
265
- final double shares = position['shares'];
266
- final double avgPrice = position['avg_price'];
267
- final double bookCost = position['book_cost'];
268
- final String idForLookup = (position['id_crypto'] as String? ?? symbol).toLowerCase();
269
- final double? livePrice = _livePrices[idForLookup];
270
- final double marketValue = position['market_value'];
271
- final double unrealizedPL = position['unrealized_pl'];
272
- final double percentPL = position['percent_pl'];
273
- final double accountAllocation = position['account_allocation_percent'];
274
- final double portfolioAllocation = position['portfolio_allocation_percent'];
275
-
276
- final plColor = (unrealizedPL >= 0) ? Colors.green : Colors.red;
255
+ final symbol = position['symbol'] as String;
256
+ final idForLookup = (position['id_crypto'] as String? ?? symbol).toLowerCase();
257
+ final livePrice = _livePrices[idForLookup];
258
+ final shares = double.parse(position['shares'].toString());
259
+ final avgPrice = double.parse(position['avg_price'].toString());
260
+ final totalPurchased = double.parse(position['total_cost'].toString());
261
+ final accountAllocation = (position['account_allocation'] as num?)?.toDouble() ?? 0.0;
262
+ final stocksAllocation = (position['stocks_allocation'] as num?)?.toDouble() ?? 0.0;
263
+
264
+ double? totalReturnValue, percentageReturn;
265
+
266
+ if (livePrice != null && shares > 0) {
267
+ final currentMarketValue = livePrice * shares;
268
+ totalReturnValue = currentMarketValue - totalPurchased;
269
+ if (totalPurchased > 0) percentageReturn = (totalReturnValue / totalPurchased) * 100;
270
+ }
271
+
272
+ final returnColor = (totalReturnValue ?? 0) >= 0 ? Colors.green : Colors.red;
277
273
 
278
274
  return DataRow(cells: [
275
+ DataCell(SizedBox(width: 150, child: Text(position['name'] as String, overflow: TextOverflow.ellipsis))),
279
276
  DataCell(Text(symbol)),
280
277
  DataCell(Text(shares.toStringAsFixed(6))),
281
278
  DataCell(Text(_formatCurrency(avgPrice))),
282
- DataCell(Text(_formatCurrency(bookCost))),
283
279
  DataCell(livePrice != null ? Text(_formatCurrency(livePrice)) : const Text('N/A')),
284
- DataCell(Text(_formatCurrency(marketValue))),
285
- DataCell(Text(_formatCurrency(unrealizedPL), style: TextStyle(color: plColor))),
286
- DataCell(Text('${percentPL.toStringAsFixed(2)}%', style: TextStyle(color: plColor))),
280
+ DataCell(Text(_formatCurrency(totalPurchased))),
287
281
  DataCell(Text('${accountAllocation.toStringAsFixed(2)}%')),
288
- DataCell(Text('${portfolioAllocation.toStringAsFixed(2)}%')),
282
+ DataCell(Text('${stocksAllocation.toStringAsFixed(2)}%')),
283
+ DataCell(totalReturnValue != null ? Text(_formatCurrency(totalReturnValue), style: TextStyle(color: returnColor)) : const Text('N/A')),
284
+ DataCell(percentageReturn != null ? Text('${percentageReturn.toStringAsFixed(2)}%', style: TextStyle(color: returnColor)) : const Text('N/A')),
289
285
  ]);
290
286
  }).toList(),
291
287
  ),
@@ -338,25 +334,7 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
338
334
  }
339
335
 
340
336
  Future<void> _confirmAndDeleteTrade(int tradeId) async {
341
- final bool? confirm = await showDialog<bool>(
342
- context: context,
343
- builder: (BuildContext context) {
344
- return AlertDialog(
345
- title: const Text('Confirm Deletion'),
346
- content: const Text('Are you sure you want to delete this trade? This action cannot be undone.'),
347
- actions: <Widget>[
348
- TextButton(
349
- onPressed: () => Navigator.of(context).pop(false),
350
- child: const Text('Cancel'),
351
- ),
352
- TextButton(
353
- onPressed: () => Navigator.of(context).pop(true),
354
- child: const Text('Delete'),
355
- ),
356
- ],
357
- );
358
- },
359
- );
337
+ final bool? confirm = await AppDialogs.showDeleteConfirmationDialog(context, 'this trade');
360
338
 
361
339
  if (confirm == true) {
362
340
  if (_token == null) {
@@ -7,6 +7,7 @@ import 'package:personal_finance_frontend_core_ui/widgets/dividend_log_form.dart
7
7
  import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
8
8
  import 'package:provider/provider.dart';
9
9
  import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
10
+ import 'package:personal_finance_frontend_core_ui/utils/app_dialogs.dart';
10
11
 
11
12
  class InvestmentAccountScreen extends StatefulWidget {
12
13
  final String accountName;
@@ -75,9 +76,12 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
75
76
  setState(() => _isLoading = true);
76
77
  try {
77
78
  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),
79
+ _transactionService.getTrades(
80
+ investmentAccount: widget.accountName, token: _token),
81
+ _transactionService.getAccountBalance(widget.accountName,
82
+ token: _token),
83
+ _transactionService.getAssets(
84
+ investmentAccount: widget.accountName, token: _token),
81
85
  _transactionService.getTotalPortfolioBookCost(token: _token),
82
86
  ];
83
87
  if (widget.showDividends) {
@@ -232,10 +236,12 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
232
236
  data['portfolio_allocation_percent'] = (_totalPortfolioBookCost > 0)
233
237
  ? (bookCost / _totalPortfolioBookCost) * 100
234
238
  : 0.0;
235
-
239
+
236
240
  // Calculate Market Value, Unrealized P/L, Total Return
237
241
  final livePrice = _livePrices[symbol];
238
- double marketValue = livePrice != null ? shares * livePrice : bookCost; // Fallback to bookCost if no live price
242
+ double marketValue = livePrice != null
243
+ ? shares * livePrice
244
+ : bookCost; // Fallback to bookCost if no live price
239
245
  double unrealizedPL = marketValue - bookCost;
240
246
  double totalReturn = unrealizedPL + totalDividends;
241
247
 
@@ -243,8 +249,10 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
243
249
  data['unrealized_pl'] = unrealizedPL;
244
250
  data['total_return'] = totalReturn;
245
251
 
246
- data['percent_unrealized_pl'] = (bookCost > 0) ? (unrealizedPL / bookCost) * 100 : 0.0;
247
- data['percent_total_return'] = (bookCost > 0) ? (totalReturn / bookCost) * 100 : 0.0;
252
+ data['percent_unrealized_pl'] =
253
+ (bookCost > 0) ? (unrealizedPL / bookCost) * 100 : 0.0;
254
+ data['percent_total_return'] =
255
+ (bookCost > 0) ? (totalReturn / bookCost) * 100 : 0.0;
248
256
 
249
257
  result.add(data);
250
258
  });
@@ -311,12 +319,12 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
311
319
  children: [
312
320
  Expanded(
313
321
  child: TradeForm(
314
- accountName: widget.accountName,
315
- portfolioSummary: _portfolioSummary,
316
- assets: _assets,
317
- onTradeCreated: (_) => _fetchData(),
318
- token: _token,
319
- )),
322
+ accountName: widget.accountName,
323
+ portfolioSummary: _portfolioSummary,
324
+ assets: _assets,
325
+ onTradeCreated: (_) => _fetchData(),
326
+ token: _token,
327
+ )),
320
328
  if (widget.showDividends) ...[
321
329
  const SizedBox(width: 16),
322
330
  Expanded(
@@ -400,16 +408,17 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
400
408
  final livePrice = _livePrices[symbol];
401
409
  final marketValue = position['market_value'] as double;
402
410
  final unrealizedPL = position['unrealized_pl'] as double;
403
- final percentUnrealizedPL = position['percent_unrealized_pl'] as double;
411
+ final percentUnrealizedPL =
412
+ position['percent_unrealized_pl'] as double;
404
413
  final totalDividends = position['total_dividends'] as double;
405
414
  final totalReturn = position['total_return'] as double;
406
415
  final percentTotalReturn = position['percent_total_return'] as double;
407
- final accountAllocationPercent = position['account_allocation_percent'] as double;
408
- final portfolioAllocationPercent = position['portfolio_allocation_percent'] as double;
409
-
416
+ final accountAllocationPercent =
417
+ position['account_allocation_percent'] as double;
418
+ final portfolioAllocationPercent =
419
+ position['portfolio_allocation_percent'] as double;
410
420
 
411
- final plColor =
412
- (unrealizedPL ?? 0) >= 0 ? Colors.green : Colors.red;
421
+ final plColor = (unrealizedPL ?? 0) >= 0 ? Colors.green : Colors.red;
413
422
  final totalReturnColor =
414
423
  (totalReturn ?? 0) >= 0 ? Colors.green : Colors.red;
415
424
 
@@ -422,12 +431,16 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
422
431
  ? Text(_formatCurrency(livePrice))
423
432
  : const Text('N/A')),
424
433
  DataCell(Text(_formatCurrency(marketValue))),
425
- DataCell(Text(_formatCurrency(unrealizedPL), style: TextStyle(color: plColor))),
426
- DataCell(Text('${percentUnrealizedPL.toStringAsFixed(2)}%', style: TextStyle(color: plColor))),
434
+ DataCell(Text(_formatCurrency(unrealizedPL),
435
+ style: TextStyle(color: plColor))),
436
+ DataCell(Text('${percentUnrealizedPL.toStringAsFixed(2)}%',
437
+ style: TextStyle(color: plColor))),
427
438
  if (widget.showDividends) ...[
428
439
  DataCell(Text(_formatCurrency(totalDividends))),
429
- DataCell(Text(_formatCurrency(totalReturn), style: TextStyle(color: totalReturnColor))),
430
- DataCell(Text('${percentTotalReturn.toStringAsFixed(2)}%', style: TextStyle(color: totalReturnColor))),
440
+ DataCell(Text(_formatCurrency(totalReturn),
441
+ style: TextStyle(color: totalReturnColor))),
442
+ DataCell(Text('${percentTotalReturn.toStringAsFixed(2)}%',
443
+ style: TextStyle(color: totalReturnColor))),
431
444
  ],
432
445
  DataCell(Text('${accountAllocationPercent.toStringAsFixed(2)}%')),
433
446
  DataCell(Text('${portfolioAllocationPercent.toStringAsFixed(2)}%')),
@@ -542,27 +555,10 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
542
555
  }).toList(),
543
556
  );
544
557
  }
545
-
558
+
546
559
  Future<void> _confirmAndDeleteTrade(int tradeId) async {
547
- final bool? confirm = await showDialog<bool>(
548
- context: context,
549
- builder: (BuildContext context) {
550
- return AlertDialog(
551
- title: const Text('Confirm Deletion'),
552
- content: const Text('Are you sure you want to delete this trade? This action cannot be undone.'),
553
- actions: <Widget>[
554
- TextButton(
555
- onPressed: () => Navigator.of(context).pop(false),
556
- child: const Text('Cancel'),
557
- ),
558
- TextButton(
559
- onPressed: () => Navigator.of(context).pop(true),
560
- child: const Text('Delete'),
561
- ),
562
- ],
563
- );
564
- },
565
- );
560
+ final bool? confirm =
561
+ await AppDialogs.showDeleteConfirmationDialog(context, 'this trade');
566
562
 
567
563
  if (confirm == true) {
568
564
  if (_token == null) {
@@ -572,7 +568,8 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
572
568
  return;
573
569
  }
574
570
  try {
575
- final success = await _transactionService.deleteTrade(tradeId, token: _token);
571
+ final success =
572
+ await _transactionService.deleteTrade(tradeId, token: _token);
576
573
  if (success) {
577
574
  ScaffoldMessenger.of(context).showSnackBar(
578
575
  const SnackBar(content: Text('Trade deleted successfully.')),
@@ -4,6 +4,7 @@ import 'package:personal_finance_frontend_core_services/services/transaction_ser
4
4
  import 'package:personal_finance_frontend_core_ui/widgets/rrsp_contribution_form.dart';
5
5
  import 'package:provider/provider.dart';
6
6
  import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
7
+ import 'package:personal_finance_frontend_core_ui/utils/app_dialogs.dart'; // Added import for AppDialogs
7
8
 
8
9
  class RrspSunLifeScreen extends StatefulWidget {
9
10
  const RrspSunLifeScreen({Key? key}) : super(key: key);
@@ -150,29 +151,32 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
150
151
  ),
151
152
  body: _isLoading
152
153
  ? const Center(child: CircularProgressIndicator())
153
- : SingleChildScrollView(
154
- padding: const EdgeInsets.all(16.0),
155
- child: Column(
156
- crossAxisAlignment: CrossAxisAlignment.stretch,
157
- children: [
158
- Text('Summary',
159
- style: Theme.of(context).textTheme.titleLarge),
160
- const SizedBox(height: 8),
161
- _buildSinteticoTable(),
162
- const SizedBox(height: 24),
163
- RrspContributionForm(
164
- investmentAccount: 'RRSP Sun Life',
165
- onContributionLogged: () => _fetchData(),
166
- token: _token,
167
- ),
168
- const SizedBox(height: 24),
169
- Text(
170
- 'Contribution History',
171
- style: Theme.of(context).textTheme.titleLarge,
172
- ),
173
- const SizedBox(height: 8),
174
- _buildContributionTable(),
175
- ],
154
+ : RefreshIndicator(
155
+ onRefresh: _fetchData,
156
+ child: SingleChildScrollView(
157
+ padding: const EdgeInsets.all(16.0),
158
+ child: Column(
159
+ crossAxisAlignment: CrossAxisAlignment.stretch,
160
+ children: [
161
+ Text('Summary',
162
+ style: Theme.of(context).textTheme.titleLarge),
163
+ const SizedBox(height: 8),
164
+ _buildSinteticoTable(),
165
+ const SizedBox(height: 24),
166
+ RrspContributionForm(
167
+ investmentAccount: 'RRSP Sun Life',
168
+ onContributionLogged: () => _fetchData(),
169
+ token: _token,
170
+ ),
171
+ const SizedBox(height: 24),
172
+ Text(
173
+ 'Contribution History',
174
+ style: Theme.of(context).textTheme.titleLarge,
175
+ ),
176
+ const SizedBox(height: 8),
177
+ _buildContributionTable(),
178
+ ],
179
+ ),
176
180
  ),
177
181
  ),
178
182
  );
@@ -290,24 +294,9 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
290
294
  }
291
295
 
292
296
  Future<void> _confirmAndDeleteContribution(int contributionId) async {
293
- final bool? confirm = await showDialog<bool>(
294
- context: context,
295
- builder: (BuildContext context) {
296
- return AlertDialog(
297
- title: const Text('Confirm Deletion'),
298
- content: const Text('Are you sure you want to delete this contribution? This action cannot be undone.'),
299
- actions: <Widget>[
300
- TextButton(
301
- onPressed: () => Navigator.of(context).pop(false),
302
- child: const Text('Cancel'),
303
- ),
304
- TextButton(
305
- onPressed: () => Navigator.of(context).pop(true),
306
- child: const Text('Delete'),
307
- ),
308
- ],
309
- );
310
- },
297
+ final bool? confirm = await AppDialogs.showDeleteConfirmationDialog(
298
+ context,
299
+ 'this contribution', // A generic string for the item being deleted
311
300
  );
312
301
 
313
302
  if (confirm == true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcos_feitoza/personal-finance-frontend-feature-investments",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },