@marcos_feitoza/personal-finance-frontend-core-ui 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.
- package/.circleci/config.yml +23 -0
- package/.metadata +45 -0
- package/CHANGELOG.md +26 -0
- package/README.md +16 -0
- package/analysis_options.yaml +28 -0
- package/android/app/build.gradle.kts +44 -0
- package/android/app/src/debug/AndroidManifest.xml +7 -0
- package/android/app/src/main/AndroidManifest.xml +45 -0
- package/android/app/src/main/kotlin/com/example/personal_finance_frontend_core_ui/MainActivity.kt +5 -0
- package/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/android/app/src/main/res/values/styles.xml +18 -0
- package/android/app/src/main/res/values-night/styles.xml +18 -0
- package/android/app/src/profile/AndroidManifest.xml +7 -0
- package/android/build.gradle.kts +21 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/android/gradle.properties +3 -0
- package/android/settings.gradle.kts +25 -0
- package/ios/Flutter/AppFrameworkInfo.plist +26 -0
- package/ios/Flutter/Debug.xcconfig +1 -0
- package/ios/Flutter/Release.xcconfig +1 -0
- package/ios/Runner/AppDelegate.swift +13 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- package/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- package/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
- package/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
- package/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
- package/ios/Runner/Base.lproj/Main.storyboard +26 -0
- package/ios/Runner/Info.plist +49 -0
- package/ios/Runner/Runner-Bridging-Header.h +1 -0
- package/ios/Runner.xcodeproj/project.pbxproj +619 -0
- package/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- package/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/ios/RunnerTests/RunnerTests.swift +12 -0
- package/lib/main.dart +122 -0
- package/lib/personal_finance_frontend_core_ui.dart +1 -0
- package/lib/utils/currency_input_formatter.dart +49 -0
- package/lib/utils/currency_utils.dart +14 -0
- package/lib/utils/theme_notifier.dart +33 -0
- package/lib/widgets/app_widgets.dart +405 -0
- package/lib/widgets/crypto_trade_form.dart +357 -0
- package/lib/widgets/dividend_log_form.dart +151 -0
- package/lib/widgets/edit_transaction_dialog.dart +112 -0
- package/lib/widgets/expense_form.dart +238 -0
- package/lib/widgets/investment_form.dart +223 -0
- package/lib/widgets/rrsp_contribution_form.dart +157 -0
- package/lib/widgets/salary_form.dart +152 -0
- package/lib/widgets/trade_form.dart +374 -0
- package/lib/widgets/user_profile_avatar.dart +60 -0
- package/linux/CMakeLists.txt +128 -0
- package/linux/flutter/CMakeLists.txt +88 -0
- package/linux/flutter/generated_plugin_registrant.cc +11 -0
- package/linux/flutter/generated_plugin_registrant.h +15 -0
- package/linux/flutter/generated_plugins.cmake +23 -0
- package/linux/runner/CMakeLists.txt +26 -0
- package/linux/runner/main.cc +6 -0
- package/linux/runner/my_application.cc +130 -0
- package/linux/runner/my_application.h +18 -0
- package/macos/Flutter/Flutter-Debug.xcconfig +1 -0
- package/macos/Flutter/Flutter-Release.xcconfig +1 -0
- package/macos/Flutter/GeneratedPluginRegistrant.swift +10 -0
- package/macos/Runner/AppDelegate.swift +13 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/macos/Runner/Base.lproj/MainMenu.xib +343 -0
- package/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- package/macos/Runner/Configs/Debug.xcconfig +2 -0
- package/macos/Runner/Configs/Release.xcconfig +2 -0
- package/macos/Runner/Configs/Warnings.xcconfig +13 -0
- package/macos/Runner/DebugProfile.entitlements +12 -0
- package/macos/Runner/Info.plist +32 -0
- package/macos/Runner/MainFlutterWindow.swift +15 -0
- package/macos/Runner/Release.entitlements +8 -0
- package/macos/Runner.xcodeproj/project.pbxproj +705 -0
- package/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- package/macos/Runner.xcworkspace/contents.xcworkspacedata +7 -0
- package/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/macos/RunnerTests/RunnerTests.swift +12 -0
- package/package.json +29 -0
- package/pubspec.yaml +94 -0
- package/test/widget_test.dart +30 -0
- package/web/favicon.png +0 -0
- package/web/icons/Icon-192.png +0 -0
- package/web/icons/Icon-512.png +0 -0
- package/web/icons/Icon-maskable-192.png +0 -0
- package/web/icons/Icon-maskable-512.png +0 -0
- package/web/index.html +38 -0
- package/web/manifest.json +35 -0
- package/windows/CMakeLists.txt +108 -0
- package/windows/flutter/CMakeLists.txt +109 -0
- package/windows/flutter/generated_plugin_registrant.cc +11 -0
- package/windows/flutter/generated_plugin_registrant.h +15 -0
- package/windows/flutter/generated_plugins.cmake +23 -0
- package/windows/runner/CMakeLists.txt +40 -0
- package/windows/runner/Runner.rc +121 -0
- package/windows/runner/flutter_window.cpp +71 -0
- package/windows/runner/flutter_window.h +33 -0
- package/windows/runner/main.cpp +43 -0
- package/windows/runner/resource.h +16 -0
- package/windows/runner/resources/app_icon.ico +0 -0
- package/windows/runner/runner.exe.manifest +14 -0
- package/windows/runner/utils.cpp +65 -0
- package/windows/runner/utils.h +19 -0
- package/windows/runner/win32_window.cpp +288 -0
- package/windows/runner/win32_window.h +102 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'package:flutter/services.dart';
|
|
3
|
+
// import 'package:flutter_multi_formatter/flutter_multi_formatter.dart'; // Removed to resolve name conflict.
|
|
4
|
+
import 'package:personal_finance_frontend_core_ui/utils/currency_input_formatter.dart';
|
|
5
|
+
|
|
6
|
+
///* Modern Theme Constants *///
|
|
7
|
+
const double _borderRadius = 16.0;
|
|
8
|
+
const double _fontSize = 16.0;
|
|
9
|
+
final Color _filterBackgroundColor = Colors.transparent;
|
|
10
|
+
final Color _buttonBackgroundColor = Colors.blueGrey;
|
|
11
|
+
final Color _textFieldBackground = Colors.transparent;
|
|
12
|
+
const double _formCardPadding = 20.0;
|
|
13
|
+
const double _formTitleBottomSpacing = 20.0;
|
|
14
|
+
const double _formCardElevation = 3.0;
|
|
15
|
+
const double _formCardBorderWidth = 1.0;
|
|
16
|
+
const double _formCardShadowOpacity = 0.2; // Alpha 51
|
|
17
|
+
const double _formCardBorderOpacity = 0.3; // Alpha 77
|
|
18
|
+
|
|
19
|
+
//* Filter Buttons (Move Money Report) *//
|
|
20
|
+
class FilterButton extends StatelessWidget {
|
|
21
|
+
final String label;
|
|
22
|
+
final String value;
|
|
23
|
+
final VoidCallback onPressed;
|
|
24
|
+
|
|
25
|
+
const FilterButton({
|
|
26
|
+
super.key,
|
|
27
|
+
required this.label,
|
|
28
|
+
required this.value,
|
|
29
|
+
required this.onPressed,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Widget build(BuildContext context) {
|
|
34
|
+
final theme = Theme.of(context);
|
|
35
|
+
return OutlinedButton(
|
|
36
|
+
style: OutlinedButton.styleFrom(
|
|
37
|
+
side: BorderSide(color: theme.colorScheme.outline, width: 1.5),
|
|
38
|
+
shape: RoundedRectangleBorder(
|
|
39
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
40
|
+
),
|
|
41
|
+
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
42
|
+
foregroundColor: theme.colorScheme.onSurface,
|
|
43
|
+
backgroundColor: _filterBackgroundColor,
|
|
44
|
+
textStyle: const TextStyle(fontWeight: FontWeight.bold),
|
|
45
|
+
),
|
|
46
|
+
onPressed: onPressed,
|
|
47
|
+
child: Row(
|
|
48
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
49
|
+
children: [
|
|
50
|
+
Flexible(
|
|
51
|
+
flex: 1,
|
|
52
|
+
child: Text(
|
|
53
|
+
label,
|
|
54
|
+
textAlign: TextAlign.center,
|
|
55
|
+
style: TextStyle(
|
|
56
|
+
color: theme.colorScheme.onSurface,
|
|
57
|
+
fontWeight: FontWeight.bold,
|
|
58
|
+
fontSize: _fontSize,
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
const SizedBox(width: 8),
|
|
63
|
+
Flexible(
|
|
64
|
+
flex: 1,
|
|
65
|
+
child: Text(
|
|
66
|
+
value,
|
|
67
|
+
overflow: TextOverflow.ellipsis,
|
|
68
|
+
textAlign: TextAlign.center,
|
|
69
|
+
style: TextStyle(color: theme.colorScheme.onSurface),
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class PrimaryButton extends StatelessWidget {
|
|
79
|
+
final String text;
|
|
80
|
+
final VoidCallback onPressed;
|
|
81
|
+
|
|
82
|
+
const PrimaryButton({super.key, required this.text, required this.onPressed});
|
|
83
|
+
|
|
84
|
+
@override
|
|
85
|
+
Widget build(BuildContext context) {
|
|
86
|
+
return ElevatedButton(
|
|
87
|
+
onPressed: onPressed,
|
|
88
|
+
child: Text(text),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class SecondaryButton extends StatelessWidget {
|
|
94
|
+
final String text;
|
|
95
|
+
final VoidCallback onPressed;
|
|
96
|
+
|
|
97
|
+
const SecondaryButton(
|
|
98
|
+
{super.key, required this.text, required this.onPressed});
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
Widget build(BuildContext context) {
|
|
102
|
+
return TextButton(
|
|
103
|
+
onPressed: onPressed,
|
|
104
|
+
child: Text(text),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
///* Dropdowns Gerencia uma lista de itens (PaymentMethod)*///
|
|
110
|
+
class AppDropdown<T> extends StatelessWidget {
|
|
111
|
+
final T? value;
|
|
112
|
+
final List<DropdownMenuItem<T>> items;
|
|
113
|
+
final ValueChanged<T?> onChanged;
|
|
114
|
+
final String hint;
|
|
115
|
+
final String? Function(T?)? validator;
|
|
116
|
+
|
|
117
|
+
const AppDropdown({
|
|
118
|
+
super.key,
|
|
119
|
+
this.value,
|
|
120
|
+
required this.items,
|
|
121
|
+
required this.onChanged,
|
|
122
|
+
this.hint = '',
|
|
123
|
+
this.validator,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
@override
|
|
127
|
+
Widget build(BuildContext context) {
|
|
128
|
+
final theme = Theme.of(context);
|
|
129
|
+
return FormField<T>(
|
|
130
|
+
initialValue: value,
|
|
131
|
+
validator: validator,
|
|
132
|
+
builder: (FormFieldState<T> field) {
|
|
133
|
+
return Column(
|
|
134
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
135
|
+
children: [
|
|
136
|
+
Container(
|
|
137
|
+
decoration: BoxDecoration(
|
|
138
|
+
color: field.value != null
|
|
139
|
+
? theme.highlightColor
|
|
140
|
+
: Colors.transparent,
|
|
141
|
+
border:
|
|
142
|
+
Border.all(color: theme.colorScheme.outline, width: 1.5),
|
|
143
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
144
|
+
),
|
|
145
|
+
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
146
|
+
child: DropdownButtonHideUnderline(
|
|
147
|
+
child: DropdownButton<T>(
|
|
148
|
+
value: field.value,
|
|
149
|
+
isExpanded: true,
|
|
150
|
+
dropdownColor: theme.cardColor,
|
|
151
|
+
iconEnabledColor: theme.colorScheme.onSurface,
|
|
152
|
+
style: TextStyle(
|
|
153
|
+
color: theme.colorScheme.onSurface,
|
|
154
|
+
fontSize: _fontSize,
|
|
155
|
+
),
|
|
156
|
+
selectedItemBuilder: (context) {
|
|
157
|
+
return items.map((item) {
|
|
158
|
+
return Align(
|
|
159
|
+
alignment: Alignment.centerLeft,
|
|
160
|
+
child: item.child,
|
|
161
|
+
);
|
|
162
|
+
}).toList();
|
|
163
|
+
},
|
|
164
|
+
items: items,
|
|
165
|
+
onChanged: (T? newValue) {
|
|
166
|
+
field.didChange(newValue);
|
|
167
|
+
onChanged(newValue);
|
|
168
|
+
},
|
|
169
|
+
hint: Text(
|
|
170
|
+
hint,
|
|
171
|
+
style: TextStyle(
|
|
172
|
+
color: theme.colorScheme.onSurface,
|
|
173
|
+
fontSize: _fontSize),
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
),
|
|
177
|
+
),
|
|
178
|
+
if (field.hasError)
|
|
179
|
+
Padding(
|
|
180
|
+
padding: const EdgeInsets.only(left: 12.0, top: 4.0),
|
|
181
|
+
child: Text(
|
|
182
|
+
field.errorText!,
|
|
183
|
+
style: const TextStyle(color: Colors.redAccent, fontSize: 12),
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
],
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
//* Custom Text Field *//
|
|
194
|
+
class AppTextField extends StatelessWidget {
|
|
195
|
+
final TextEditingController controller;
|
|
196
|
+
final String labelText;
|
|
197
|
+
final bool obscureText;
|
|
198
|
+
final IconData? icon;
|
|
199
|
+
final TextInputType? keyboardType;
|
|
200
|
+
final bool readOnly;
|
|
201
|
+
final VoidCallback? onTap;
|
|
202
|
+
final Widget? suffixIcon;
|
|
203
|
+
final List<TextInputFormatter>? inputFormatters;
|
|
204
|
+
final bool isCurrency;
|
|
205
|
+
final String? Function(String?)? validator;
|
|
206
|
+
final String? initialValue;
|
|
207
|
+
final FocusNode? focusNode;
|
|
208
|
+
final bool useDefaultCurrencyFormatter;
|
|
209
|
+
final ValueChanged<String>? onChanged;
|
|
210
|
+
|
|
211
|
+
AppTextField({
|
|
212
|
+
super.key,
|
|
213
|
+
required this.controller,
|
|
214
|
+
required this.labelText,
|
|
215
|
+
this.obscureText = false,
|
|
216
|
+
this.icon,
|
|
217
|
+
this.keyboardType,
|
|
218
|
+
this.readOnly = false,
|
|
219
|
+
this.onTap,
|
|
220
|
+
this.suffixIcon,
|
|
221
|
+
this.inputFormatters,
|
|
222
|
+
this.validator,
|
|
223
|
+
this.isCurrency = false,
|
|
224
|
+
this.initialValue,
|
|
225
|
+
this.focusNode,
|
|
226
|
+
this.useDefaultCurrencyFormatter = true,
|
|
227
|
+
this.onChanged,
|
|
228
|
+
}) {
|
|
229
|
+
// ✅ Valor inicial automático para campo de moeda
|
|
230
|
+
if (isCurrency && (initialValue == null || initialValue!.isEmpty)) {
|
|
231
|
+
controller.text = '0.00';
|
|
232
|
+
} else if (initialValue != null) {
|
|
233
|
+
controller.text = initialValue!;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ✅ Função para converter string formatada em número
|
|
238
|
+
double _unformatCurrency(String value) {
|
|
239
|
+
return CurrencyInputFormatter.unformat(value);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ✅ Validação de campos de moeda
|
|
243
|
+
String? _currencyValidator(String? value) {
|
|
244
|
+
if (value == null || value.isEmpty) return 'Please enter an amount';
|
|
245
|
+
if (_unformatCurrency(value) == 0) return 'Please enter an amount';
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@override
|
|
250
|
+
Widget build(BuildContext context) {
|
|
251
|
+
final theme = Theme.of(context);
|
|
252
|
+
final formatters = <TextInputFormatter>[
|
|
253
|
+
if (isCurrency && useDefaultCurrencyFormatter) CurrencyInputFormatter(),
|
|
254
|
+
if (inputFormatters != null) ...inputFormatters!,
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
return TextFormField(
|
|
258
|
+
controller: controller,
|
|
259
|
+
obscureText: obscureText,
|
|
260
|
+
keyboardType: keyboardType ??
|
|
261
|
+
(isCurrency ? TextInputType.number : TextInputType.text),
|
|
262
|
+
readOnly: readOnly,
|
|
263
|
+
onTap: onTap,
|
|
264
|
+
inputFormatters: formatters,
|
|
265
|
+
validator: validator ?? (isCurrency ? _currencyValidator : null),
|
|
266
|
+
focusNode: focusNode,
|
|
267
|
+
onChanged: onChanged,
|
|
268
|
+
style: TextStyle(color: theme.colorScheme.onSurface, fontSize: _fontSize),
|
|
269
|
+
decoration: InputDecoration(
|
|
270
|
+
labelText: labelText,
|
|
271
|
+
labelStyle: TextStyle(color: theme.colorScheme.onSurface),
|
|
272
|
+
filled: true,
|
|
273
|
+
fillColor: _textFieldBackground,
|
|
274
|
+
prefixText: isCurrency ? '\$' : null,
|
|
275
|
+
prefixStyle: isCurrency
|
|
276
|
+
? TextStyle(color: theme.colorScheme.onSurface, fontSize: _fontSize)
|
|
277
|
+
: null,
|
|
278
|
+
prefixIcon: icon != null
|
|
279
|
+
? Icon(icon, color: theme.colorScheme.onSurface)
|
|
280
|
+
: null,
|
|
281
|
+
suffixIcon: suffixIcon,
|
|
282
|
+
enabledBorder: OutlineInputBorder(
|
|
283
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
284
|
+
borderSide: BorderSide(color: theme.colorScheme.outline, width: 1.2),
|
|
285
|
+
),
|
|
286
|
+
focusedBorder: OutlineInputBorder(
|
|
287
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
288
|
+
borderSide: BorderSide(color: theme.colorScheme.primary, width: 1.6),
|
|
289
|
+
),
|
|
290
|
+
errorBorder: OutlineInputBorder(
|
|
291
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
292
|
+
borderSide: const BorderSide(color: Colors.redAccent, width: 1.2),
|
|
293
|
+
),
|
|
294
|
+
focusedErrorBorder: OutlineInputBorder(
|
|
295
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
296
|
+
borderSide: const BorderSide(color: Colors.redAccent, width: 1.4),
|
|
297
|
+
),
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
///* App Button *///
|
|
304
|
+
class AppButton extends StatelessWidget {
|
|
305
|
+
final String label;
|
|
306
|
+
final Future<void> Function()? onPressed;
|
|
307
|
+
final bool isLoading;
|
|
308
|
+
|
|
309
|
+
const AppButton({
|
|
310
|
+
super.key,
|
|
311
|
+
required this.label,
|
|
312
|
+
required this.onPressed,
|
|
313
|
+
this.isLoading = false,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
@override
|
|
317
|
+
Widget build(BuildContext context) {
|
|
318
|
+
final theme = Theme.of(context);
|
|
319
|
+
return SizedBox(
|
|
320
|
+
width: double.infinity,
|
|
321
|
+
child: ElevatedButton(
|
|
322
|
+
style: ElevatedButton.styleFrom(
|
|
323
|
+
foregroundColor: theme.colorScheme.onPrimary,
|
|
324
|
+
backgroundColor: theme.colorScheme.primary,
|
|
325
|
+
disabledBackgroundColor: theme.colorScheme.primary.withAlpha(153),
|
|
326
|
+
shape: RoundedRectangleBorder(
|
|
327
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
328
|
+
),
|
|
329
|
+
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
330
|
+
textStyle: const TextStyle(
|
|
331
|
+
fontSize: _fontSize,
|
|
332
|
+
fontWeight: FontWeight.bold,
|
|
333
|
+
),
|
|
334
|
+
elevation: 4,
|
|
335
|
+
shadowColor: theme.colorScheme.primary.withAlpha(102),
|
|
336
|
+
),
|
|
337
|
+
onPressed: (isLoading || onPressed == null)
|
|
338
|
+
? null
|
|
339
|
+
: () async {
|
|
340
|
+
FocusScope.of(context).unfocus(); // Fecha o teclado
|
|
341
|
+
await onPressed!();
|
|
342
|
+
},
|
|
343
|
+
child: isLoading
|
|
344
|
+
? const SizedBox(
|
|
345
|
+
height: 22,
|
|
346
|
+
width: 22,
|
|
347
|
+
child: CircularProgressIndicator(
|
|
348
|
+
color: Colors.white,
|
|
349
|
+
strokeWidth: 2,
|
|
350
|
+
),
|
|
351
|
+
)
|
|
352
|
+
: Text(
|
|
353
|
+
label,
|
|
354
|
+
style: TextStyle(color: theme.colorScheme.onPrimary),
|
|
355
|
+
),
|
|
356
|
+
),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
///* Reusable Form Card *///
|
|
362
|
+
class AppFormCard extends StatelessWidget {
|
|
363
|
+
final String title;
|
|
364
|
+
final Widget child;
|
|
365
|
+
|
|
366
|
+
const AppFormCard({super.key, required this.title, required this.child});
|
|
367
|
+
|
|
368
|
+
@override
|
|
369
|
+
Widget build(BuildContext context) {
|
|
370
|
+
final theme = Theme.of(context);
|
|
371
|
+
|
|
372
|
+
return Card(
|
|
373
|
+
color: theme.cardColor,
|
|
374
|
+
elevation: _formCardElevation,
|
|
375
|
+
shadowColor: theme.colorScheme.outline
|
|
376
|
+
.withAlpha((_formCardShadowOpacity * 255).round()),
|
|
377
|
+
shape: RoundedRectangleBorder(
|
|
378
|
+
borderRadius: BorderRadius.circular(_borderRadius),
|
|
379
|
+
side: BorderSide(
|
|
380
|
+
color: theme.colorScheme.outline
|
|
381
|
+
.withAlpha((_formCardBorderOpacity * 255).round()),
|
|
382
|
+
width: _formCardBorderWidth,
|
|
383
|
+
),
|
|
384
|
+
),
|
|
385
|
+
child: Padding(
|
|
386
|
+
padding: const EdgeInsets.all(_formCardPadding),
|
|
387
|
+
child: Column(
|
|
388
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
389
|
+
children: [
|
|
390
|
+
Text(
|
|
391
|
+
title,
|
|
392
|
+
style: theme.textTheme.titleLarge?.copyWith(
|
|
393
|
+
color: theme.colorScheme.primary,
|
|
394
|
+
fontWeight: FontWeight.bold,
|
|
395
|
+
),
|
|
396
|
+
textAlign: TextAlign.center,
|
|
397
|
+
),
|
|
398
|
+
const SizedBox(height: _formTitleBottomSpacing),
|
|
399
|
+
child,
|
|
400
|
+
],
|
|
401
|
+
),
|
|
402
|
+
),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|