@marcos_feitoza/personal-finance-frontend-core-ui 1.1.0 → 1.2.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,23 @@
1
+ ## [1.2.1](https://github.com/MarcosOps/personal-finance-frontend-core-ui/compare/v1.2.0...v1.2.1) (2026-01-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Web responsivo (mobile/tablet/resize) ([dba28a4](https://github.com/MarcosOps/personal-finance-frontend-core-ui/commit/dba28a4c370aac6f02a0e304e25c6eebaca4b961))
7
+
8
+ # [1.2.0](https://github.com/MarcosOps/personal-finance-frontend-core-ui/compare/v1.1.0...v1.2.0) (2026-01-29)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * new SnackBar ([7c519e0](https://github.com/MarcosOps/personal-finance-frontend-core-ui/commit/7c519e06b53884863a32170015080860ddf836be))
14
+ * update SnackBar ([b0c3b71](https://github.com/MarcosOps/personal-finance-frontend-core-ui/commit/b0c3b71efa0a7dd855fed3561c0c40bca33b23ec))
15
+
16
+
17
+ ### Features
18
+
19
+ * new AppTheme process ([7557114](https://github.com/MarcosOps/personal-finance-frontend-core-ui/commit/755711424e37a65f6891748c9d60940e1bbf2e80))
20
+
1
21
  # [1.1.0](https://github.com/MarcosOps/personal-finance-frontend-core-ui/compare/v1.0.2...v1.1.0) (2025-11-28)
2
22
 
3
23
 
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).
@@ -1 +1,6 @@
1
- export 'widgets/app_widgets.dart';
1
+ export 'widgets/app_widgets.dart';
2
+ export 'theme/app_theme.dart';
3
+ export 'theme/app_colors.dart';
4
+ export 'theme/app_spacing.dart';
5
+ export 'utils/app_snackbars.dart';
6
+ export 'utils/app_responsive.dart';
@@ -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
+ }
@@ -10,17 +10,17 @@ class AppDialogs {
10
10
  context: context,
11
11
  builder: (BuildContext context) {
12
12
  return AlertDialog(
13
- title: const Text('Confirmar Exclusão'),
13
+ title: const Text('Confirm Deletion'),
14
14
  content: Text(
15
- 'Você tem certeza que deseja excluir "$itemName"? Esta ação não pode ser desfeita.'),
15
+ 'Are you sure you want to delete "$itemName"? This action cannot be undone.'),
16
16
  actions: <Widget>[
17
17
  TextButton(
18
18
  onPressed: () => Navigator.of(context).pop(false),
19
- child: const Text('Cancelar'),
19
+ child: const Text('Cancel'),
20
20
  ),
21
21
  TextButton(
22
22
  onPressed: () => Navigator.of(context).pop(true),
23
- child: const Text('Excluir'),
23
+ child: const Text('Delete'),
24
24
  ),
25
25
  ],
26
26
  );
@@ -0,0 +1,87 @@
1
+ import 'package:flutter/widgets.dart';
2
+
3
+ /// Simple responsive helpers and breakpoints.
4
+ ///
5
+ /// Why this exists:
6
+ /// - Most of the app was built desktop-first (many wide `Row`s and `DataTable`s).
7
+ /// - On mobile/tablet or when resizing the browser, layouts overflow.
8
+ /// - These helpers give us one place to define breakpoints and common patterns.
9
+ ///
10
+ /// Breakpoints (can be adjusted later):
11
+ /// - mobile: < 600
12
+ /// - tablet: 600 .. < 1024
13
+ /// - desktop: >= 1024
14
+ class AppBreakpoints {
15
+ AppBreakpoints._();
16
+
17
+ static const double mobile = 600;
18
+ static const double tablet = 1024;
19
+
20
+ static bool isMobileWidth(double width) => width < mobile;
21
+ static bool isTabletWidth(double width) => width >= mobile && width < tablet;
22
+ static bool isDesktopWidth(double width) => width >= tablet;
23
+ }
24
+
25
+ /// Builds different widgets depending on the current available width.
26
+ class ResponsiveBuilder extends StatelessWidget {
27
+ final Widget Function(BuildContext context) mobile;
28
+ final Widget Function(BuildContext context)? tablet;
29
+ final Widget Function(BuildContext context) desktop;
30
+
31
+ const ResponsiveBuilder({
32
+ super.key,
33
+ required this.mobile,
34
+ this.tablet,
35
+ required this.desktop,
36
+ });
37
+
38
+ @override
39
+ Widget build(BuildContext context) {
40
+ return LayoutBuilder(
41
+ builder: (context, constraints) {
42
+ final width = constraints.maxWidth;
43
+
44
+ if (AppBreakpoints.isMobileWidth(width)) {
45
+ return mobile(context);
46
+ }
47
+
48
+ if (AppBreakpoints.isTabletWidth(width)) {
49
+ return (tablet ?? desktop)(context);
50
+ }
51
+
52
+ return desktop(context);
53
+ },
54
+ );
55
+ }
56
+ }
57
+
58
+ /// Wraps a wide widget (like DataTable) with horizontal scroll when needed.
59
+ ///
60
+ /// On desktop the widget is rendered normally.
61
+ /// On mobile/tablet we enable horizontal scrolling to avoid overflow.
62
+ class ResponsiveHorizontalScroll extends StatelessWidget {
63
+ final Widget child;
64
+
65
+ const ResponsiveHorizontalScroll({super.key, required this.child});
66
+
67
+ @override
68
+ Widget build(BuildContext context) {
69
+ return LayoutBuilder(
70
+ builder: (context, constraints) {
71
+ final width = constraints.maxWidth;
72
+ final needsScroll = !AppBreakpoints.isDesktopWidth(width);
73
+
74
+ if (!needsScroll) return child;
75
+
76
+ return SingleChildScrollView(
77
+ scrollDirection: Axis.horizontal,
78
+ child: ConstrainedBox(
79
+ // Make sure the child has at least the current width.
80
+ constraints: BoxConstraints(minWidth: width),
81
+ child: child,
82
+ ),
83
+ );
84
+ },
85
+ );
86
+ }
87
+ }
@@ -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
- ScaffoldMessenger.of(context).showSnackBar(
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
- ScaffoldMessenger.of(context).showSnackBar(
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
- ScaffoldMessenger.of(context).showSnackBar(
205
- SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
206
- );
201
+ showErrorSnackBar(context, errorMessage);
207
202
  } else {
208
- ScaffoldMessenger.of(context).showSnackBar(
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
- ScaffoldMessenger.of(context).showSnackBar(
79
- SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
80
- );
79
+ showErrorSnackBar(context, errorMessage);
81
80
  } else {
82
- ScaffoldMessenger.of(context).showSnackBar(
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
+ import 'package:personal_finance_frontend_core_ui/utils/app_responsive.dart';
3
+
4
+ /// A reusable widget to display historical data in a standardized DataTable.
5
+ class HistoricalDataTable extends StatelessWidget {
6
+ final List<DataColumn> columns;
7
+ final List<DataRow> rows;
8
+ final String noDataFoundMessage;
9
+
10
+ const HistoricalDataTable({
11
+ Key? key,
12
+ required this.columns,
13
+ required this.rows,
14
+ this.noDataFoundMessage = 'No data found.',
15
+ }) : super(key: key);
16
+
17
+ @override
18
+ Widget build(BuildContext context) {
19
+ if (rows.isEmpty) {
20
+ return Center(
21
+ child: Padding(
22
+ padding: const EdgeInsets.all(16.0),
23
+ child: Text(noDataFoundMessage),
24
+ ),
25
+ );
26
+ }
27
+
28
+ return ResponsiveHorizontalScroll(
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
- ScaffoldMessenger.of(context).showSnackBar(
81
- SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
82
- );
81
+ showErrorSnackBar(context, errorMessage);
83
82
  } else {
84
- ScaffoldMessenger.of(context).showSnackBar(
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
- ScaffoldMessenger.of(context).showSnackBar(
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
- ScaffoldMessenger.of(context).showSnackBar(
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
- ScaffoldMessenger.of(context).showSnackBar(
199
- SnackBar(content: Text(errorMessage), backgroundColor: Colors.red),
200
- );
191
+ showErrorSnackBar(context, errorMessage);
201
192
  } else {
202
- ScaffoldMessenger.of(context).showSnackBar(
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcos_feitoza/personal-finance-frontend-core-ui",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },