@marcos_feitoza/personal-finance-frontend-core-ui 1.1.0 → 1.2.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.
- package/CHANGELOG.md +13 -0
- package/README.md +60 -1
- package/lib/personal_finance_frontend_core_ui.dart +5 -1
- package/lib/theme/app_colors.dart +37 -0
- package/lib/theme/app_spacing.dart +34 -0
- package/lib/theme/app_theme.dart +112 -0
- package/lib/utils/app_snackbars.dart +49 -0
- package/lib/widgets/crypto_trade_form.dart +5 -12
- package/lib/widgets/dividend_log_form.dart +3 -6
- package/lib/widgets/historical_data_table.dart +36 -0
- package/lib/widgets/rrsp_contribution_form.dart +4 -7
- package/lib/widgets/trade_form.dart +5 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# [1.2.0](https://github.com/MarcosOps/personal-finance-frontend-core-ui/compare/v1.1.0...v1.2.0) (2026-01-29)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* new SnackBar ([7c519e0](https://github.com/MarcosOps/personal-finance-frontend-core-ui/commit/7c519e06b53884863a32170015080860ddf836be))
|
|
7
|
+
* update SnackBar ([b0c3b71](https://github.com/MarcosOps/personal-finance-frontend-core-ui/commit/b0c3b71efa0a7dd855fed3561c0c40bca33b23ec))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* new AppTheme process ([7557114](https://github.com/MarcosOps/personal-finance-frontend-core-ui/commit/755711424e37a65f6891748c9d60940e1bbf2e80))
|
|
13
|
+
|
|
1
14
|
# [1.1.0](https://github.com/MarcosOps/personal-finance-frontend-core-ui/compare/v1.0.2...v1.1.0) (2025-11-28)
|
|
2
15
|
|
|
3
16
|
|
package/README.md
CHANGED
|
@@ -14,6 +14,65 @@ O objetivo deste pacote é centralizar e padronizar a aparência e o comportamen
|
|
|
14
14
|
- **`utils/currency_input_formatter.dart`**: Formatter para campos de entrada de moeda.
|
|
15
15
|
- **`utils/theme_notifier.dart`**: Provedor para gerenciar o tema da aplicação (claro/escuro).
|
|
16
16
|
|
|
17
|
+
## Tema (Fase 1 — centralização)
|
|
18
|
+
|
|
19
|
+
Para evitar que cada tela/feature package defina seus próprios estilos (cores, espaçamentos, bordas), o **tema global** foi centralizado no core-ui.
|
|
20
|
+
|
|
21
|
+
Arquivos:
|
|
22
|
+
|
|
23
|
+
- `lib/theme/app_theme.dart`
|
|
24
|
+
- Define `AppTheme.light` e `AppTheme.dark`.
|
|
25
|
+
- **O que controla:**
|
|
26
|
+
- `ColorScheme` (paleta de cores principal)
|
|
27
|
+
- `AppBarTheme` (cor do topo e cor do texto/ícones)
|
|
28
|
+
- `ElevatedButtonTheme` / `OutlinedButtonTheme` (tamanho e bordas padrão dos botões)
|
|
29
|
+
- `InputDecorationTheme` (bordas/padding padrão de `TextFormField`)
|
|
30
|
+
- `lib/theme/app_colors.dart`
|
|
31
|
+
- Tokens semânticos (ex.: `success`, `error`, `warning`) e `seed`.
|
|
32
|
+
- **O que controla:**
|
|
33
|
+
- cores usadas em feedback (SnackBars, highlights de sucesso/erro)
|
|
34
|
+
- seed do tema (mudança global do “tom” do app)
|
|
35
|
+
- `lib/theme/app_spacing.dart`
|
|
36
|
+
- Tokens de espaçamento (`xs/sm/md/lg/xl/...`).
|
|
37
|
+
- **O que controla:**
|
|
38
|
+
- valores de padding/margin padronizados
|
|
39
|
+
|
|
40
|
+
Uso no app shell (`personal-finance-frontend/lib/main.dart`):
|
|
41
|
+
|
|
42
|
+
```dart
|
|
43
|
+
import 'package:personal_finance_frontend_core_ui/theme/app_theme.dart';
|
|
44
|
+
|
|
45
|
+
MaterialApp(
|
|
46
|
+
theme: AppTheme.light,
|
|
47
|
+
darkTheme: AppTheme.dark,
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Regras recomendadas:
|
|
52
|
+
|
|
53
|
+
- Em telas/features: preferir `Theme.of(context).colorScheme`.
|
|
54
|
+
- Para cores semânticas (ex.: erro/sucesso), usar `AppColors`.
|
|
55
|
+
- Para componentes, preferir `widgets/app_widgets.dart`.
|
|
56
|
+
|
|
57
|
+
## SnackBars padronizados (Fase 2)
|
|
58
|
+
|
|
59
|
+
Para evitar SnackBars com cores/textos inconsistentes, use os helpers:
|
|
60
|
+
|
|
61
|
+
- `showErrorSnackBar(context, message)`
|
|
62
|
+
- `showSuccessSnackBar(context, message)`
|
|
63
|
+
- `showInfoSnackBar(context, message)`
|
|
64
|
+
|
|
65
|
+
Arquivo:
|
|
66
|
+
- `lib/utils/app_snackbars.dart`
|
|
67
|
+
|
|
68
|
+
Exemplo:
|
|
69
|
+
|
|
70
|
+
```dart
|
|
71
|
+
import 'package:personal_finance_frontend_core_ui/utils/app_snackbars.dart';
|
|
72
|
+
|
|
73
|
+
showErrorSnackBar(context, 'Something went wrong');
|
|
74
|
+
```
|
|
75
|
+
|
|
17
76
|
---
|
|
18
77
|
|
|
19
78
|
## Como Usar (Instalação como Dependência)
|
|
@@ -32,4 +91,4 @@ personal_finance_frontend_core_ui:
|
|
|
32
91
|
- **Widgets Reutilizáveis**: Componentes de UI com design e comportamento padronizados.
|
|
33
92
|
- **Diálogos Centralizados**: Funções para exibir diálogos consistentes em todo o aplicativo.
|
|
34
93
|
- **Logging Condicional**: Um logger que ajuda a manter o console limpo em produção, exibindo mensagens apenas em desenvolvimento.
|
|
35
|
-
- **Ferramentas de Formatação**: Utilitários para formatar entradas de usuário (ex: valores monetários).
|
|
94
|
+
- **Ferramentas de Formatação**: Utilitários para formatar entradas de usuário (ex: valores monetários).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
/// App-wide color tokens.
|
|
4
|
+
///
|
|
5
|
+
/// Why this file exists:
|
|
6
|
+
/// - In a modular Flutter app (multiple packages/features), colors tend to be
|
|
7
|
+
/// duplicated across screens and drift over time.
|
|
8
|
+
/// - By keeping "semantic" colors here (success/error/warning + seed), we can
|
|
9
|
+
/// change the look globally without hunting through many screens.
|
|
10
|
+
///
|
|
11
|
+
/// How to use:
|
|
12
|
+
/// - Prefer `Theme.of(context).colorScheme.*` for most UI.
|
|
13
|
+
/// - Use these tokens when the color is semantic (success/error), not arbitrary.
|
|
14
|
+
class AppColors {
|
|
15
|
+
AppColors._();
|
|
16
|
+
|
|
17
|
+
/// Seed used to generate the Material `ColorScheme`.
|
|
18
|
+
///
|
|
19
|
+
/// Changing this will change the app's primary/secondary tones.
|
|
20
|
+
static const Color seed = Colors.blueGrey;
|
|
21
|
+
|
|
22
|
+
/// Background colors (used as overrides in ThemeData).
|
|
23
|
+
///
|
|
24
|
+
/// Note: in the current setup, the light theme relies on Material defaults
|
|
25
|
+
/// (we don't override scaffoldBackgroundColor). If you want to set a custom
|
|
26
|
+
/// background, add a token here and wire it in `AppTheme.light`.
|
|
27
|
+
static const Color darkScaffoldBackground = Color(0xFF212121); // ~ Colors.grey[900]
|
|
28
|
+
static const Color darkCardBackground = Color(0xFF303030); // ~ Colors.grey[850]
|
|
29
|
+
static const Color darkTextFieldFill = Color(0xFF424242); // ~ Colors.grey[800]
|
|
30
|
+
|
|
31
|
+
/// Semantic colors.
|
|
32
|
+
///
|
|
33
|
+
/// These should be used for meaning (success/error) instead of random `Colors.*`.
|
|
34
|
+
static const Color success = Colors.green;
|
|
35
|
+
static const Color error = Colors.red;
|
|
36
|
+
static const Color warning = Colors.orange;
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// App-wide spacing tokens.
|
|
2
|
+
///
|
|
3
|
+
/// Why this file exists:
|
|
4
|
+
/// - Prevents each screen from inventing its own paddings/margins.
|
|
5
|
+
/// - Makes global layout adjustments easy.
|
|
6
|
+
///
|
|
7
|
+
/// Usage:
|
|
8
|
+
/// ```dart
|
|
9
|
+
/// Padding(
|
|
10
|
+
/// padding: const EdgeInsets.all(AppSpacing.lg),
|
|
11
|
+
/// child: ...,
|
|
12
|
+
/// )
|
|
13
|
+
/// ```
|
|
14
|
+
class AppSpacing {
|
|
15
|
+
AppSpacing._();
|
|
16
|
+
|
|
17
|
+
/// 4px base spacing.
|
|
18
|
+
static const double xs = 4;
|
|
19
|
+
|
|
20
|
+
/// 8px spacing.
|
|
21
|
+
static const double sm = 8;
|
|
22
|
+
|
|
23
|
+
/// 12px spacing.
|
|
24
|
+
static const double md = 12;
|
|
25
|
+
|
|
26
|
+
/// 16px spacing.
|
|
27
|
+
static const double lg = 16;
|
|
28
|
+
|
|
29
|
+
/// 20px spacing.
|
|
30
|
+
static const double xl = 20;
|
|
31
|
+
|
|
32
|
+
/// 24px spacing.
|
|
33
|
+
static const double xxl = 24;
|
|
34
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import 'app_colors.dart';
|
|
4
|
+
|
|
5
|
+
/// Centralized ThemeData for the whole app.
|
|
6
|
+
///
|
|
7
|
+
/// Why this file exists:
|
|
8
|
+
/// - The app is split into multiple Flutter packages (shell + feature packages).
|
|
9
|
+
/// - If each screen defines its own colors/fonts, UI drift happens.
|
|
10
|
+
/// - By centralizing ThemeData here, we keep a single source of truth.
|
|
11
|
+
///
|
|
12
|
+
/// How to use:
|
|
13
|
+
/// - In the app shell (`personal-finance-frontend/lib/main.dart`), set:
|
|
14
|
+
/// - `theme: AppTheme.light`
|
|
15
|
+
/// - `darkTheme: AppTheme.dark`
|
|
16
|
+
/// - Inside widgets, prefer `Theme.of(context)` and `ColorScheme`.
|
|
17
|
+
class AppTheme {
|
|
18
|
+
AppTheme._();
|
|
19
|
+
|
|
20
|
+
/// Light theme.
|
|
21
|
+
///
|
|
22
|
+
/// Key choices:
|
|
23
|
+
/// - `ColorScheme.fromSeed`: creates a consistent palette based on a single seed.
|
|
24
|
+
/// - `useMaterial3: true`: aligns the app to modern Material components.
|
|
25
|
+
static final ThemeData light = ThemeData(
|
|
26
|
+
colorScheme: ColorScheme.fromSeed(seedColor: AppColors.seed),
|
|
27
|
+
useMaterial3: true,
|
|
28
|
+
|
|
29
|
+
// AppBar (top bar)
|
|
30
|
+
// - backgroundColor: the bar background
|
|
31
|
+
// - foregroundColor: icon/text color on top of the bar
|
|
32
|
+
appBarTheme: const AppBarTheme(
|
|
33
|
+
centerTitle: true,
|
|
34
|
+
backgroundColor: AppColors.seed,
|
|
35
|
+
foregroundColor: Colors.white,
|
|
36
|
+
),
|
|
37
|
+
|
|
38
|
+
// Default style for elevated buttons.
|
|
39
|
+
// Affects buttons created without custom style.
|
|
40
|
+
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
41
|
+
style: ElevatedButton.styleFrom(
|
|
42
|
+
minimumSize: const Size.fromHeight(50),
|
|
43
|
+
shape: RoundedRectangleBorder(
|
|
44
|
+
borderRadius: BorderRadius.circular(8),
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
|
|
49
|
+
// Default style for outlined buttons.
|
|
50
|
+
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
51
|
+
style: OutlinedButton.styleFrom(
|
|
52
|
+
minimumSize: const Size.fromHeight(50),
|
|
53
|
+
shape: RoundedRectangleBorder(
|
|
54
|
+
borderRadius: BorderRadius.circular(8),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
|
|
59
|
+
// Default decoration for text fields.
|
|
60
|
+
// Controls border and padding used by TextFormField/InputDecoration.
|
|
61
|
+
inputDecorationTheme: const InputDecorationTheme(
|
|
62
|
+
border: OutlineInputBorder(),
|
|
63
|
+
contentPadding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
/// Dark theme.
|
|
68
|
+
///
|
|
69
|
+
/// Differences from light theme:
|
|
70
|
+
/// - `brightness: Brightness.dark` for proper Material defaults
|
|
71
|
+
/// - custom scaffold/card/text field background colors
|
|
72
|
+
static final ThemeData dark = ThemeData(
|
|
73
|
+
colorScheme: ColorScheme.fromSeed(
|
|
74
|
+
seedColor: AppColors.seed,
|
|
75
|
+
brightness: Brightness.dark,
|
|
76
|
+
),
|
|
77
|
+
useMaterial3: true,
|
|
78
|
+
appBarTheme: const AppBarTheme(
|
|
79
|
+
centerTitle: true,
|
|
80
|
+
backgroundColor: Colors.black,
|
|
81
|
+
foregroundColor: Colors.white,
|
|
82
|
+
),
|
|
83
|
+
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
84
|
+
style: ElevatedButton.styleFrom(
|
|
85
|
+
minimumSize: const Size.fromHeight(50),
|
|
86
|
+
shape: RoundedRectangleBorder(
|
|
87
|
+
borderRadius: BorderRadius.circular(8),
|
|
88
|
+
),
|
|
89
|
+
backgroundColor: Colors.blueGrey,
|
|
90
|
+
foregroundColor: Colors.white,
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
94
|
+
style: OutlinedButton.styleFrom(
|
|
95
|
+
minimumSize: const Size.fromHeight(50),
|
|
96
|
+
shape: RoundedRectangleBorder(
|
|
97
|
+
borderRadius: BorderRadius.circular(8),
|
|
98
|
+
),
|
|
99
|
+
backgroundColor: Colors.blueGrey,
|
|
100
|
+
foregroundColor: Colors.white,
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
inputDecorationTheme: const InputDecorationTheme(
|
|
104
|
+
border: OutlineInputBorder(),
|
|
105
|
+
contentPadding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
|
|
106
|
+
fillColor: AppColors.darkTextFieldFill,
|
|
107
|
+
filled: true,
|
|
108
|
+
),
|
|
109
|
+
scaffoldBackgroundColor: AppColors.darkScaffoldBackground,
|
|
110
|
+
cardColor: AppColors.darkCardBackground,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import '../theme/app_colors.dart';
|
|
4
|
+
|
|
5
|
+
/// Standard SnackBars for the whole app.
|
|
6
|
+
///
|
|
7
|
+
/// Why this exists:
|
|
8
|
+
/// - Many screens create SnackBars with hardcoded colors.
|
|
9
|
+
/// - Over time, feedback UX becomes inconsistent.
|
|
10
|
+
/// - Centralizing here makes it easy to change styling globally.
|
|
11
|
+
///
|
|
12
|
+
/// Usage:
|
|
13
|
+
/// ```dart
|
|
14
|
+
/// showErrorSnackBar(context, 'Login failed');
|
|
15
|
+
/// showSuccessSnackBar(context, 'Saved!');
|
|
16
|
+
/// ```
|
|
17
|
+
void showErrorSnackBar(BuildContext context, String message) {
|
|
18
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
19
|
+
SnackBar(
|
|
20
|
+
content: Text(message),
|
|
21
|
+
backgroundColor: AppColors.error,
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void showSuccessSnackBar(BuildContext context, String message) {
|
|
27
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
28
|
+
SnackBar(
|
|
29
|
+
content: Text(message),
|
|
30
|
+
backgroundColor: AppColors.success,
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void showInfoSnackBar(BuildContext context, String message) {
|
|
36
|
+
// Info uses the current theme's surface for readability.
|
|
37
|
+
final theme = Theme.of(context);
|
|
38
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
39
|
+
SnackBar(
|
|
40
|
+
// SnackBar doesn't support a `textStyle` parameter in some Flutter versions,
|
|
41
|
+
// so we style the Text directly.
|
|
42
|
+
content: Text(
|
|
43
|
+
message,
|
|
44
|
+
style: TextStyle(color: theme.colorScheme.onInverseSurface),
|
|
45
|
+
),
|
|
46
|
+
backgroundColor: theme.colorScheme.inverseSurface,
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:intl/intl.dart';
|
|
3
3
|
import 'package:personal_finance_frontend_core_services/services/crypto_service.dart';
|
|
4
|
+
import 'package:personal_finance_frontend_core_ui/utils/app_snackbars.dart';
|
|
4
5
|
import 'package:personal_finance_frontend_core_services/services/transaction_service.dart';
|
|
5
6
|
import 'package:personal_finance_frontend_core_ui/utils/currency_input_formatter.dart';
|
|
6
7
|
import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
|
|
@@ -156,9 +157,7 @@ class _CryptoTradeFormState extends State<CryptoTradeForm> {
|
|
|
156
157
|
final validationResult = await _cryptoService.validateCryptoSymbol(symbol);
|
|
157
158
|
if (validationResult == null) {
|
|
158
159
|
if (mounted) {
|
|
159
|
-
|
|
160
|
-
SnackBar(content: Text('Invalid crypto ID: "$symbol"'), backgroundColor: Colors.red),
|
|
161
|
-
);
|
|
160
|
+
showErrorSnackBar(context, 'Invalid crypto ID: "$symbol"');
|
|
162
161
|
}
|
|
163
162
|
setState(() => _isSubmitting = false);
|
|
164
163
|
return;
|
|
@@ -176,9 +175,7 @@ class _CryptoTradeFormState extends State<CryptoTradeForm> {
|
|
|
176
175
|
} else { // Sell
|
|
177
176
|
if (_livePriceForSell == null) {
|
|
178
177
|
if (mounted) {
|
|
179
|
-
|
|
180
|
-
const SnackBar(content: Text('Could not get live price. Please try again.'), backgroundColor: Colors.red),
|
|
181
|
-
);
|
|
178
|
+
showErrorSnackBar(context, 'Could not get live price. Please try again.');
|
|
182
179
|
}
|
|
183
180
|
setState(() => _isSubmitting = false);
|
|
184
181
|
return;
|
|
@@ -201,13 +198,9 @@ class _CryptoTradeFormState extends State<CryptoTradeForm> {
|
|
|
201
198
|
|
|
202
199
|
if (mounted) {
|
|
203
200
|
if (errorMessage != null) {
|
|
204
|
-
|
|
205
|
-
SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
|
|
206
|
-
);
|
|
201
|
+
showErrorSnackBar(context, errorMessage);
|
|
207
202
|
} else {
|
|
208
|
-
|
|
209
|
-
SnackBar(content: Text('Trade created successfully!'), backgroundColor: Colors.green),
|
|
210
|
-
);
|
|
203
|
+
showSuccessSnackBar(context, 'Trade created successfully!');
|
|
211
204
|
widget.onTradeCreated(tradeData);
|
|
212
205
|
_formKey.currentState!.reset();
|
|
213
206
|
_symbolController.clear();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:intl/intl.dart';
|
|
3
3
|
import 'package:personal_finance_frontend_core_services/services/transaction_service.dart';
|
|
4
|
+
import 'package:personal_finance_frontend_core_ui/utils/app_snackbars.dart';
|
|
4
5
|
import 'package:personal_finance_frontend_core_ui/utils/currency_input_formatter.dart';
|
|
5
6
|
import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
|
|
6
7
|
|
|
@@ -75,13 +76,9 @@ class _DividendLogFormState extends State<DividendLogForm> {
|
|
|
75
76
|
|
|
76
77
|
if (mounted) {
|
|
77
78
|
if (errorMessage != null) {
|
|
78
|
-
|
|
79
|
-
SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
|
|
80
|
-
);
|
|
79
|
+
showErrorSnackBar(context, errorMessage);
|
|
81
80
|
} else {
|
|
82
|
-
|
|
83
|
-
const SnackBar(content: Text('Dividend logged successfully!'), backgroundColor: Colors.green),
|
|
84
|
-
);
|
|
81
|
+
showSuccessSnackBar(context, 'Dividend logged successfully!');
|
|
85
82
|
widget.onDividendLogged();
|
|
86
83
|
_formKey.currentState!.reset();
|
|
87
84
|
_amountController.clear();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
/// A reusable widget to display historical data in a standardized DataTable.
|
|
4
|
+
class HistoricalDataTable extends StatelessWidget {
|
|
5
|
+
final List<DataColumn> columns;
|
|
6
|
+
final List<DataRow> rows;
|
|
7
|
+
final String noDataFoundMessage;
|
|
8
|
+
|
|
9
|
+
const HistoricalDataTable({
|
|
10
|
+
Key? key,
|
|
11
|
+
required this.columns,
|
|
12
|
+
required this.rows,
|
|
13
|
+
this.noDataFoundMessage = 'No data found.',
|
|
14
|
+
}) : super(key: key);
|
|
15
|
+
|
|
16
|
+
@override
|
|
17
|
+
Widget build(BuildContext context) {
|
|
18
|
+
if (rows.isEmpty) {
|
|
19
|
+
return Center(
|
|
20
|
+
child: Padding(
|
|
21
|
+
padding: const EdgeInsets.all(16.0),
|
|
22
|
+
child: Text(noDataFoundMessage),
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return SingleChildScrollView(
|
|
28
|
+
scrollDirection: Axis.horizontal,
|
|
29
|
+
child: DataTable(
|
|
30
|
+
columnSpacing: 24.0,
|
|
31
|
+
columns: columns,
|
|
32
|
+
rows: rows,
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|
|
2
2
|
import 'package:flutter/services.dart';
|
|
3
3
|
import 'package:intl/intl.dart';
|
|
4
4
|
import 'package:personal_finance_frontend_core_services/services/transaction_service.dart';
|
|
5
|
+
import 'package:personal_finance_frontend_core_ui/utils/app_snackbars.dart';
|
|
5
6
|
import 'package:personal_finance_frontend_core_ui/utils/currency_input_formatter.dart';
|
|
6
7
|
import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
|
|
7
8
|
|
|
@@ -77,13 +78,9 @@ class _RrspContributionFormState extends State<RrspContributionForm> {
|
|
|
77
78
|
|
|
78
79
|
if (mounted) {
|
|
79
80
|
if (errorMessage != null) {
|
|
80
|
-
|
|
81
|
-
SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
|
|
82
|
-
);
|
|
81
|
+
showErrorSnackBar(context, errorMessage);
|
|
83
82
|
} else {
|
|
84
|
-
|
|
85
|
-
const SnackBar(content: Text('RRSP Contribution logged successfully!'), backgroundColor: Colors.green),
|
|
86
|
-
);
|
|
83
|
+
showSuccessSnackBar(context, 'RRSP Contribution logged successfully!');
|
|
87
84
|
widget.onContributionLogged(); // Notify parent to refresh
|
|
88
85
|
_formKey.currentState!.reset();
|
|
89
86
|
_rrspAmountController.clear();
|
|
@@ -154,4 +151,4 @@ class _RrspContributionFormState extends State<RrspContributionForm> {
|
|
|
154
151
|
),
|
|
155
152
|
);
|
|
156
153
|
}
|
|
157
|
-
}
|
|
154
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:intl/intl.dart';
|
|
3
3
|
import 'package:personal_finance_frontend_core_services/services/stock_service.dart';
|
|
4
|
+
import 'package:personal_finance_frontend_core_ui/utils/app_snackbars.dart';
|
|
4
5
|
import 'package:personal_finance_frontend_core_services/services/transaction_service.dart';
|
|
5
6
|
import 'package:personal_finance_frontend_core_ui/utils/currency_input_formatter.dart';
|
|
6
7
|
import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
|
|
@@ -147,11 +148,7 @@ class _TradeFormState extends State<TradeForm> {
|
|
|
147
148
|
final isValid = await _stockService.validateStockSymbol(symbol);
|
|
148
149
|
if (!isValid) {
|
|
149
150
|
if (mounted) {
|
|
150
|
-
|
|
151
|
-
SnackBar(
|
|
152
|
-
content: Text('Invalid stock symbol: "$symbol"'),
|
|
153
|
-
backgroundColor: Colors.red),
|
|
154
|
-
);
|
|
151
|
+
showErrorSnackBar(context, 'Invalid stock symbol: "$symbol"');
|
|
155
152
|
}
|
|
156
153
|
setState(() => _isSubmitting = false);
|
|
157
154
|
return; // Stop submission
|
|
@@ -163,11 +160,7 @@ class _TradeFormState extends State<TradeForm> {
|
|
|
163
160
|
if (_tradeType == 'sell') {
|
|
164
161
|
if (_livePriceForSell == null) {
|
|
165
162
|
if (mounted) {
|
|
166
|
-
|
|
167
|
-
const SnackBar(
|
|
168
|
-
content: Text('Could not get live price. Please try again.'),
|
|
169
|
-
backgroundColor: Colors.red),
|
|
170
|
-
);
|
|
163
|
+
showErrorSnackBar(context, 'Could not get live price. Please try again.');
|
|
171
164
|
}
|
|
172
165
|
setState(() => _isSubmitting = false);
|
|
173
166
|
return;
|
|
@@ -195,15 +188,9 @@ class _TradeFormState extends State<TradeForm> {
|
|
|
195
188
|
|
|
196
189
|
if (mounted) {
|
|
197
190
|
if (errorMessage != null) {
|
|
198
|
-
|
|
199
|
-
SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
|
|
200
|
-
);
|
|
191
|
+
showErrorSnackBar(context, errorMessage);
|
|
201
192
|
} else {
|
|
202
|
-
|
|
203
|
-
const SnackBar(
|
|
204
|
-
content: Text('Trade created successfully!'),
|
|
205
|
-
backgroundColor: Colors.green),
|
|
206
|
-
);
|
|
193
|
+
showSuccessSnackBar(context, 'Trade created successfully!');
|
|
207
194
|
widget.onTradeCreated(tradeData);
|
|
208
195
|
_formKey.currentState!.reset();
|
|
209
196
|
_symbolController.clear();
|