Compare commits

...

79 Commits

Author SHA1 Message Date
Ivan Antunes fb5f755d70
Merge pull request #99 from FRE-Informatica/feat/fd-465
FEAT/FD-465: Documentação no APP
2025-02-27 10:26:24 -03:00
jantunesmessias 40ef82cb85 Merge branch 'develop' into feat/fd-465 2025-02-27 10:21:42 -03:00
jantunesmessias 203632bbdf fix 2025-02-26 14:41:28 -03:00
jantunesmessias a7f1ea418b correções do pr 2025-02-25 17:13:09 -03:00
jantunesmessias aa1bd55818 WIP 2025-02-24 17:59:29 -03:00
jantunesmessias 575cfc5a83 fix nosso de cada dia 2025-02-24 17:30:41 -03:00
jantunesmessias d03b3fbdfe resolve conflits 2025-02-20 08:52:59 -03:00
jantunesmessias c0061eabe0 Merge branch 'develop' into feat/fd-465 2025-02-20 08:48:32 -03:00
jantunesmessias 00e5f416b9 misc 2025-02-20 08:33:39 -03:00
jantunesmessias a8220f7743 WIP 2025-02-20 08:27:17 -03:00
Ivan Antunes 7d63d91ffd build 28 2025-02-19 11:32:27 -03:00
Ivan Antunes 1b785ab995
Merge pull request #98 from FRE-Informatica/transparency
Transparency
2025-02-19 11:31:54 -03:00
jantunesmessias 8c91335ba1 WIP 2025-02-19 11:23:59 -03:00
jantunesmessias 6b437e70e6 WIP 2025-02-19 11:18:20 -03:00
jantunesmessias a94f0dd977 appTracking in init 2025-02-19 11:17:40 -03:00
jantunesmessias a9a62a4f9e WIP 2025-02-19 11:08:25 -03:00
jantunesmessias d585eaace5 WIP 2025-02-19 11:00:26 -03:00
jantunesmessias b89b4b8a2c WIP 2025-02-19 10:58:27 -03:00
jantunesmessias 3bbe005e80 WIP 2025-02-19 09:55:48 -03:00
jantunesmessias 9df0a15374 WIP 2025-02-19 09:08:45 -03:00
jantunesmessias d465869bc9 WIP 2025-02-19 09:00:04 -03:00
jantunesmessias f570d9db23 WIP 2025-02-19 08:58:04 -03:00
jantunesmessias ce6426ee9c WIP 2025-02-19 08:55:19 -03:00
jantunesmessias 6f12eaa8ea WIP 2025-02-19 08:54:48 -03:00
jantunesmessias afe8d0d509 WIP 2025-02-19 08:40:13 -03:00
jantunesmessias 5ffd4427d3 WIP 2025-02-19 08:39:20 -03:00
jantunesmessias 272f50ed61 WIP 2025-02-19 08:28:07 -03:00
jantunesmessias 7d6f7ecbc8 update app-tracking-transparency 2025-02-19 08:24:42 -03:00
Ivan Antunes fb026fc22f build 27 2025-02-18 14:38:33 -03:00
Ivan Antunes 1f04e7e5ff
Merge pull request #97 from FRE-Informatica/crashlytics/rollback
Crashlytics/rollback
2025-02-18 14:37:43 -03:00
jantunesmessias 85f961076f WIP 2025-02-18 09:17:55 -03:00
jantunesmessias 6ac6e15009 enable crashlyticsCOllection 2025-02-18 09:16:04 -03:00
jantunesmessias e21d5b5e53 WIP 2025-02-18 08:56:42 -03:00
jantunesmessias 6b724b3821 rollback firebase crashlytics 2025-02-18 08:55:02 -03:00
Ivan Antunes 5b7ed35499 build 26 2025-02-17 15:28:30 -03:00
Ivan Antunes 66b6b6d884
Merge pull request #96 from FRE-Informatica/hot-fix/vehicles
Label de total de veiculos
2025-02-17 14:54:50 -03:00
jantunesmessias e08a049000 comment crashlytics 'fix' 2025-02-17 14:17:08 -03:00
jantunesmessias 3a9772558f FIX 2025-02-17 12:08:27 -03:00
jantunesmessias b9f355cdbf firebase crashlytics fix 2025-02-17 09:23:01 -03:00
Ivan Antunes 75e4a98b17 version 1.4.0 | build 25 2025-02-17 08:59:07 -03:00
Ivan Antunes 37f624401f
Merge pull request #95 from FRE-Informatica/feat/fd-1123
FEAT/FD-1123 - Veiculos
2025-02-14 11:57:47 -03:00
jantunesmessias 5845538518 ? 2025-02-14 11:00:18 -03:00
jantunesmessias c7483d4676 fix keyboard focus 2025-02-14 10:41:58 -03:00
jantunesmessias d42c6301a8 wip - keyboard bugfix 2025-02-14 10:34:15 -03:00
jantunesmessias fcf6af93b9 seState tabbar if isEditing 2025-02-14 09:53:09 -03:00
jantunesmessias 49154d9945 fix refresh 2025-02-14 09:46:56 -03:00
jantunesmessias 0b38538d2b keyboard uppercasse and max length for licensePlate 2025-02-13 17:54:29 -03:00
jantunesmessias 9edd350f13 misc & 2025-02-13 17:04:30 -03:00
jantunesmessias a46181d2f4 refresh listVIew after details 2025-02-13 16:59:29 -03:00
jantunesmessias 70e81c0e34 fix max length 80 2025-02-13 16:55:28 -03:00
jantunesmessias b1a09d999c fix paginacao 2025-02-13 16:54:21 -03:00
jantunesmessias 7b0297a491 wip pageable mixin 2025-02-10 15:13:35 -03:00
jantunesmessias a4b7ee3cd0 uncomment tests 2025-02-10 14:07:38 -03:00
jantunesmessias b0e4e86391 add infinite_scroll_View 2025-02-10 14:00:03 -03:00
jantunesmessias 05b3a612ba paginetedListView 2025-02-10 11:22:48 -03:00
jantunesmessias f193baebe7 backup 2025-02-07 17:37:43 -03:00
jantunesmessias 906fbcaf24 WIP 2025-02-07 16:50:45 -03:00
jantunesmessias 16f43bbab8 WIP - paginacao 2025-02-06 18:01:47 -03:00
jantunesmessias b2df549066 finnish 2025-02-04 10:48:37 -03:00
jantunesmessias 2e22045eea hotfix's miscs 2025-02-04 10:17:31 -03:00
jantunesmessias 8e035e00aa WIP 2025-02-03 16:51:02 -03:00
jantunesmessias 61e3effa59 format 2025-02-03 16:47:38 -03:00
jantunesmessias 56cb5427a2 test 2025-02-03 16:41:59 -03:00
jantunemesssias f64d65425f refinamento e modularização 2025-02-03 11:35:07 -03:00
jantunemesssias 73ea7d5641 format 2025-02-03 09:02:23 -03:00
J. A. Messias a07edd0c21 milestone 2025-01-31 20:58:08 -03:00
jantunemesssias 1cc481a83e checkpoint 2025-01-31 15:28:53 -03:00
jantunemesssias 268c4c897b WIP 2025-01-31 14:30:52 -03:00
jantunemesssias bc16df866a WIP 2025-01-30 17:58:30 -03:00
J. A. Messias 742312e888 wip 2025-01-30 19:33:04 -03:00
J. A. Messias 1d4041f92b WIP 2025-01-30 12:14:24 -03:00
J. A. Messias a79b165635 WIP 2025-01-30 09:30:57 -03:00
J. A. Messias 7dd7ed7a32 WIP 2025-01-29 10:27:23 -03:00
J. A. Messias 3b7a33801f licenes -> vehicle manager module 2025-01-28 11:29:42 -03:00
J. A. Messias e5bd18dec6 WIP 2025-01-28 08:59:00 -03:00
J. A. Messias f33081a773 WIP 2025-01-28 08:53:56 -03:00
J. A. Messias fb91e62a25 WIP 2025-01-27 14:33:08 -03:00
J. A. Messias 3378c3b7a4 Merge branch 'develop' into feat/fd-1123 2025-01-27 14:31:03 -03:00
J. A. Messias 083f2d200d implement vehicle crud screens 2025-01-27 14:28:34 -03:00
79 changed files with 2620 additions and 790 deletions

View File

@ -1 +1 @@
gradle 7.6.1
gradle 8.10.2

View File

@ -7,9 +7,11 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hub/components/atomic_components/shared_components_atoms/submit_button.dart';
import 'package:hub/components/molecular_components/throw_exception/throw_exception_widget.dart';
import 'package:hub/components/organism_components/bottom_arrow_linked_locals_component/bottom_arrow_linked_locals_component_widget.dart';
import 'package:hub/components/templates_components/card_item_template_component/card_item_template_component_widget.dart';
import 'package:hub/components/templates_components/details_component/details_component_widget.dart';
import 'package:hub/features/backend/api_requests/index.dart';
import 'package:hub/features/local/index.dart';
import 'package:hub/features/menu/index.dart';
@ -20,6 +22,8 @@ import 'package:hub/features/storage/index.dart';
import 'package:hub/flutter_flow/index.dart' as ff;
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/main.dart';
import 'package:hub/pages/vehicles_on_the_property/vehicles_on_the_property.dart';
import 'package:integration_test/integration_test.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
@ -27,7 +31,6 @@ import 'app_test.dart';
import 'fuzzer/fuzzer.dart';
import 'package:patrol_finders/patrol_finders.dart';
import 'package:integration_test/integration_test.dart';
export 'package:flutter_test/flutter_test.dart';
export 'package:patrol/patrol.dart';
@ -56,6 +59,7 @@ part 'storage_test.dart';
part 'utils_test.dart';
part 'welcome_test.dart';
part 'vehicle_test.dart';
late PatrolTester $;
@ -83,5 +87,9 @@ void main() {
LocalsTest.setLocal();
LocalsTest.unlinkLocal();
LocalsTest.attachLocal();
VehicleTest.vehiclePage();
VehicleTest.historyScreen();
VehicleTest.registerScreen();
VehicleTest.updateScreen();
}

View File

@ -0,0 +1,320 @@
part of 'app_test.dart';
class VehicleTest {
static Future<void> _initVehicleModule() async {
final vehicleParam = <String, dynamic>{
'display': 'VISIVEL',
'expirationDate': '',
'startDate': '',
'quantity': 0,
};
final vehicleManagerParam = <String, dynamic>{
'display': 'VISIVEL',
'expirationDate': '',
'startDate': '',
'quantity': 0,
};
await LicenseRepositoryImpl()
.setModule(LicenseKeys.vehicles.value, vehicleParam);
await LicenseRepositoryImpl()
.setModule(LicenseKeys.vehiclesManager.value, vehicleManagerParam);
}
static Future<void> vehiclePage() async {
patrolWidgetTest(
'Vehicle Page',
(PatrolTester tester) async {
$ = tester;
$.tester.printToConsole('Vehicle Page');
final PatrolFinder throwsException = $(Dialog).$(ThrowExceptionWidget);
await _loggedWithMultiLocalsAccount();
await _initVehicleModule();
await $.pumpAndSettle();
await $.pumpWidgetAndSettle(const App());
ff.navigatorKey.currentContext!.go('/vehiclesOnThePropertyPage');
final String title = MenuEntry.entries //
.where((entry) => entry.key == 'FRE-HUB-VEHICLES') //
.map((entry) => entry.name)
.first;
final PatrolFinder appBar = await $(AppBar) //
.waitUntilExists();
final PatrolFinder titleAppBar = await appBar //
.$(title)
.waitUntilVisible();
expect(titleAppBar, findsOneWidget);
final PatrolFinder tab1 = await $(#TabView_Tab1) //
.waitUntilExists();
final PatrolFinder tab2 = await $(#TabView_Tab2) //
.waitUntilExists();
await tab2.tap();
await Future.delayed(const Duration(milliseconds: 500));
await tab1.tap();
final PatrolFinder listViewFinder = await $(VehicleHistoryScreen) //
.$(ListView)
.waitUntilVisible();
expect(listViewFinder, findsOneWidget);
final PatrolFinder entriesFinder = await $(listViewFinder)
.$(CardItemTemplateComponentWidget)
.waitUntilVisible();
expect(entriesFinder, findsWidgets);
final int entriesCount = entriesFinder.evaluate().length;
await $.pumpAndSettle();
if (entriesCount > 0)
for (int i = 0; i < entriesCount; i++) {
await $(entriesFinder.at(i)).scrollTo();
await $(entriesFinder.at(i))
.waitUntilVisible(timeout: const Duration(seconds: 1))
.tap(
settleTimeout: const Duration(seconds: 1),
settlePolicy: SettlePolicy.noSettle,
);
await $.pumpAndSettle(duration: Duration(milliseconds: 500));
final PatrolFinder detailsFinder =
await $(DetailsComponentWidget).waitUntilVisible();
expect(detailsFinder, findsOneWidget);
await _navigateBackUsingSystemGesture();
// await $.native.pressBack().then((_) => $.pumpAndSettle());
}
},
);
patrolWidgetTest(
'License',
(PatrolTester tester) async {
$ = tester;
$.tester.printToConsole('Vehicle Page');
await _loggedWithMultiLocalsAccount();
await _initVehicleModule();
await $.pumpAndSettle();
await $.pumpWidgetAndSettle(const App());
ff.navigatorKey.currentContext!.go('/vehiclesOnThePropertyPage');
final String title = MenuEntry.entries //
.where((entry) => entry.key == 'FRE-HUB-VEHICLES') //
.map((entry) => entry.name)
.first;
final PatrolFinder appBar = await $(AppBar) //
.waitUntilExists();
final PatrolFinder titleAppBar = await appBar //
.$(title)
.waitUntilVisible();
expect(titleAppBar, findsOneWidget);
final PatrolFinder tab1 = await $(#TabView_Tab1) //
.waitUntilExists();
final PatrolFinder tab2 = await $(#TabView_Tab2) //
.waitUntilExists();
await tab2.tap();
await Future.delayed(const Duration(milliseconds: 500));
await tab1.tap();
final PatrolFinder listViewFinder = await $(VehicleHistoryScreen) //
.$(ListView)
.waitUntilVisible();
expect(listViewFinder, findsOneWidget);
final PatrolFinder entriesFinder = await $(listViewFinder)
.$(CardItemTemplateComponentWidget)
.waitUntilVisible();
expect(entriesFinder, findsWidgets);
await $.pumpAndSettle();
await Future.delayed(const Duration(milliseconds: 1000));
},
);
}
static Future<void> historyScreen() async {
patrolWidgetTest(
'historyScreen',
(PatrolTester tester) async {
$ = tester;
$.tester.printToConsole('Vehicle Page');
final PatrolFinder throwsException = $(Dialog).$(ThrowExceptionWidget);
await _loggedWithMultiLocalsAccount();
await _initVehicleModule();
await $.pumpAndSettle();
await $.pumpWidgetAndSettle(const App());
ff.navigatorKey.currentContext!.go('/vehiclesOnThePropertyPage');
final String title = MenuEntry.entries //
.where((entry) => entry.key == 'FRE-HUB-VEHICLES') //
.map((entry) => entry.name)
.first;
final PatrolFinder appBar = await $(AppBar) //
.waitUntilExists();
final PatrolFinder titleAppBar = await appBar //
.$(title)
.waitUntilVisible();
expect(titleAppBar, findsOneWidget);
final PatrolFinder listViewFinder = await $(VehicleHistoryScreen) //
.$(ListView)
.waitUntilVisible();
expect(listViewFinder, findsOneWidget);
final PatrolFinder entriesFinder = await $(listViewFinder)
.$(CardItemTemplateComponentWidget)
.waitUntilVisible();
expect(entriesFinder, findsWidgets);
await $.pumpAndSettle();
await $(entriesFinder.first)
.waitUntilVisible(timeout: const Duration(seconds: 1))
.tap(
settleTimeout: const Duration(seconds: 1),
settlePolicy: SettlePolicy.noSettle,
);
await $.pumpAndSettle(duration: Duration(milliseconds: 500));
final PatrolFinder detailsFinder =
await $(DetailsComponentWidget).waitUntilVisible();
expect(detailsFinder, findsOneWidget);
await _navigateBackUsingSystemGesture();
/// Iterable Test
// final int entriesCount = entriesFinder.evaluate().length;
// for (int i = 0; i < entriesCount; i++) {
// // await $(entriesFinder.at(i)).scrollTo();
// await $(entriesFinder.at(i))
// .waitUntilVisible(timeout: const Duration(seconds: 1))
// .tap(
// settleTimeout: const Duration(seconds: 1),
// settlePolicy: SettlePolicy.noSettle,
// );
// await $.pumpAndSettle(duration: Duration(milliseconds: 500));
// final PatrolFinder detailsFinder =
// await $(DetailsComponentWidget).waitUntilVisible();
// expect(detailsFinder, findsOneWidget);
// await _navigateBackUsingSystemGesture();
// // await $.native.pressBack().then((_) => $.pumpAndSettle());
// }
},
);
}
static Future<void> registerScreen() async {
patrolWidgetTest(
'registerScreen',
(PatrolTester tester) async {
$ = tester;
$.tester.printToConsole('Vehicle Register Page');
await _loggedWithMultiLocalsAccount();
await _initVehicleModule();
await $.pumpAndSettle();
await $.pumpWidgetAndSettle(const App());
ff.navigatorKey.currentContext!.go('/vehiclesOnThePropertyPage');
final PatrolFinder tab2 = await $(#TabView_Tab2) //
.waitUntilExists();
await tab2.tap();
final PatrolFinder licensePlateField =
await $(TextField).at(0).waitUntilVisible();
final PatrolFinder modelField =
await $(TextField).at(1).waitUntilVisible();
final PatrolFinder colorField =
await $(TextField).at(2).waitUntilVisible();
final PatrolFinder submitButton =
await $(SubmitButtonUtil).waitUntilVisible();
await licensePlateField.enterText('ABC1234');
await modelField.enterText('Voyage');
await colorField.enterText('Black');
await submitButton.tap();
await $.pumpAndSettle();
final PatrolFinder successDialog = await $(Dialog).waitUntilVisible();
expect(successDialog, findsOneWidget);
},
);
}
static Future<void> updateScreen() async {
patrolWidgetTest(
'updateScreen',
(PatrolTester tester) async {
$ = tester;
$.tester.printToConsole('Vehicle Update Page');
await _loggedWithMultiLocalsAccount();
await _initVehicleModule();
await $.pumpAndSettle();
await $.pumpWidgetAndSettle(const App());
ff.navigatorKey.currentContext!.go('/vehiclesOnThePropertyPage');
final PatrolFinder tab1 = await $(#TabView_Tab1) //
.waitUntilExists();
await tab1.tap();
final PatrolFinder listViewFinder = await $(VehicleHistoryScreen) //
.$(ListView)
.waitUntilVisible();
expect(listViewFinder, findsOneWidget);
final PatrolFinder entriesFinder = await $(listViewFinder)
.$(CardItemTemplateComponentWidget)
.waitUntilVisible();
expect(entriesFinder, findsWidgets);
await $(entriesFinder.at(0)).tap();
final PatrolFinder editButton =
await $(FFButtonWidget).$('Edit').waitUntilVisible();
await editButton.tap();
final PatrolFinder licensePlateField =
await $(TextField).at(0).waitUntilVisible();
final PatrolFinder modelField =
await $(TextField).at(1).waitUntilVisible();
final PatrolFinder colorField =
await $(TextField).at(2).waitUntilVisible();
final PatrolFinder submitButton =
await $(SubmitButtonUtil).waitUntilVisible();
await licensePlateField.enterText('XYZ5678');
await modelField.enterText('Fiesta');
await colorField.enterText('Red');
await submitButton.tap();
await $.pumpAndSettle();
final PatrolFinder successDialog = await $(Dialog).waitUntilVisible();
expect(successDialog, findsOneWidget);
},
);
}
}

View File

@ -5,6 +5,15 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:hub/flutter_flow/flutter_flow_theme.dart';
import 'package:hub/shared/utils/limited_text_size.dart';
class UpperCaseTextFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
return TextEditingValue(
text: newValue.text.toUpperCase(), selection: newValue.selection);
}
}
// ignore: must_be_immutable
class CustomInputUtil extends StatefulWidget {
final TextEditingController? controller;
@ -20,9 +29,10 @@ class CustomInputUtil extends StatefulWidget {
final String? Function(String?)? validator;
final bool haveMaxLength;
final void Function(String)? onChanged;
final List<TextInputFormatter>? inputFormatters;
CustomInputUtil(
{super.key,
CustomInputUtil({
super.key,
this.controller,
required this.labelText,
required this.hintText,
@ -35,7 +45,9 @@ class CustomInputUtil extends StatefulWidget {
this.maxLength = 80,
this.validator,
this.obscureText,
required this.haveMaxLength});
this.inputFormatters,
required this.haveMaxLength,
});
@override
State<CustomInputUtil> createState() => _CustomInputUtilState();
@ -152,6 +164,7 @@ class _CustomInputUtilState extends State<CustomInputUtil> {
keyboardType: widget.keyboardType,
inputFormatters: [
LengthLimitingTextInputFormatter(widget.maxLength),
if (widget.inputFormatters != null) ...widget.inputFormatters!
],
),
],

View File

@ -10,7 +10,7 @@ class TabViewUtil extends StatelessWidget {
String labelTab1;
String labelTab2;
final TabController controller;
final Function(bool) onEditingChanged;
final Function([bool]) onEditingChanged;
Widget widget1;
Widget widget2;
@ -49,15 +49,17 @@ class TabViewUtil extends StatelessWidget {
padding: const EdgeInsets.all(4.0),
tabs: [
Tab(
key: ValueKey('TabView_Tab1'),
text: labelTab1,
),
Tab(
key: ValueKey('TabView_Tab2'),
text: labelTab2,
),
],
controller: controller,
onTap: (i) async {
if (i == 1) onEditingChanged(false);
onEditingChanged();
[() async {}, () async {}][i]();
},
),

View File

@ -209,6 +209,7 @@ class _BottomArrowLinkedLocalsComponentWidgetState
return CardItemTemplateComponentWidget(
key: ValueKey<String>(local['CLI_NOME']),
imagePath: _imagePath(local),
icon: null,
labelsHashMap: _labelsHashMap(local),
statusHashMap: [_statusHashMap(local)],
onTapCardItemAction: () async {

View File

@ -12,18 +12,74 @@ import 'card_item_template_component_model.dart';
export 'card_item_template_component_model.dart';
class FreCardIcon extends StatelessWidget {
final double height;
final double width;
final Icon icon;
const FreCardIcon({
super.key,
required this.height,
required this.width,
required this.icon,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: height,
width: width,
child: icon,
);
}
}
class FreCardPin extends StatelessWidget {
final double height;
final double width;
final Color color;
const FreCardPin({
super.key,
required this.height,
required this.width,
required this.color,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: height,
width: width,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
);
}
}
class CardItemTemplateComponentWidget extends StatefulWidget {
const CardItemTemplateComponentWidget({
super.key,
required this.labelsHashMap,
required this.statusHashMap,
required this.imagePath,
this.imagePath,
this.icon,
this.pin,
this.itemWidthFactor = 0.25,
required this.onTapCardItemAction,
});
final Map<String, String>? labelsHashMap;
final List<Map<String, Color>?> statusHashMap;
final String? imagePath;
final FreCardIcon? icon;
final FreCardPin? pin;
final double itemWidthFactor;
final Future Function()? onTapCardItemAction;
@override
@ -125,9 +181,24 @@ class _CardItemTemplateComponentWidgetState
);
}
Widget _generateIcon() {
return Column(
children: [
widget.icon!,
],
);
}
Widget _generatePin() {
return widget.pin!;
}
List<Widget> _generateStatus() {
double limitedBodyTextSize = LimitedFontSizeUtil.getBodyFontSize(context);
return statusLinkedHashMap.expand((statusLinked) {
log('statusHashMap: ${statusLinked.length}');
return statusLinked.entries.map((entry) {
final text = entry.key;
final color = entry.value;
@ -136,7 +207,7 @@ class _CardItemTemplateComponentWidgetState
message: text,
child: Container(
padding: const EdgeInsets.all(5),
width: MediaQuery.of(context).size.width * 0.25,
width: MediaQuery.of(context).size.width * widget.itemWidthFactor,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(5),
@ -162,7 +233,16 @@ class _CardItemTemplateComponentWidgetState
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 360) {
return Row(
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: [if (widget.pin != null) _generatePin()]),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -172,7 +252,6 @@ class _CardItemTemplateComponentWidgetState
mainAxisSize: MainAxisSize.min,
children: [
..._generateLabels(),
SizedBox(height: 3),
Wrap(
spacing: 8,
runSpacing: 4,
@ -180,20 +259,24 @@ class _CardItemTemplateComponentWidgetState
),
]
.addToEnd(const SizedBox(height: 5))
.divide(const SizedBox(height: 1))
.divide(const SizedBox(height: 3))
.addToStart(const SizedBox(height: 5)),
),
),
if (widget.imagePath != null) _generateImage(),
if (widget.icon != null) _generateIcon(),
if (widget.imagePath != null) _generateImage()
]
.addToEnd(const SizedBox(width: 10))
.addToStart(const SizedBox(width: 10)),
),
].addToStart(SizedBox(height: 5)).addToEnd(SizedBox(height: 5)),
);
} else {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.imagePath != null) _generateImage(),
if (widget.icon != null) _generateIcon(),
Container(
padding: const EdgeInsets.all(8),
child: Column(

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hub/components/templates_components/card_item_template_component/card_item_template_component_widget.dart';
import 'package:hub/components/templates_components/details_component/details_component_model.dart';
import 'package:hub/flutter_flow/flutter_flow_theme.dart';
import 'package:hub/flutter_flow/flutter_flow_util.dart';
@ -14,6 +15,7 @@ class DetailsComponentWidget extends StatefulWidget {
required this.labelsHashMap,
required this.statusHashMap,
this.imagePath,
this.icon,
this.onTapCardItemAction,
required this.buttons,
});
@ -23,6 +25,7 @@ class DetailsComponentWidget extends StatefulWidget {
final String? imagePath;
final Future Function()? onTapCardItemAction;
final List<Widget>? buttons;
final FreCardIcon? icon;
@override
State<DetailsComponentWidget> createState() => _DetailsComponentWidgetState();
@ -97,6 +100,16 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
useOldImageOnUrlChange: true,
),
),
if (widget.icon != null && widget.icon != '')
Container(
width: MediaQuery.of(context).size.width * 0.3,
height: MediaQuery.of(context).size.width * 0.3,
clipBehavior: Clip.antiAlias,
decoration: const BoxDecoration(
shape: BoxShape.circle,
),
child: widget.icon!,
),
SizedBox(height: MediaQuery.of(context).size.height * 0.03),
Row(
children: statusLinkedHashMap.expand((linkedHashMap) {
@ -111,6 +124,7 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
autofocus: false,
canRequestFocus: false,
readOnly: true,
initialValue: item.key,
obscureText: false,
decoration: InputDecoration(
isDense: true,
@ -122,7 +136,7 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
),
filled: true,
fillColor: item.value,
labelText: item.key,
// labelText: item.key,
labelStyle: FlutterFlowTheme.of(context)
.labelMedium
.override(
@ -161,14 +175,16 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
),
),
style: FlutterFlowTheme.of(context)
.bodyMedium
.labelMedium
.override(
fontFamily: FlutterFlowTheme.of(context)
.bodyMediumFamily,
.labelMediumFamily,
fontWeight: FontWeight.bold,
color: FlutterFlowTheme.of(context).info,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).bodyMediumFamily,
FlutterFlowTheme.of(context)
.labelMediumFamily,
),
fontSize: limitedBodyFontSize,
),

View File

@ -34,6 +34,7 @@ class RegisiterVistorTemplateComponentWidget extends StatefulWidget {
class _RegisiterVistorTemplateComponentWidgetState
extends State<RegisiterVistorTemplateComponentWidget> {
late RegisiterVistorTemplateComponentModel _model;
final bool _isLoading = false;
final scaffoldKey = GlobalKey<ScaffoldState>();
bool _isVisitorRegistered = false;

View File

@ -1 +0,0 @@

View File

@ -1,5 +0,0 @@
class DeadCode {
final String? desc;
const DeadCode([this.desc = '']);
}

View File

@ -1 +0,0 @@
export 'anotations.dart';

View File

@ -11,7 +11,7 @@ import 'package:hub/features/storage/index.dart';
import 'package:hub/shared/utils/log_util.dart';
import 'package:hub/shared/utils/validator_util.dart';
import 'package:hub/shared/widgets/widgets.dart';
import 'package:hub/shared/widgets.dart';
import '/flutter_flow/flutter_flow_util.dart';
import 'api_manager.dart';
@ -75,6 +75,187 @@ class FreAccessWSGlobal extends Api {
@override
GetLicense getLicense = GetLicense();
static GetProvSchedules getProvSchedules = GetProvSchedules();
static RegisterVehicle registerVehicle = RegisterVehicle();
static UpdateVehicle updateVehicle = UpdateVehicle();
static DeleteVehicle deleteVehicle = DeleteVehicle();
static CancelDeleteVehicle cancelDelete = CancelDeleteVehicle();
static GetPDF getPDF = GetPDF();
static GetCategories getCategories = GetCategories();
static GetDocuments getDocuments = GetDocuments();
}
class CancelDeleteVehicle {
Future<ApiCallResponse> call({
required final int vehicleId,
required final String licensePlate,
required final String model,
required final String color,
}) async {
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
final String devUUID =
(await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? '';
final String userUUID =
(await StorageHelper().get(ProfileStorageKey.userUUID.key)) ?? '';
final String cliID =
(await StorageHelper().get(ProfileStorageKey.clientUUID.key)) ?? '';
const String atividade = 'cancelDeleteVehicleRequest';
return await ApiManager.instance.makeApiCall(
callName: atividade,
apiUrl: '$baseUrl/processRequest.php',
callType: ApiCallType.POST,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
params: {
'devUUID': devUUID,
'userUUID': userUUID,
'cliID': cliID,
'atividade': atividade,
'vehicleId': vehicleId,
'licensePlate': licensePlate,
'model': model,
'color': color
},
bodyType: BodyType.X_WWW_FORM_URL_ENCODED,
returnBody: true,
encodeBodyUtf8: false,
decodeUtf8: false,
cache: false,
isStreamingApi: false,
alwaysAllowBody: false,
);
}
}
class DeleteVehicle {
Future<ApiCallResponse> call({
required final int vehicleId,
required final String licensePlate,
required final String model,
required final String color,
}) async {
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
final String devUUID =
(await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? '';
final String userUUID =
(await StorageHelper().get(ProfileStorageKey.userUUID.key)) ?? '';
final String cliID =
(await StorageHelper().get(ProfileStorageKey.clientUUID.key)) ?? '';
const String atividade = 'deleteVehicle';
return await ApiManager.instance.makeApiCall(
callName: 'deleteVehicle',
apiUrl: '$baseUrl/processRequest.php',
callType: ApiCallType.POST,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
params: {
'devUUID': devUUID,
'userUUID': userUUID,
'cliID': cliID,
'atividade': atividade,
'vehicleId': vehicleId,
'licensePlate': licensePlate,
'model': model,
'color': color
},
bodyType: BodyType.X_WWW_FORM_URL_ENCODED,
returnBody: true,
encodeBodyUtf8: false,
decodeUtf8: false,
cache: false,
isStreamingApi: false,
alwaysAllowBody: false,
);
}
}
class RegisterVehicle {
Future<ApiCallResponse> call({
final String? licensePlate,
final String? color,
final String? model,
}) async {
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
final String devUUID =
(await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? '';
final String userUUID =
(await StorageHelper().get(ProfileStorageKey.userUUID.key)) ?? '';
final String cliID =
(await StorageHelper().get(ProfileStorageKey.clientUUID.key)) ?? '';
const String atividade = 'insertVehicle';
return await ApiManager.instance.makeApiCall(
callName: 'registerVehicle',
apiUrl: '$baseUrl/processRequest.php',
callType: ApiCallType.POST,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
params: {
'devUUID': devUUID,
'userUUID': userUUID,
'cliID': cliID,
'atividade': atividade,
'licensePlate': licensePlate,
'color': color,
'model': model,
},
bodyType: BodyType.X_WWW_FORM_URL_ENCODED,
returnBody: true,
encodeBodyUtf8: false,
decodeUtf8: false,
cache: false,
isStreamingApi: false,
alwaysAllowBody: false,
);
}
}
class UpdateVehicle {
Future<ApiCallResponse> call({
required final int vehicleId,
final String? licensePlate,
final String? color,
final String? model,
}) async {
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
final String devUUID =
(await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? '';
final String userUUID =
(await StorageHelper().get(ProfileStorageKey.userUUID.key)) ?? '';
final String cliID =
(await StorageHelper().get(ProfileStorageKey.clientUUID.key)) ?? '';
const String atividade = 'updateVehicleToInsertRequest';
return await ApiManager.instance.makeApiCall(
callName: 'updateVehicle',
apiUrl: '$baseUrl/processRequest.php',
callType: ApiCallType.POST,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
params: {
'devUUID': devUUID,
'userUUID': userUUID,
'cliID': cliID,
'atividade': atividade,
'licensePlate': licensePlate,
'color': color,
'model': model,
'vehicleId': vehicleId
},
bodyType: BodyType.X_WWW_FORM_URL_ENCODED,
returnBody: true,
encodeBodyUtf8: false,
decodeUtf8: false,
cache: false,
isStreamingApi: false,
alwaysAllowBody: false,
);
}
static GetCategories getCategories = GetCategories();
static GetDocuments getDocuments = GetDocuments();
}

View File

@ -1,6 +1,5 @@
import 'dart:convert';
import 'dart:core';
import 'dart:developer';
import 'dart:io';
import 'dart:typed_data';
@ -496,11 +495,11 @@ class ApiManager {
result = ApiCallResponse(null, {}, -1, exception: e);
}
log('API Call: $callName');
log('URL: $apiUrl');
log('Headers: $headers');
log('Params: $params');
log('Response: ${result.jsonBody}');
print('API Call: $callName');
print('URL: $apiUrl');
print('Headers: $headers');
print('Params: $params');
print('Response: ${result.jsonBody}');
return result;
}
}

View File

@ -1,17 +1,14 @@
import 'dart:developer';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:hub/components/templates_components/details_component/details_component_widget.dart';
import 'package:hub/features/backend/index.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/extensions/index.dart';
import 'package:hub/shared/utils/index.dart';
import 'package:hub/shared/widgets/widgets.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:hub/shared/extensions.dart';
import 'package:hub/shared/utils.dart';
import 'package:hub/shared/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart' as rx;
@ -134,7 +131,7 @@ class DocumentManagerScreen extends StatelessScreen {
children: [
Expanded(
child: EnhancedListView<Document, SearchField, Category, Query>(
key: model.vehicleScreenManager,
key: model.enhancedListViewKey,
controller: controller,
repository: repository,
),
@ -167,15 +164,14 @@ class _DocumentViewerScreenState extends ScreenState<DocumentViewerScreen> {
super.initState();
}
backAction() => widget.bloc.events.unselectDocument();
@override
Widget build(BuildContext context) {
final String title = widget.doc.$1.description;
final theme = FlutterFlowTheme.of(context);
final locale = FFLocalizations.of(context);
final Color color = widget.doc.$1.category.color;
;
backAction() => widget.bloc.events.unselectDocument();
infoAction() => DetailsComponentWidget(
buttons: [],
statusHashMap: [
@ -230,6 +226,7 @@ class _DocumentViewerScreenState extends ScreenState<DocumentViewerScreen> {
return ReadView(
title: widget.doc.$1.description,
url: widget.doc.$2.toString(),
onError: backAction,
);
}
}
@ -243,28 +240,25 @@ class DocumentModel extends FlutterFlowModel<DocumentPage> {
DocumentModel(this.bloc);
late EnhancedListViewKey<Document, SearchField, Category, Query>
vehicleScreenManager;
late DocumentKey vehicleScreenViewer;
late PagingController<int, Document> _pagingController;
enhancedListViewKey;
late EnhancedCarouselViewKey<Category> carouselViewKey;
late bool categoryIsSelected;
late DocumentKey vehicleScreenViewer;
/// ------------
@override
void initState(BuildContext context) {
vehicleScreenManager =
enhancedListViewKey =
EnhancedListViewKey<Document, SearchField, Category, Query>();
carouselViewKey = EnhancedCarouselViewKey<Category>();
vehicleScreenViewer = DocumentKey();
_pagingController = PagingController<int, Document>(firstPageKey: 1);
categoryIsSelected = false;
}
@override
void dispose() {
_pagingController.dispose();
vehicleScreenManager.currentState?.dispose();
enhancedListViewKey.currentState?.dispose();
carouselViewKey.currentState?.dispose();
vehicleScreenViewer.currentState?.dispose();
}
@ -338,14 +332,17 @@ class DocumentModel extends FlutterFlowModel<DocumentPage> {
Widget itemFooterBuilder<T extends Category>(
Future<List<T?>> Function() fetchData) =>
Builder(builder: (context) {
CategoryComponent categoryItemBuilder<T>(T? item) {
return CategoryComponent(category: item! as Category);
CategoryComponent categoryItemBuilder<T>(T? item, bool isSelected) {
return CategoryComponent(
category: item! as Category, isSelected: isSelected);
}
return EnhancedCarouselView<T>(
key: carouselViewKey,
dataProvider: fetchData,
itemBuilder: categoryItemBuilder,
filter: filterByCategory<T>,
showIndicator: true,
);
});
@ -458,18 +455,14 @@ class DocumentModel extends FlutterFlowModel<DocumentPage> {
/// [Filter]
void filterBySearchBar<T>(T query, BuildContext context) {
final key = vehicleScreenManager.currentState;
return key?.filterBodyItems(query);
final enhancedListViewState = enhancedListViewKey.currentState;
return enhancedListViewState?.filterBodyItems(query);
}
void filterByCategory<T>(T query, BuildContext context) {
final key = vehicleScreenManager.currentState;
void filterByCategory<T>(T? query, BuildContext context) {
final enhancedListViewState = enhancedListViewKey.currentState;
categoryIsSelected
? key?.filterBodyItems(null)
: key?.filterBodyItems(query);
categoryIsSelected = !categoryIsSelected;
enhancedListViewState?.filterBodyItems(query);
}
/// [Exception]
@ -662,8 +655,14 @@ class DocumentComponent extends StatelessComponent {
final description = document.description;
final title = document.category.title;
const double size = 20;
final date = ValidatorUtil.toLocalDateTime(
'yyyy-MM-dd',
document.updatedAt,
);
return InkWell(
return Tooltip(
message: description,
child: InkWell(
onTap: () => onPressed(document, context),
enableFeedback: true,
overlayColor: WidgetStateProperty.all<Color>(primaryColor),
@ -690,14 +689,14 @@ class DocumentComponent extends StatelessComponent {
overflow: TextOverflow.ellipsis,
),
),
AutoText(
ValidatorUtil.toLocalDateTime(
'yyyy-MM-dd',
document.updatedAt,
),
Tooltip(
message: date,
child: AutoText(
date,
style: textStyleMinor,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
@ -722,6 +721,7 @@ class DocumentComponent extends StatelessComponent {
.addToEnd(space),
),
),
),
);
},
),
@ -740,15 +740,19 @@ class DocumentComponent extends StatelessComponent {
class CategoryComponent extends StatelessComponent {
final Category category;
final bool isSelected;
const CategoryComponent({
super.key,
required this.category,
this.isSelected = false,
});
@override
Widget build(BuildContext context) {
final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground;
final textTheme = FlutterFlowTheme.of(context).primaryText;
final color = isSelected ? category.color.highlight : category.color;
return ColoredBox(
color: backgroundTheme,
child: Padding(
@ -758,7 +762,7 @@ class CategoryComponent extends StatelessComponent {
Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: category.color,
color: color,
shape: BoxShape.circle,
),
child: Icon(
@ -771,7 +775,7 @@ class CategoryComponent extends StatelessComponent {
Text(
category.title,
style: TextStyle(
color: category.color,
color: textTheme,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,

View File

@ -46,12 +46,15 @@ class _AccessHistoryState extends State<AccessHistoryScreen> {
selectedTypeSubject.listen((value) {});
}
@override
void initState() {
super.initState();
_model = createModel(context, () => AcessHistoryPageModel());
_accessFuture = fetchAccessHistoryService();
_scrollController = ScrollController()
..addListener(() {
if (_scrollController.position.atEdge &&

View File

@ -34,20 +34,13 @@ class LocalsRemoteDataSourceImpl implements LocalsRemoteDataSource {
try {
final GetLocalsCall callback = FreAccessWSGlobal.getLocalsCall;
var response = await callback.call();
final bool? isError = response.jsonBody['error'];
if (response.jsonBody == null) return;
final bool? isError = response.jsonBody['error'];
if (isError == true) {
LocalUtil.handleError(context, response.jsonBody['error_msg']);
return;
}
if (response.jsonBody == null) {
// final String errorMsg = FFLocalizations.of(context).getVariableText(
// enText: 'Verify your connection',
// ptText: 'Verifique sua conexão',
// );
// await DialogUtil.error(context, errorMsg).whenComplete(() async => await selectLocal(context, response));
return;
}
final List<dynamic> locals = response.jsonBody['locais'] ?? [];
final bool isEmpty = locals.isEmpty;

View File

@ -80,7 +80,6 @@ class LocalsRepositoryImpl implements LocalsRepository {
}
} else {
log('_handleLocal -> Local selected');
return true;
}

View File

@ -115,6 +115,9 @@ class LocalUtil {
.set(LocalsStorageKey.whatsapp.key, jsonBody['whatsapp'] ?? false);
await StorageHelper().set(
LocalsStorageKey.provisional.key, jsonBody['provisional'] ?? false);
await StorageHelper().set(LocalsStorageKey.vehicleAutoApproval.key,
jsonBody['vehicleAutoApproval'] ?? false);
await StorageHelper().set(
LocalsStorageKey.pets.key,
jsonBody['pet'] ?? false,
@ -141,6 +144,13 @@ class LocalUtil {
jsonBody['petAmountRegister']?.toString().isEmpty ?? true
? '0'
: jsonBody['petAmountRegister'].toString());
await StorageHelper().set(
LocalsStorageKey.vehicleAmountRegister.key,
jsonBody['vehicleAmountRegister']?.toString().isEmpty ?? true
? '0'
: jsonBody['vehicleAmountRegister'].toString());
await StorageHelper().set(ProfileStorageKey.userName.key,
jsonBody['visitado']['VDO_NOME'] ?? '');
await StorageHelper().set(ProfileStorageKey.userEmail.key,

View File

@ -17,10 +17,6 @@ abstract class MenuLocalDataSource {
Future<void> handleMenu(EnumMenuItem item, EnumDisplay display, MenuEntry opt,
List<MenuItem?> entries);
Future<bool> processStartDate(String startDate, MenuEntry entry);
Future<bool> processExpirationDate(String expirationDate, MenuEntry entry);
}
class MenuLocalDataSourceImpl implements MenuLocalDataSource {
@ -92,30 +88,4 @@ class MenuLocalDataSourceImpl implements MenuLocalDataSource {
log('Error processing display for module ${opt.key}: $e');
}
}
@override
Future<bool> processStartDate(String startDate, MenuEntry opt) async {
try {
if (startDate.isEmpty) return true;
final start = DateTime.tryParse(startDate);
if (start == null) return false;
return DateTime.now().isAfter(start);
} catch (e) {
log('Error processing start date for module ${opt.key}: $e');
}
return false;
}
@override
Future<bool> processExpirationDate(
String expirationDate, MenuEntry opt) async {
try {
if (expirationDate.isEmpty) return false;
final expiration = DateTime.tryParse(expirationDate);
return expiration != null && DateTime.now().isAfter(expiration);
} catch (e) {
log('Error processing expiration date for module ${opt.key}: $e');
}
return false;
}
}

View File

@ -4,6 +4,7 @@ import 'package:hub/features/menu/index.dart';
import 'package:hub/features/module/index.dart';
import 'package:hub/features/storage/index.dart';
import 'package:hub/flutter_flow/custom_functions.dart';
import 'package:hub/shared/utils/datetime_util.dart';
class MenuRepositoryImpl implements MenuRepository {
final MenuLocalDataSource menuDataSource = MenuLocalDataSourceImpl();
@ -25,10 +26,9 @@ class MenuRepositoryImpl implements MenuRepository {
final display = EnumDisplay.fromString(licenseMap['display']);
final startDate = licenseMap['startDate'] ?? '';
final expirationDate = licenseMap['expirationDate'] ?? '';
final isStarted =
await menuDataSource.processStartDate(startDate, entry);
final isStarted = await DateTimeUtil.processStartDate(startDate);
final isExpired =
await menuDataSource.processExpirationDate(expirationDate, entry);
await DateTimeUtil.processExpirationDate(expirationDate);
if (isStarted && !isExpired) {
await menuDataSource.handleMenu(menuItem, display, entry, entries);
}

View File

@ -76,6 +76,16 @@ class MenuEntry implements BaseModule {
route: '/residentsOnThePropertyPage',
types: [MenuEntryType.Property],
),
// MenuEntry(
// key: 'FRE-HUB-VEHICLES-MANAGER',
// icon: Icons.directions_car,
// name: FFLocalizations.of(navigatorKey.currentContext!).getVariableText(
// ptText: 'Veículos',
// enText: 'Vehicles',
// ),
// route: '/vehiclesOnThePropertyPage',
// types: [MenuEntryType.Property],
// ),
MenuEntry(
key: 'FRE-HUB-VEHICLES',
icon: Icons.directions_car,

View File

@ -94,7 +94,7 @@ class LicenseLocalDataSourceImpl implements LicenseLocalDataSource {
Future<bool> isNewVersion() async {
final String? reponse =
await StorageHelper().get(LocalsStorageKey.isNewVersion.key);
final bool isNewVersion = reponse.toBoolean();
final bool isNewVersion = reponse.toBoolean;
return isNewVersion;
}

View File

@ -10,6 +10,7 @@ enum LicenseKeys {
access('FRE-HUB-ACCESS'),
openedVisits('FRE-HUB-OPENED-VISITS'),
vehicles('FRE-HUB-VEHICLES'),
vehiclesManager('FRE-HUB-VEHICLES-MANAGER'),
residents('FRE-HUB-RESIDENTS'),
about('FRE-HUB-ABOUT-SYSTEM'),
pets('FRE-HUB-PETS'),
@ -63,7 +64,7 @@ class License {
static Future<String> _precessWpp() async {
final bool whatsapp = await StorageHelper()
.get(LocalsStorageKey.whatsapp.key)
.then((v) => v.toBoolean());
.then((v) => v.toBoolean);
if (whatsapp)
return ModuleStatus.active.key;
else
@ -73,7 +74,7 @@ class License {
static Future<String> _processProvisional() async {
final bool provisional = await StorageHelper()
.get(LocalsStorageKey.provisional.key)
.then((v) => v.toBoolean());
.then((v) => v.toBoolean);
if (provisional)
return ModuleStatus.active.key;
else
@ -83,7 +84,7 @@ class License {
static Future<String> _processPets() async {
final bool pets = await StorageHelper()
.get(LocalsStorageKey.pets.key)
.then((v) => v.toBoolean());
.then((v) => v.toBoolean);
if (pets)
return ModuleStatus.active.key;
else
@ -163,6 +164,13 @@ class License {
startDate: '',
quantity: 0,
),
Module(
key: LicenseKeys.vehicles.value,
display: ModuleStatus.inactive.key,
expirationDate: '',
startDate: '',
quantity: 0,
),
Module(
key: LicenseKeys.residents.value,
display: isNewVersionWithModule

View File

@ -34,7 +34,9 @@ enum LocalsStorageKey implements DatabaseStorageKey {
panic('fre_panic'),
person('fre_person'),
requestOSNotification('fre_requestOSnotification'),
isNewVersion('fre_isNewVersion');
isNewVersion('fre_isNewVersion'),
vehicleAutoApproval('fre_vehicleAutoApproval'),
vehicleAmountRegister('fre_vehicleAmountRegister');
final String key;

View File

@ -529,7 +529,7 @@ void setAppLanguage(BuildContext context, String language) =>
void setDarkModeSetting(BuildContext context, ThemeMode themeMode) =>
App.of(context).setThemeMode(themeMode);
void showSnackbar(
void showSnackbarMessenger(
BuildContext context,
String message,
bool error, {
@ -537,8 +537,18 @@ void showSnackbar(
int duration = 4,
}) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
ScaffoldMessenger.of(context)
.showSnackBar(showSnackbar(context, message, error));
}
SnackBar showSnackbar(
BuildContext context,
String message,
bool error, {
bool loading = false,
int duration = 4,
}) {
return SnackBar(
content: Row(
children: [
if (loading)
@ -569,7 +579,6 @@ void showSnackbar(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
);
}

View File

@ -198,7 +198,7 @@ GoRouter createRouter(AppStateNotifier appStateNotifier) {
FFRoute(
name: 'vehiclesOnThePropertyPage',
path: '/vehiclesOnThePropertyPage',
builder: (context, params) => const VehicleOnTheProperty()),
builder: (context, params) => const VehiclePage()),
FFRoute(
name: 'receptionPage',
path: '/receptionPage',

View File

@ -1,4 +1,5 @@
import 'dart:developer';
import 'dart:io';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:firebase_core/firebase_core.dart';
@ -13,23 +14,33 @@ import 'package:hub/flutter_flow/flutter_flow_theme.dart';
import 'package:hub/flutter_flow/flutter_flow_util.dart';
import 'package:hub/flutter_flow/nav/nav.dart';
Future<void> initializeApp() async {
Future<void> initializeBindings() async {
// await _initializeTracking();
WidgetsFlutterBinding.ensureInitialized();
await _initializeTracking();
await _initializeAppTrackingTransparency();
await StorageHelper().init();
await _initializeFirebase();
await _initializeNotificationService();
_initializeUrlStrategy();
_initializeSystemSettings();
await _initializeUrlStrategy();
await _initializeSystemSettings();
await _initializeFlutterFlow();
await _initializeNav();
}
Future<void> _initializeTracking() async {
log('Requesting tracking authorization...');
Future<void> _initializeAppTrackingTransparency() async {
if (Platform.isIOS) {
final status = await AppTrackingTransparency.trackingAuthorizationStatus;
if (status == TrackingStatus.notDetermined) {
await Future.delayed(const Duration(seconds: 2));
final request =
await AppTrackingTransparency.requestTrackingAuthorization();
log('Tracking authorization requested');
if (request == TrackingStatus.authorized) {
log('Tracking authorized');
} else {
log('Tracking not authorized');
}
}
}
}
Future<void> _initializeFirebase() async {
@ -44,19 +55,21 @@ Future<void> _initializeNotificationService() async {
log('Notification Service initialized');
}
void _initializeUrlStrategy() {
log('Initializing URL Strategy...');
Future<void> _initializeUrlStrategy() async {
print('Initializing URL Strategy...');
setUrlStrategy(PathUrlStrategy());
log('URL Strategy initialized');
}
Future<void> _initializeSystemSettings() async {
log('Initializing System Settings...');
final crashlyticsInstance = FirebaseCrashlytics.instance;
print('Initializing System Settings...');
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
final crashlyticsInstance = FirebaseCrashlytics.instance;
if (kDebugMode) {
log('Debug mode');
print('Debug mode');
await crashlyticsInstance.setCrashlyticsCollectionEnabled(false);
} else {
log('Release mode');
@ -72,10 +85,22 @@ Future<void> _initializeSystemSettings() async {
// }
await crashlyticsInstance.setCrashlyticsCollectionEnabled(true);
// if (crashlyticsInstance.isCrashlyticsCollectionEnabled) {
if (crashlyticsInstance.isCrashlyticsCollectionEnabled) {
// Configura o tratamento de erros não capturados
FlutterError.onError = crashlyticsInstance.recordFlutterError;
log('Crashlytics enabled');
// }
crashlyticsInstance.checkForUnsentReports().then((unsentReports) {
if (unsentReports) {
crashlyticsInstance.sendUnsentReports();
print('Existem relatórios de falhas não enviados.');
} else {
print('Todos os relatórios de falhas foram enviados.');
}
}).catchError((error) {
print('Erro ao verificar ou enviar relatórios não enviados: $error');
});
}
print('Crashlytics enabled');
}
}

View File

@ -4,6 +4,7 @@ import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/gestures.dart';
@ -31,7 +32,7 @@ class CustomScrollBehavior extends MaterialScrollBehavior {
}
void main() async {
await initializeApp();
await initializeBindings();
runApp(const ProviderScope(child: App()));
FirebaseMessaging.onBackgroundMessage(_backgroundHandlerMessage);
}
@ -147,7 +148,7 @@ class _AppState extends State<App> {
void initState() {
super.initState();
FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
// FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
_appStateNotifier = AppStateNotifier.instance;
_router = createRouter(_appStateNotifier);
Future.delayed(

View File

@ -299,7 +299,7 @@ class _LiberationHistoryWidgetState extends State<LiberationHistoryWidget> {
)
.then((message) {
if (message != null || message != '') {
showSnackbar(
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Successfully resolved visit',
@ -308,7 +308,7 @@ class _LiberationHistoryWidgetState extends State<LiberationHistoryWidget> {
false,
);
} else {
showSnackbar(context, message, true);
showSnackbarMessenger(context, message, true);
}
}).whenComplete(() {
safeSetState(() {

View File

@ -478,7 +478,7 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
context.pop(value);
if (value == false) {
showSnackbar(
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
ptText: 'Erro ao excluir pet',
@ -487,7 +487,7 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
true,
);
} else if (value == true) {
showSnackbar(
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Success deleting pet',
@ -498,7 +498,7 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
}
}).catchError((err, stack) {
context.pop();
showSnackbar(
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Error deleting pet',

View File

@ -86,9 +86,9 @@ class _PetsPageWidgetState extends State<PetsPageWidget>
);
}
void onEditingChanged(bool value) {
void onEditingChanged([bool? value]) {
setState(() {
_model.handleEditingChanged(value);
_model.handleEditingChanged(value!);
});
}

View File

@ -1,3 +1,4 @@
import 'package:awesome_notifications/awesome_notifications.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

View File

@ -472,7 +472,7 @@ class ScheduleCompleteVisitPageModel
context.pop(value);
if (value == false) {
showSnackbar(
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Error blocking visit',
@ -481,7 +481,7 @@ class ScheduleCompleteVisitPageModel
true,
);
} else if (value == true) {
showSnackbar(
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Success canceling visit',
@ -492,7 +492,7 @@ class ScheduleCompleteVisitPageModel
}
}).catchError((err, stack) {
context.pop();
showSnackbar(
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Error blocking visit',

View File

@ -0,0 +1,2 @@
export 'vehicles_on_the_property.dart';
export 'vehicle_model.dart';

View File

@ -0,0 +1,246 @@
part of 'vehicles_on_the_property.dart';
class VehicleHistoryScreen extends StatefulWidget {
final VehicleModel model;
const VehicleHistoryScreen(this.model, {super.key});
@override
State<VehicleHistoryScreen> createState() => _VehicleHistoryScreenState();
}
class _VehicleHistoryScreenState extends State<VehicleHistoryScreen>
with Pageable {
final apiCall = FreAccessWSGlobal.getVehiclesByProperty;
int totalOwnerVehicles = 0;
final PagingController<int, dynamic> _pagingController =
PagingController<int, dynamic>(firstPageKey: 1);
bool _isSnackble = true;
@override
void initState() {
super.initState();
_pagingController.addPageRequestListener(
(int pageKey) => fetchPage(
dataProvider: () async {
final newItems = await apiCall.call(pageKey.toString());
if (newItems.jsonBody == null) return (false, null);
final List<dynamic> vehicles =
(newItems.jsonBody['vehicles'] as List<dynamic>?) ?? [];
safeSetState(() {
totalOwnerVehicles = newItems.jsonBody['total_owner_vehicles'] ?? 0;
});
return (vehicles.isNotEmpty, vehicles);
},
onDataUnavailable: (vehicles) {
setState(() {});
final bool isFirst = pageKey == 2;
if (!isFirst && _isSnackble) showNoMoreDataSnackBar(context);
_pagingController.appendLastPage(vehicles);
},
onDataAvailable: (vehicles) {
setState(() {});
_pagingController.appendPage(vehicles, pageKey + 1);
},
onFetchError: (e, s) {
DialogUtil.errorDefault(context);
LogUtil.requestAPIFailed(
"proccessRequest.php", "", "Consulta de Veículo", e, s);
setState(() {});
},
),
);
_pagingController.addStatusListener(_showError);
}
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
Future<void> _showError(PagingStatus status) async {
if (status == PagingStatus.subsequentPageError) {
final message = FFLocalizations.of(context).getVariableText(
enText: 'Something went wrong while fetching a new page.',
ptText: 'Algo deu errado ao buscar uma nova página.',
);
final retry = FFLocalizations.of(context).getVariableText(
enText: 'Retry',
ptText: 'Recarregar',
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
action: SnackBarAction(
label: retry,
onPressed: () => _pagingController.retryLastFailedRequest(),
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
_buildHeader(context),
_buildBody(context),
],
);
}
Widget _buildHeader(BuildContext context) {
final bodyFontSize = LimitedFontSizeUtil.getBodyFontSize(context);
final headerTitle = FFLocalizations.of(context).getVariableText(
ptText: "Meus Veículos: ",
enText: "My Vehicles: ",
);
final totalRegisteredVehicles = widget.model.amountRegister;
return SizedBox(
height: 30,
child: Center(
child: Text(
(widget.model.amountRegister == '0' ||
widget.model.amountRegister == null)
? ''
: "$headerTitle $totalOwnerVehicles/$totalRegisteredVehicles",
textAlign: TextAlign.right,
style: TextStyle(
fontFamily: 'Nunito',
fontSize: bodyFontSize,
),
),
),
);
}
Expanded _buildBody(BuildContext context) {
final noDataFound = FFLocalizations.of(context).getVariableText(
ptText: "Nenhum veículo encontrado!",
enText: "No vehicle found",
);
return buildPaginatedListView<int, dynamic>(
noDataFound,
_pagingController,
_generateItems,
);
}
Widget _generateItems(
BuildContext context,
dynamic item,
int index,
) {
log('item: $item');
final bool? isOwner = item['isOwnerVehicle'];
final IconData iconData =
isOwner == true ? Symbols.garage : Symbols.directions_car;
final FreCardIcon? cardIcon = isOwner != null
? FreCardIcon(
height: 50,
width: 100,
icon: Icon(iconData, size: 80, opticalSize: 10),
)
: null;
final String? tag = item['tag'];
final bool containTag = tag.isNotNullAndEmpty;
final Map<String, String> labelsHashMap =
_generateLabelsHashMap(context, item, tag, containTag);
final List<Map<String, Color>> statusHashMapList =
_generateStatusHashMapList(item);
Future<void> onTapCardItemAction() async {
await _handleCardItemTap(context, cardIcon, item);
}
final statusLinkedHashMap = statusHashMapList
.map((map) => LinkedHashMap<String, Color>.from(map))
.toList();
final length = statusLinkedHashMap.expand((e) => [e.length]);
final double itemWidthFactor = length == 1 ? 0.25 : 0.50;
return CardItemTemplateComponentWidget(
icon: cardIcon,
labelsHashMap: labelsHashMap,
statusHashMap: statusHashMapList,
onTapCardItemAction: onTapCardItemAction,
itemWidthFactor: itemWidthFactor,
);
}
Map<String, String> _generateLabelsHashMap(
BuildContext context,
Map<String, dynamic> item,
String? tag,
bool containTag,
) {
final localization = FFLocalizations.of(context);
return {
'${localization.getVariableText(ptText: "Placa", enText: "License Plate")}:':
item['licensePlate'] ?? '',
'${localization.getVariableText(ptText: "Modelo", enText: "Model")}:':
item['model'] ?? '',
'${localization.getVariableText(ptText: "Proprietário", enText: "Owner")}:':
item['personName'] ?? '',
if (containTag)
'${localization.getVariableText(ptText: "Tag", enText: "Tag")}:':
tag ?? '',
};
}
List<Map<String, Color>> _generateStatusHashMapList(
Map<String, dynamic> item) {
final statusHashMap = widget.model.generateStatusColorMap(item, false);
return statusHashMap != null ? [statusHashMap] : [];
}
Future<void> _handleCardItemTap(
BuildContext context,
FreCardIcon? cardIcon,
Map<String, dynamic> item,
) async {
try {
final dialogContent = widget.model.buildVehicleDetails(
icon: cardIcon,
item: item,
context: context,
model: widget.model,
);
await showDialog<bool>(
useSafeArea: true,
context: context,
builder: (context) => Dialog(
alignment: Alignment.center,
child: dialogContent,
),
) //
.then((response) async {
if (response == true) {
_isSnackble = false;
_pagingController.refresh();
} else {
_isSnackble = true;
}
}) //
.whenComplete(() {});
} catch (e, s) {
DialogUtil.errorDefault(context);
LogUtil.requestAPIFailed(
"proccessRequest.php", "", "Consulta de Veículos", e, s);
safeSetState(() {
// _hasData = false;
// _loading = false;
});
}
}
}

View File

@ -1,37 +1,526 @@
import 'package:flutter/material.dart';
import 'package:hub/components/templates_components/details_component/details_component_widget.dart';
import 'package:hub/flutter_flow/flutter_flow_model.dart';
import 'package:hub/flutter_flow/internationalization.dart';
import 'package:hub/pages/vehicles_on_the_property/vehicles_on_the_property.dart';
import 'dart:developer';
class VehicleModel extends FlutterFlowModel<VehicleOnTheProperty> {
import 'package:flutter/material.dart';
import 'package:hub/components/templates_components/card_item_template_component/card_item_template_component_widget.dart';
import 'package:hub/components/templates_components/details_component/details_component_widget.dart';
import 'package:hub/features/backend/index.dart';
import 'package:hub/features/storage/index.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/pages/vehicles_on_the_property/vehicles_on_the_property.dart';
import 'package:hub/shared/extensions.dart';
import 'package:hub/shared/utils.dart';
/// [VehicleModel] is a class that contains the business logic of the vehicle page.
class VehicleModel extends FlutterFlowModel<VehiclePage>
with
_BaseVehiclePage,
_VehicleHistoryScreenModel,
_VehicleRegisterScreenModel,
_VehicleUpdateScreenModel {
/// [VehicleModel] is a singleton class that contains the business logic of the vehicle page.
static VehicleModel? _instance = VehicleModel._internal();
VehicleModel._internal();
factory VehicleModel() => _instance ?? VehicleModel._internal();
static void resetInstance() => _instance = null;
dynamic item;
final GlobalKey<FormState> registerFormKey = GlobalKey<FormState>();
final GlobalKey<FormState> updateFormKey = GlobalKey<FormState>();
@override
void initState(BuildContext context) {
log('VehicleModel -> initState');
resetInstance();
initAsync();
initializeControllers(context);
WidgetsBinding.instance.addPostFrameCallback((_) async {
amountRegister = '0';
});
}
void initializeControllers(BuildContext context) {
tabBarController = TabController(
vsync: Navigator.of(context),
length: 2,
);
textFieldFocusLicensePlate = FocusNode();
textFieldControllerLicensePlate = TextEditingController();
textFieldFocusColor = FocusNode();
textFieldControllerColor = TextEditingController();
textFieldFocusModel = FocusNode();
textFieldControllerModel = TextEditingController();
}
@override
void dispose() {}
void dispose() {
disposeControllers();
}
Future<void> initAsync() async {}
void disposeControllers() {
tabBarController.dispose();
textFieldFocusLicensePlate!.dispose();
textFieldControllerLicensePlate!.dispose();
textFieldFocusColor!.dispose();
textFieldControllerColor!.dispose();
textFieldFocusModel!.dispose();
textFieldControllerModel!.dispose();
}
Widget buildVehicleDetails({
required dynamic item,
required BuildContext context,
required VehicleModel model,
}) {
return DetailsComponentWidget(
buttons: [],
labelsHashMap: Map<String, String>.from({
Future<void> initAsync() async {
amountRegister =
await StorageHelper().get(LocalsStorageKey.vehicleAmountRegister.key);
autoApproval =
await StorageHelper().get(LocalsStorageKey.vehicleAutoApproval.key);
}
bool isFormValid(BuildContext context, GlobalKey<FormState> formKey) {
if (formKey.currentState == null) return false;
return formKey.currentState!.validate();
}
}
/// [_BaseVehiclePage] is a mixin that contains the base logic of the vehicle page.
mixin class _BaseVehiclePage {
int count = 0;
late final TabController tabBarController;
// dynamic item;
late int vehicleId;
BuildContext context = navigatorKey.currentContext!;
bool isEditing = false;
ApiCallResponse? vehicleResponse;
String? amountRegister = '0';
late final String? autoApproval;
VoidCallback? onUpdateVehicle;
VoidCallback? onRegisterVehicle;
VoidCallback? safeSetState;
FocusNode? textFieldFocusLicensePlate;
TextEditingController? textFieldControllerLicensePlate;
String? textControllerLicensePlateValidator(
BuildContext context, String? value) {
final validationMessage = validateField(
context, value, 'Placa é obrigatória', 'License Plate is required');
if (validationMessage != null) return validationMessage;
final brazilianPlateRegex = RegExp(r'^[A-Z]{3}\d{4}$');
final mercosurPlateRegex = RegExp(r'^[A-Z]{3}\d[A-Z0-9]\d{2}$');
if (!brazilianPlateRegex.hasMatch(value!) &&
!mercosurPlateRegex.hasMatch(value)) {
return FFLocalizations.of(context).getVariableText(
ptText: 'Placa inválida',
enText: 'Invalid license plate',
);
}
return null;
}
FocusNode? textFieldFocusColor;
TextEditingController? textFieldControllerColor;
String? textControllerColorValidator(BuildContext contexnt, String? value) {
return validateField(
context, value, 'Cor é obrigatória', 'Color is required');
}
FocusNode? textFieldFocusModel;
TextEditingController? textFieldControllerModel;
String? textControllerModelValidator(BuildContext contexnt, String? value) {
return validateField(
context, value, 'Modelo é obrigatório', 'Model is required');
}
String? validateField(
BuildContext context, String? value, String ptText, String enText) {
if (value == null || value.isEmpty) {
return FFLocalizations.of(context).getVariableText(
ptText: ptText,
enText: enText,
);
}
return null;
}
Future<void> switchTab(int index) async {
tabBarController.animateTo(index);
if (index == 0) await handleEditingChanged(false);
safeSetState?.call();
}
Future<void> clearFields() async {
textFieldControllerLicensePlate = TextEditingController(text: '');
textFieldControllerColor = TextEditingController(text: '');
textFieldControllerModel = TextEditingController(text: '');
}
Future<void> handleEditingChanged(bool editing) async {
isEditing = editing;
await clearFields();
}
Future<void> setEditForm(dynamic item) async {
if (item != null) {
log('vehicleId: ${item['vehicleId']}');
vehicleId = item['vehicleId'];
textFieldControllerLicensePlate = TextEditingController(
text: item != null ? item['licensePlate'] ?? '' : '');
textFieldControllerColor =
TextEditingController(text: item != null ? item['color'] ?? '' : '');
textFieldControllerModel =
TextEditingController(text: item != null ? item['model'] ?? '' : '');
}
}
Future<void> handleVehicleResponse(ApiCallResponse response,
String successMessage, String errorMessage) async {
if (response.jsonBody['error'] == false) {
await DialogUtil.success(context, successMessage).then((_) async {
await switchTab(0);
});
} else {
String errorMsg;
try {
errorMsg = response.jsonBody['error_msg'];
} catch (e) {
errorMsg = errorMessage;
}
await DialogUtil.error(context, errorMsg);
}
}
}
/// [_VehicleRegisterScreenModel] is a mixin that contains the business logic of the vehicle register page.
mixin _VehicleRegisterScreenModel on _BaseVehiclePage {
Future<void> registerVehicle() async {
final response = await FreAccessWSGlobal.registerVehicle.call(
licensePlate: textFieldControllerLicensePlate!.text,
color: textFieldControllerColor!.text,
model: textFieldControllerModel!.text,
);
await handleVehicleResponse(
response,
FFLocalizations.of(context).getVariableText(
ptText: 'Veículo cadastrado com sucesso',
enText: 'Vehicle registered successfully',
),
FFLocalizations.of(context).getVariableText(
ptText: 'Erro ao cadastrar veículo',
enText: 'Error registering vehicle',
),
);
}
}
/// [_VehicleUpdateScreenModel] is a mixin that contains the business logic of the vehicle update page.
mixin _VehicleUpdateScreenModel on _BaseVehiclePage {
Future<void> updateVehicle() async {
final response = await FreAccessWSGlobal.updateVehicle.call(
licensePlate: textFieldControllerLicensePlate!.text,
color: textFieldControllerColor!.text,
model: textFieldControllerModel!.text,
vehicleId: vehicleId,
);
await handleVehicleResponse(
response,
FFLocalizations.of(context).getVariableText(
ptText: 'Veículo atualizado com sucesso',
enText: 'Vehicle updated successfully',
),
FFLocalizations.of(context).getVariableText(
ptText: 'Erro ao atualizar veículo',
enText: 'Error updating vehicle',
),
);
}
}
/// [_VehicleHistoryScreenModel] is a mixin that contains the business logic of the vehicle history page.
mixin _VehicleHistoryScreenModel on _BaseVehiclePage {
Map<String, Color>? generateStatusColorMap(dynamic uItem, bool isDetail) {
if (autoApproval.toBoolean == true) return null;
final theme = FlutterFlowTheme.of(context);
final localization = FFLocalizations.of(context);
final status = uItem['status'];
final isOwner = uItem['isOwnerVehicle'];
if (isOwner == null && status == null) return null;
String byLanguage(String en, String pt) =>
localization.getVariableText(enText: en, ptText: pt);
final vehicleStatusMap = {
"ATI": {
"text": byLanguage('Active', 'Ativo'),
"color": theme.success,
},
"INA": {
"text": byLanguage('Inactive', 'Inativo'),
"color": theme.accent2,
},
"APR_CREATE": {
"text": byLanguage('Awaiting Creation', 'Aguardando Criação'),
"color": theme.success,
},
"APR_DELETE": {
"text": byLanguage('Awaiting Deletion', 'Aguardando Exclusão'),
"color": theme.error,
},
"APR_UPDATE": {
"text": byLanguage('Awaiting Update', 'Aguardando Atualização'),
"color": theme.accent2,
},
"AGU_CHANGE": {
"text": byLanguage('Awaiting Change', 'Aguardando Alteração'),
"color": theme.warning,
},
};
if (vehicleStatusMap.containsKey(status)) {
final statusMap = {
vehicleStatusMap[status]!['text'] as String:
vehicleStatusMap[status]!['color'] as Color,
};
if (!isDetail && isOwner && (status != 'ATI' && status != 'INA')) {
statusMap[byLanguage('My Vehicle', 'Meu Veículo')] = theme.accent4;
}
return statusMap;
}
return {};
}
List<FFButtonWidget> generateActionButtons(dynamic item) {
final Color iconButtonColor = FlutterFlowTheme.of(context).primaryText;
final FFButtonOptions buttonOptions = FFButtonOptions(
height: 40,
color: FlutterFlowTheme.of(context).primaryBackground,
elevation: 0,
textStyle: TextStyle(
color: FlutterFlowTheme.of(context).primaryText,
fontSize: LimitedFontSizeUtil.getNoResizeFont(context, 15),
),
splashColor: FlutterFlowTheme.of(context).success,
borderSide: BorderSide(
color: FlutterFlowTheme.of(context).primaryBackground,
width: 1,
),
);
if (item['status'] == null) return [];
final updateText = FFLocalizations.of(context)
.getVariableText(ptText: 'Editar', enText: 'Edit');
final updateIcon = Icon(Icons.edit, color: iconButtonColor);
Future updateOnPressed() async {
context.pop();
isEditing = true;
await switchTab(1);
await setEditForm(item);
}
final cancelText = FFLocalizations.of(context)
.getVariableText(ptText: 'Cancelar', enText: 'Cancel');
final cancelIcon = Icon(Icons.close, color: iconButtonColor);
Future cancelOnPressed() async {
showAlertDialog(
context,
FFLocalizations.of(context).getVariableText(
ptText: 'Cancelar Solicitação',
enText: 'Cancel Request',
),
FFLocalizations.of(context).getVariableText(
ptText: 'Você tem certeza que deseja cancelar essa solicitação?',
enText: 'Are you sure you want to delete this request?',
),
() async => await processCancelRequest(item['status'], item));
}
final deleteText = FFLocalizations.of(context)
.getVariableText(ptText: 'Excluir', enText: 'Delete');
final deleteIcon = Icon(Icons.delete, color: iconButtonColor);
Future deleteOnPressed() async {
showAlertDialog(
context,
FFLocalizations.of(context).getVariableText(
ptText: 'Excluir Veículo',
enText: 'Delete Vehicle',
),
FFLocalizations.of(context).getVariableText(
ptText: 'Você tem certeza que deseja excluir esse veículo?',
enText: 'Are you sure you want to delete this vehicle?',
),
() async => processDeleteRequest(item),
);
}
final bool containStatus = item['status'] == null;
final bool isOwnerVehicle = item['isOwnerVehicle'];
final bool isAGU = item['status'].contains('AGU');
final bool isAPR = item['status'].contains('APR');
final bool isATI = item['status'].contains('ATI');
if (containStatus)
return [
FFButtonWidget(
text: deleteText,
icon: deleteIcon,
onPressed: deleteOnPressed,
options: buttonOptions,
),
];
return [
if (isAGU && isOwnerVehicle)
FFButtonWidget(
text: updateText,
icon: updateIcon,
onPressed: updateOnPressed,
options: buttonOptions,
),
if ((isAPR || isAGU) && (isOwnerVehicle))
FFButtonWidget(
text: cancelText,
icon: cancelIcon,
onPressed: cancelOnPressed,
options: buttonOptions,
),
if (isATI && isOwnerVehicle)
FFButtonWidget(
text: deleteText,
icon: deleteIcon,
onPressed: deleteOnPressed,
options: buttonOptions,
),
];
}
Future<bool> processDeleteRequest(dynamic item) async {
log('processDeleteRequest -> item[$item]');
bool result = await FreAccessWSGlobal.deleteVehicle
.call(
vehicleId: item['vehicleId'],
licensePlate: item['licensePlate'],
model: item['model'],
color: item['color'],
)
.then((value) {
// ignore: unrelated_type_equality_checks
if (value.jsonBody['error'] == true) {
final String errorMsg = value.jsonBody['error_msg'];
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
ptText: errorMsg,
enText: 'Error deleting vehicle',
),
true,
);
return false;
// ignore: unrelated_type_equality_checks
}
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Success deleting vehicle',
ptText: 'Succeso ao excluir veículo',
),
false,
);
return true;
}) //
.catchError((err, stack) {
showSnackbarMessenger(
context,
FFLocalizations.of(context).getVariableText(
enText: 'Error deleting vehicle',
ptText: 'Erro ao excluir veículo',
),
true,
);
return false;
});
context.pop(result);
context.pop(result);
return result;
}
Future<bool> processCancelRequest(String status, dynamic item) async {
try {
final ApiCallResponse value;
switch (status) {
case 'APR_CREATE':
value = await processCancelDeleteRequest(item);
break;
case 'AGU_CHANGE':
value = await processCancelUpdateRequest(item);
break;
case 'APR_DELETE':
value = await processCancelCreateRequest(item);
break;
default:
throw ArgumentError('Status inválido: $status');
}
final bool isError = value.jsonBody['error'] == true;
final String message = FFLocalizations.of(context).getVariableText(
ptText: value.jsonBody['error_msg'] ?? 'Erro ao cancelar solicitação',
enText:
isError ? 'Error canceling request' : 'Success canceling request',
);
showSnackbarMessenger(context, message, isError);
context.pop(!isError);
context.pop(!isError);
return !isError;
} catch (err) {
final String errorMessage = FFLocalizations.of(context).getVariableText(
ptText: 'Erro ao cancelar solicitação',
enText: 'Error canceling request',
);
showSnackbarMessenger(context, errorMessage, true);
context.pop(false);
context.pop(false);
return false;
}
}
Future<ApiCallResponse> processCancelDeleteRequest(dynamic item) async {
return await FreAccessWSGlobal.deleteVehicle.call(
vehicleId: item['vehicleId'],
licensePlate: item['licensePlate'],
model: item['model'],
color: item['color'],
);
}
Future<ApiCallResponse> processCancelUpdateRequest(dynamic item) async {
return await FreAccessWSGlobal.deleteVehicle.call(
vehicleId: item['vehicleId'],
licensePlate: item['licensePlate'],
model: item['model'],
color: item['color'],
);
}
Future<ApiCallResponse> processCancelCreateRequest(dynamic item) async {
return await FreAccessWSGlobal.cancelDelete.call(
vehicleId: item['vehicleId'],
licensePlate: item['licensePlate'],
model: item['model'],
color: item['color'],
);
}
Map<String, String> generateLabelsHashMap(dynamic item) {
return {
if (item['model'] != null && item['model'] != '')
'${FFLocalizations.of(context).getVariableText(ptText: "Modelo", enText: "Model")}:':
item['model'].toString().toUpperCase(),
@ -47,8 +536,23 @@ class VehicleModel extends FlutterFlowModel<VehicleOnTheProperty> {
if (item['tag'] != null && item['tag'] != '')
'${FFLocalizations.of(context).getVariableText(ptText: "Tag", enText: "Tag")}:':
item['tag'].toString().toUpperCase(),
}),
statusHashMap: [],
};
}
DetailsComponentWidget buildVehicleDetails({
required dynamic item,
required BuildContext context,
required VehicleModel model,
required FreCardIcon? icon,
}) {
final status = generateStatusColorMap(item, true);
final buttons = generateActionButtons(item);
final labels = generateLabelsHashMap(item);
return DetailsComponentWidget(
icon: icon,
buttons: buttons,
labelsHashMap: labels,
statusHashMap: [status],
);
}
}

View File

@ -0,0 +1,141 @@
part of 'vehicles_on_the_property.dart';
/// [VehicleRegisterScreen] is a StatefulWidget that displays a form to register a vehicle.
// ignore: must_be_immutable
class VehicleRegisterScreen extends StatefulWidget {
VehicleRegisterScreen(this.model, {super.key});
late VehicleModel model;
@override
State<VehicleRegisterScreen> createState() => _VehicleRegisterScreenState();
}
class _VehicleRegisterScreenState extends State<VehicleRegisterScreen> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
_buildHeader(context),
_buildBody(context),
],
),
);
}
Form _buildBody(BuildContext context) {
return Form(
key: widget.model.registerFormKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildCustomInput(
context: context,
controller: widget.model.textFieldControllerLicensePlate!,
validator: widget.model.textControllerLicensePlateValidator,
focusNode: widget.model.textFieldFocusLicensePlate!,
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Placa', enText: 'License Plate'),
hintText: FFLocalizations.of(context)
.getVariableText(ptText: 'Placa', enText: 'License Plate'),
suffixIcon: Symbols.format_color_text,
inputFormatters: [UpperCaseTextFormatter()],
maxLength: 7,
),
_buildCustomInput(
context: context,
controller: widget.model.textFieldControllerModel!,
validator: widget.model.textControllerModelValidator,
focusNode: widget.model.textFieldFocusModel!,
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Modelo', enText: 'Model'),
hintText: FFLocalizations.of(context).getVariableText(
ptText: 'Ex: Voyage, Ford', enText: 'e.g. Voyage, Ford'),
suffixIcon: Symbols.car_repair,
inputFormatters: [],
),
_buildCustomInput(
context: context,
controller: widget.model.textFieldControllerColor!,
validator: widget.model.textControllerColorValidator,
focusNode: widget.model.textFieldFocusColor!,
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Cor', enText: 'Color'),
hintText: FFLocalizations.of(context).getVariableText(
ptText: 'Ex: Preto, Amarelo, Branco',
enText: 'e.g. Black, Yellow, White'),
suffixIcon: Symbols.palette,
inputFormatters: [],
),
Padding(
padding: const EdgeInsets.fromLTRB(70, 20, 70, 30),
child: SubmitButtonUtil(
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Cadastrar', enText: 'Register'),
onPressed: widget.model
.isFormValid(context, widget.model.registerFormKey)
? widget.model.registerVehicle
: null,
),
),
],
),
);
}
Widget _buildCustomInput({
required BuildContext context,
required TextEditingController controller,
required String? Function(BuildContext, String?) validator,
required FocusNode focusNode,
required String labelText,
required String hintText,
required IconData suffixIcon,
required final List<TextInputFormatter>? inputFormatters,
int maxLength = 80,
}) {
return CustomInputUtil(
controller: controller,
validator: (value) => validator(context, value),
focusNode: focusNode,
labelText: labelText,
hintText: hintText,
suffixIcon: suffixIcon,
haveMaxLength: true,
onChanged: (value) => setState(() {}),
inputFormatters: inputFormatters,
maxLength: maxLength,
);
}
Align _buildHeader(BuildContext context) {
double limitedHeaderFontSize =
LimitedFontSizeUtil.getHeaderFontSize(context);
return Align(
alignment: const AlignmentDirectional(-1.0, 0.0),
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(24.0, 20, 0.0, 15),
child: Text(
FFLocalizations.of(context).getVariableText(
ptText:
'Preencha o formulário de cadastro com os dados do seu veículo',
enText: 'Fill out the registration form with your vehicle data',
),
textAlign: TextAlign.start,
style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap()
.containsKey(FlutterFlowTheme.of(context).bodyMediumFamily),
fontSize: limitedHeaderFontSize,
),
),
),
);
}
}

View File

@ -0,0 +1,140 @@
part of 'vehicles_on_the_property.dart';
/// [VehicleUpdateScreen] is a StatefulWidget that displays a form to update a vehicle.
// ignore: must_be_immutable
class VehicleUpdateScreen extends StatefulWidget {
VehicleUpdateScreen(this.model, {super.key});
late VehicleModel model;
@override
State<VehicleUpdateScreen> createState() => _VehicleUpdateScreenState();
}
class _VehicleUpdateScreenState extends State<VehicleUpdateScreen> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
_buildHeader(context),
buildBody(context),
],
),
);
}
Form buildBody(BuildContext context) {
return Form(
key: widget.model.updateFormKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildCustomInput(
context: context,
controller: widget.model.textFieldControllerLicensePlate!,
validator: widget.model.textControllerLicensePlateValidator,
focusNode: widget.model.textFieldFocusLicensePlate!,
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Placa', enText: 'License Plate'),
hintText: FFLocalizations.of(context)
.getVariableText(ptText: 'Placa', enText: 'License Plate'),
suffixIcon: Symbols.format_color_text,
inputFormatters: [UpperCaseTextFormatter()],
maxLength: 7,
),
_buildCustomInput(
context: context,
controller: widget.model.textFieldControllerModel!,
validator: widget.model.textControllerModelValidator,
focusNode: widget.model.textFieldFocusModel!,
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Modelo', enText: 'Model'),
hintText: FFLocalizations.of(context).getVariableText(
ptText: 'Ex: Voyage, Ford', enText: 'e.g. Voyage, Ford'),
suffixIcon: Symbols.car_repair,
inputFormatters: [],
),
_buildCustomInput(
context: context,
controller: widget.model.textFieldControllerColor!,
validator: widget.model.textControllerColorValidator,
focusNode: widget.model.textFieldFocusColor!,
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Cor', enText: 'Color'),
hintText: FFLocalizations.of(context).getVariableText(
ptText: 'Ex: Preto, Amarelo, Branco',
enText: 'e.g. Black, Yellow, White'),
suffixIcon: Symbols.palette,
inputFormatters: [],
),
_buildSubmitButton(context),
],
),
);
}
Widget _buildHeader(BuildContext context) {
return Align(
alignment: const AlignmentDirectional(-1.0, 0.0),
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(24.0, 20, 0.0, 15),
child: Text(
FFLocalizations.of(context).getVariableText(
ptText:
'Preencha o formulário de alteração com os dados do seu veículo',
enText: 'Fill out the update form with your vehicle data',
),
textAlign: TextAlign.start,
style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap()
.containsKey(FlutterFlowTheme.of(context).bodyMediumFamily),
),
),
),
);
}
Widget _buildCustomInput({
required BuildContext context,
required TextEditingController controller,
required String? Function(BuildContext, String?) validator,
required FocusNode focusNode,
required String labelText,
required String hintText,
required IconData suffixIcon,
required List<TextInputFormatter>? inputFormatters,
int maxLength = 80,
}) {
return CustomInputUtil(
controller: controller,
validator: (value) => validator(context, value),
focusNode: focusNode,
labelText: labelText,
hintText: hintText,
suffixIcon: suffixIcon,
haveMaxLength: true,
onChanged: (value) => setState(() {}),
inputFormatters: inputFormatters,
maxLength: maxLength,
);
}
Widget _buildSubmitButton(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(70, 20, 70, 30),
child: SubmitButtonUtil(
labelText: FFLocalizations.of(context)
.getVariableText(ptText: 'Salvar', enText: 'Save'),
onPressed: widget.model.isFormValid(context, widget.model.updateFormKey)
? () => widget.model.updateVehicle()
: null,
),
);
}
}

View File

@ -1,149 +1,184 @@
import 'dart:collection';
import 'dart:developer';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hub/components/atomic_components/shared_components_atoms/custom_input.dart';
import 'package:hub/components/atomic_components/shared_components_atoms/submit_button.dart';
import 'package:hub/components/atomic_components/shared_components_atoms/tabview.dart';
import 'package:hub/components/templates_components/card_item_template_component/card_item_template_component_widget.dart';
import 'package:hub/features/backend/index.dart';
import 'package:hub/flutter_flow/flutter_flow_icon_button.dart';
import 'package:hub/flutter_flow/flutter_flow_theme.dart';
import 'package:hub/flutter_flow/flutter_flow_util.dart';
import 'package:hub/features/module/index.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/pages/vehicles_on_the_property/vehicle_model.dart';
import 'package:hub/shared/extensions.dart';
import 'package:hub/shared/mixins/pageable_mixin.dart';
import 'package:hub/shared/utils/dialog_util.dart';
import 'package:hub/shared/utils/license_util.dart';
import 'package:hub/shared/utils/limited_text_size.dart';
import 'package:hub/shared/utils/log_util.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:material_symbols_icons/symbols.dart';
class VehicleOnTheProperty extends StatefulWidget {
const VehicleOnTheProperty({super.key});
part 'vehicle_history_screen.dart';
part 'vehicle_register_screen.dart';
part 'vehicle_update_screen.dart';
/// [VehiclePage] is a StatefulWidget that displays the vehicle screens.
class VehiclePage extends StatefulWidget {
const VehiclePage({super.key});
@override
_VehicleOnThePropertyState createState() => _VehicleOnThePropertyState();
// ignore: library_private_types_in_public_api
_VehiclePageState createState() => _VehiclePageState();
}
class _VehicleOnThePropertyState extends State<VehicleOnTheProperty>
class _VehiclePageState extends State<VehiclePage>
with TickerProviderStateMixin {
late ScrollController _scrollController;
int _pageNumber = 1;
bool _hasData = false;
bool _loading = false;
int count = 0;
late final VehicleModel model;
late Future<void> _future;
List<dynamic> _wrap = [];
late final VehicleModel _model;
@override
void initState() {
super.initState();
model = createModel(context, () => VehicleModel());
_future = _fetchVisits();
_scrollController = ScrollController()
..addListener(() {
if (_scrollController.position.atEdge &&
_scrollController.position.pixels != 0) {
_loadMore();
}
_model = createModel(context, () => VehicleModel());
_model.updateOnChange = true;
_model.onUpdateVehicle = () {
safeSetState(() {
_model.clearFields();
});
};
_model.onRegisterVehicle = () {
safeSetState(() {
_model.clearFields();
});
};
_model.safeSetState = () {
safeSetState(() {});
};
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
// @override
// void dispose() {
// super.dispose();
// }
@override
Widget build(BuildContext context) {
late final limitedHeaderTextSize =
LimitedFontSizeUtil.getHeaderFontSize(context);
final backgroundColor = FlutterFlowTheme.of(context).primaryBackground;
return Scaffold(
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
appBar: _appBar(context),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (_hasData == false && _pageNumber <= 1 && _loading == false)
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Center(
child: Text(
FFLocalizations.of(context).getVariableText(
ptText: "Nenhum veículo encontrado!",
enText: "No vehicle found",
),
style: TextStyle(
fontFamily: 'Nunito',
fontSize: limitedHeaderTextSize,
),
),
)
],
),
)
else if (_hasData == true || _pageNumber >= 1)
Expanded(
child: FutureBuilder<void>(
future: _future,
builder: (context, snapshot) {
return ListView.builder(
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
controller: _scrollController,
itemCount: _wrap.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
// Add your item here
return Padding(
padding: const EdgeInsets.only(right: 30, top: 10),
child: Text(
'',
textAlign: TextAlign.right,
),
backgroundColor: backgroundColor,
appBar: _buildHeader(context),
body: _buildBody(context),
);
} else {
final item = _wrap[index - 1];
return _item(context, item);
}
});
},
)),
if (_hasData == true && _loading == true)
Container(
padding: const EdgeInsets.only(top: 15, bottom: 15),
child: Center(
child: CircularProgressIndicator(
/// [Body] of the page.
FutureBuilder<bool> _buildBody(BuildContext context) {
Widget progressIndicator() {
return CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
FlutterFlowTheme.of(context).primary,
),
),
),
)
].addToStart(const SizedBox(height: 0)),
),
);
}
PreferredSizeWidget _appBar(BuildContext context) {
return AppBar(
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
automaticallyImplyLeading: false,
title: Text(
FFLocalizations.of(context)
.getVariableText(enText: 'Vehicles', ptText: 'Veículos'),
style: FlutterFlowTheme.of(context).headlineMedium.override(
fontFamily: FlutterFlowTheme.of(context).headlineMediumFamily,
color: FlutterFlowTheme.of(context).primaryText,
return FutureBuilder<bool>(
future: _initializeModule(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return progressIndicator();
} else if (snapshot.hasError) {
return progressIndicator();
} else if (snapshot.hasData && snapshot.data == true) {
return _buildVehicleManager(context);
} else {
return _buildVehicleHistory(context);
}
},
);
}
Future<bool> _initializeModule() async {
try {
final module =
await LicenseRepositoryImpl().getModule('FRE-HUB-VEHICLES-MANAGER');
return await LicenseUtil.processModule(module);
} catch (e) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
context.pop();
await DialogUtil.errorDefault(navigatorKey.currentContext!);
});
return false;
}
}
void onEditingChanged([bool? value]) {
bool isFirst = _model.tabBarController.index == 0;
if (_model.isEditing & isFirst) {
_model.handleEditingChanged(false);
}
if (isFirst) {
setState(() {});
}
}
Widget _buildVehicleHistory(BuildContext context) {
return VehicleHistoryScreen(_model);
}
Widget _buildVehicleManager(BuildContext context) {
final vehicleHistoryScreenLabel = FFLocalizations.of(context)
.getVariableText(ptText: 'Consultar', enText: 'History');
final vehicleRegisterScreenLabel = FFLocalizations.of(context)
.getVariableText(ptText: 'Cadastrar', enText: 'Register');
final vehicleUpdateScreenLabel = FFLocalizations.of(context)
.getVariableText(ptText: 'Editar', enText: 'Edit');
return TabViewUtil(
context: context,
model: _model,
labelTab1: vehicleHistoryScreenLabel,
labelTab2: _model.isEditing
? vehicleUpdateScreenLabel
: vehicleRegisterScreenLabel,
controller: _model.tabBarController,
widget1: VehicleHistoryScreen(_model),
widget2: _model.isEditing
? VehicleUpdateScreen(_model)
: VehicleRegisterScreen(_model),
onEditingChanged: onEditingChanged,
);
}
/// -----------------------------------
/// [Header] of the page.
PreferredSizeWidget _buildHeader(BuildContext context) {
final theme = FlutterFlowTheme.of(context);
final backgroundColor = theme.primaryBackground;
final primaryText = theme.primaryText;
final title = FFLocalizations.of(context)
.getVariableText(enText: 'Vehicles', ptText: 'Veículos');
final titleStyle = theme.headlineMedium.override(
fontFamily: theme.headlineMediumFamily,
color: primaryText,
fontSize: 16.0,
fontWeight: FontWeight.bold,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).headlineMediumFamily),
),
),
leading: _backButton(context, FlutterFlowTheme.of(context)),
useGoogleFonts:
GoogleFonts.asMap().containsKey(theme.headlineMediumFamily),
);
final backButton = _backButton(context, theme);
return AppBar(
backgroundColor: backgroundColor,
automaticallyImplyLeading: false,
title: Text(title, style: titleStyle),
leading: backButton,
centerTitle: true,
elevation: 0.0,
actions: [],
@ -151,131 +186,22 @@ class _VehicleOnThePropertyState extends State<VehicleOnTheProperty>
}
Widget _backButton(BuildContext context, FlutterFlowTheme theme) {
final Icon icon = Icon(
Icons.keyboard_arrow_left,
color: theme.primaryText,
size: 30.0,
);
onPressed() => Navigator.of(context).pop();
return FlutterFlowIconButton(
key: ValueKey<String>('BackNavigationAppBar'),
borderColor: Colors.transparent,
borderRadius: 30.0,
borderWidth: 1.0,
buttonSize: 60.0,
icon: Icon(
Icons.keyboard_arrow_left,
color: theme.primaryText,
size: 30.0,
),
onPressed: () => Navigator.of(context).pop(),
icon: icon,
onPressed: onPressed,
);
}
Future<ApiCallResponse?> _fetchVisits() async {
try {
setState(() => _loading = true);
var response = await FreAccessWSGlobal.getVehiclesByProperty
.call(_pageNumber.toString());
final List<dynamic> vehicles = response.jsonBody['vehicles'] ?? [];
safeSetState(() => count = response.jsonBody['total_rows'] ?? 0);
if (vehicles.isNotEmpty) {
setState(() {
_wrap.addAll(vehicles);
_hasData = true;
_loading = false;
});
return response;
}
_showNoMoreDataSnackBar(context);
setState(() {
_hasData = false;
_loading = false;
});
return null;
} catch (e, s) {
DialogUtil.errorDefault(context);
LogUtil.requestAPIFailed(
"proccessRequest.php", "", "Consulta de Veículo", e, s);
setState(() {
_hasData = false;
_loading = false;
});
}
return null;
}
void _loadMore() {
if (_hasData == true) {
_pageNumber++;
_future = _fetchVisits();
}
}
void _showNoMoreDataSnackBar(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
FFLocalizations.of(context).getVariableText(
ptText: "Não há mais dados.",
enText: "No more data.",
),
style: TextStyle(
color: Colors.white,
fontSize: LimitedFontSizeUtil.getBodyFontSize(context),
),
),
duration: const Duration(seconds: 3),
backgroundColor: FlutterFlowTheme.of(context).primary,
),
);
}
Widget _item(BuildContext context, dynamic uItem) {
return CardItemTemplateComponentWidget(
imagePath: null,
labelsHashMap: {
'${FFLocalizations.of(context).getVariableText(ptText: "Placa", enText: "License Plate")}:':
uItem['licensePlate'] ?? '',
'${FFLocalizations.of(context).getVariableText(ptText: "Modelo", enText: "Model")}:':
uItem['model'] ?? '',
'${FFLocalizations.of(context).getVariableText(ptText: "Tag", enText: "Tag")}:':
uItem['tag'] ?? '',
},
statusHashMap: [],
onTapCardItemAction: () async {
await showDialog(
useSafeArea: true,
context: context,
builder: (context) {
return Dialog(
alignment: Alignment.center,
child: model.buildVehicleDetails(
item: uItem,
context: context,
model: model,
),
);
},
).whenComplete(() {
safeSetState(() {
_pageNumber = 1;
_wrap = [];
_future = _fetchVisits()
.then((value) => value!.jsonBody['vehicles'] ?? []);
});
}).catchError((e, s) {
DialogUtil.errorDefault(context);
LogUtil.requestAPIFailed(
"proccessRequest.php", "", "Consulta de Veículos", e, s);
safeSetState(() {
_hasData = false;
_loading = false;
});
});
},
);
}
/// -----------------------------------
}

View File

@ -6,7 +6,7 @@ import 'package:hub/flutter_flow/flutter_flow_theme.dart';
import 'package:hub/flutter_flow/internationalization.dart';
import 'package:hub/pages/vehicles_on_the_property/vehicles_on_the_property.dart';
class VisitsModel extends FlutterFlowModel<VehicleOnTheProperty> {
class VisitsModel extends FlutterFlowModel<VehiclePage> {
static VisitsModel? _instance;
VisitsModel._internal({this.onRefresh});

View File

View File

@ -1 +0,0 @@

1
lib/shared/enums.dart Normal file
View File

@ -0,0 +1 @@
export 'enums/enum_throw_exception.dart';

View File

@ -1 +0,0 @@
export 'enum_throw_exception.dart';

View File

@ -0,0 +1,3 @@
export 'extensions/dialog_extensions.dart';
export 'extensions/flutter_secure_storage_extensions.dart';
export 'extensions/string_extensions.dart';

View File

@ -1,3 +0,0 @@
export 'dialog_extensions.dart';
export 'flutter_secure_storage_extensions.dart';
export 'string_extensions.dart';

View File

@ -1,7 +1,7 @@
import 'dart:ui';
extension StringNullableExtensions on String? {
bool toBoolean() {
bool get toBoolean {
if (this == null) return false;
return this!.toLowerCase() == 'true';
}
@ -11,10 +11,16 @@ extension StringNullableExtensions on String? {
if (this == '') return true;
return false;
}
bool get isNotNullAndEmpty {
if (this == null) return false;
if (this == '') return false;
return true;
}
}
extension StringExtensions on String {
bool toBoolean() {
bool get toBoolean {
return toLowerCase() == 'true';
}
}

0
lib/shared/helpers.dart Normal file
View File

View File

@ -1 +0,0 @@

View File

@ -1 +0,0 @@

4
lib/shared/mixins.dart Normal file
View File

@ -0,0 +1,4 @@
export 'mixins/pegeable_mixin.dart';
export 'mixins/status_mixin.dart';
export 'mixins/switcher_mixin.dart';
export 'mixins/template_mixin.dart';

View File

@ -1,3 +0,0 @@
export 'pegeable_mixin.dart';
export 'status_mixin.dart';
export 'switcher_mixin.dart';

View File

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/utils/limited_text_size.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
extension PagedListViewExtension<PageKeyType, ItemType>
on PagedSliverList<PageKeyType, ItemType> {}
mixin Pageable<T extends StatefulWidget> on State<T> {
Expanded buildPaginatedListView<X, Y>(
String noDataFound,
PagingController<X, Y> pg,
Widget Function(BuildContext, Y, int) itemBuilder) {
final theme = FlutterFlowTheme.of(context);
return Expanded(
child: RefreshIndicator(
backgroundColor: theme.primaryBackground,
color: theme.primary,
onRefresh: () async => pg.refresh(),
child: PagedListView<X, Y>(
pagingController: pg,
builderDelegate: PagedChildBuilderDelegate<Y>(
animateTransitions: true,
itemBuilder: (context, item, index) =>
itemBuilder(context, item, index),
// noMoreItemsIndicatorBuilder: ,
newPageProgressIndicatorBuilder: (context) =>
buildLoadingIndicator(context),
firstPageProgressIndicatorBuilder: (context) =>
buildLoadingIndicator(context),
noItemsFoundIndicatorBuilder: (context) =>
buildNoDataFound(context, noDataFound),
// firstPageErrorIndicatorBuilder: (context) => const Placeholder(),
// newPageErrorIndicatorBuilder: (context) => const Placeholder(),
),
),
),
);
}
Future<void> fetchPage({
required Future<(bool, dynamic)> Function() dataProvider,
required void Function(dynamic data) onDataAvailable,
required void Function(dynamic data) onDataUnavailable,
required void Function(Object error, StackTrace stackTrace) onFetchError,
}) async {
try {
final (bool isDataAvailable, dynamic data) = await dataProvider();
if (isDataAvailable) {
onDataAvailable(data);
} else {
onDataUnavailable(data);
}
} catch (error, stackTrace) {
onFetchError(error, stackTrace);
}
}
void showNoMoreDataSnackBar(BuildContext context) {
final message = FFLocalizations.of(context).getVariableText(
ptText: "Não há mais dados.",
enText: "No more data.",
);
showSnackbarMessenger(context, message, true);
}
Widget buildNoDataFound(BuildContext context, String title) {
final headerFontSize = LimitedFontSizeUtil.getHeaderFontSize(context);
// final bodyFontSize = LimitedFontSizeUtil.getBodyFontSize(context);
return Expanded(
child: Center(
child: Text(
title,
style: TextStyle(
fontFamily: 'Nunito',
fontSize: headerFontSize,
),
),
),
);
}
Widget buildLoadingIndicator(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
FlutterFlowTheme.of(context).primary,
),
),
),
);
}
}

View File

@ -44,7 +44,7 @@ mixin Template {
),
icon: Icon(
Symbols.info_i_rounded,
color: Colors.black,
color: FlutterFlowTheme.of(context).primaryText,
),
)
];

0
lib/shared/services.dart Normal file
View File

View File

@ -1 +0,0 @@

15
lib/shared/utils.dart Normal file
View File

@ -0,0 +1,15 @@
export 'utils/biometric_util.dart';
export 'utils/device_util.dart';
export 'utils/dialog_util.dart';
export 'utils/image_util.dart';
export 'utils/limited_text_size.dart';
export 'utils/loading_util.dart';
export 'utils/log_util.dart';
export 'utils/path_util.dart';
export 'utils/share_util.dart';
export 'utils/snackbar_util.dart';
export 'utils/string_util.dart';
export 'utils/text_util.dart';
export 'utils/validator_util.dart';
export 'utils/webview_util.dart';
export 'utils/color_util.dart';

View File

@ -0,0 +1,48 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class ColorUtil {
static Color getContrastColor(Color a, Color b) {
double luminance(Color color) {
return (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255;
}
double contrastRatio(Color a, Color b) {
final lumA = luminance(a) + 0.05;
final lumB = luminance(b) + 0.05;
return lumA > lumB ? lumA / lumB : lumB / lumA;
}
if (contrastRatio(a, b) < 4.5) {
// Find a color with higher contrast within the same hue
final hsv = HSVColor.fromColor(a);
double hue = hsv.hue;
double saturation = hsv.saturation;
double brightness = hsv.value;
// Increase brightness to ensure higher contrast
brightness = brightness > 0.5 ? brightness - 0.5 : brightness + 0.5;
return HSVColor.fromAHSV(1.0, hue, saturation, brightness).toColor();
}
return b;
}
static Color getSelfContrastColor(Color color) {
final hsv = HSVColor.fromColor(color);
double hue = hsv.hue;
double saturation = hsv.saturation;
double brightness = hsv.value;
// Increase brightness to ensure higher contrast
brightness = brightness > 0.5 ? brightness - 0.5 : brightness + 0.5;
return HSVColor.fromAHSV(1.0, hue, saturation, brightness).toColor();
}
}
extension ColorUtilExtension on Color {
Color get highlight => ColorUtil.getSelfContrastColor(this);
}

View File

@ -0,0 +1,26 @@
import 'dart:developer';
class DateTimeUtil {
static Future<bool> processStartDate(String startDate) async {
try {
if (startDate.isEmpty) return true;
final start = DateTime.tryParse(startDate);
if (start == null) return false;
return DateTime.now().isAfter(start);
} catch (e) {
log('Error processing start date for module: $e');
}
return false;
}
static Future<bool> processExpirationDate(String expirationDate) async {
try {
if (expirationDate.isEmpty) return false;
final expiration = DateTime.tryParse(expirationDate);
return expiration != null && DateTime.now().isAfter(expiration);
} catch (e) {
log('Error processing expiration date for module: $e');
}
return false;
}
}

View File

@ -1,14 +0,0 @@
export 'biometric_util.dart';
export 'device_util.dart';
export 'dialog_util.dart';
export 'image_util.dart';
export 'limited_text_size.dart';
export 'loading_util.dart';
export 'log_util.dart';
export 'path_util.dart';
export 'share_util.dart';
export 'snackbar_util.dart';
export 'string_util.dart';
export 'text_util.dart';
export 'validator_util.dart';
export 'webview_util.dart';

View File

@ -0,0 +1,18 @@
import 'package:hub/features/module/index.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/utils/datetime_util.dart';
class LicenseUtil {
static Future<bool> processModule(String? module) async {
if (module == null) return false;
final moduleMap = await stringToMap(module);
final startDate = moduleMap['startDate'] ?? '';
final expirationDate = moduleMap['expirationDate'] ?? '';
final isStarted = await DateTimeUtil.processStartDate(startDate);
final isExpired = await DateTimeUtil.processExpirationDate(expirationDate);
if (isStarted && !isExpired)
return EnumDisplay.fromString(moduleMap["display"]) == EnumDisplay.active;
if (isExpired) return false;
return false;
}
}

18
lib/shared/widgets.dart Normal file
View File

@ -0,0 +1,18 @@
/// [Base]
library;
export 'widgets/page.dart';
export 'widgets/component.dart';
export 'widgets/screen.dart';
export 'widgets/model.dart';
export 'widgets/entity.dart';
/// [View]'s
export 'widgets/list_view.dart';
export 'widgets/read_view.dart';
export 'widgets/enhanced_carousel_view.dart';
export 'widgets/enhanced_list_view.dart';
export 'widgets/enhanced_search_view.dart';
/// [Component]'s
export 'widgets/text.dart';

View File

@ -1,4 +1,5 @@
part of 'widgets.dart';
import 'package:flutter/material.dart';
import 'package:hub/shared/widgets.dart';
/// [ComponentWidget]

View File

@ -1,28 +1,49 @@
part of 'widgets.dart';
import 'dart:developer';
class EnhancedCarouselView<T> extends StatelessWidget {
import 'package:flutter/material.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/utils.dart';
typedef EnhancedCarouselViewKey<T> = GlobalKey<_EnhancedCarouselViewState<T>>;
class EnhancedCarouselView<T> extends StatefulWidget {
final Future<List<T?>> Function() dataProvider;
final void Function(T, BuildContext) filter;
final Widget Function<T>(T? item) itemBuilder;
final void Function(T?, BuildContext) filter;
final Widget Function<T>(T? item, bool isSelected) itemBuilder;
final bool showIndicator;
const EnhancedCarouselView({
super.key,
required this.dataProvider,
required this.filter,
required this.itemBuilder,
this.showIndicator = false,
});
@override
_EnhancedCarouselViewState<T> createState() =>
_EnhancedCarouselViewState<T>();
}
class _EnhancedCarouselViewState<T> extends State<EnhancedCarouselView<T>> {
T? selectedCategory;
bool itemIsSelected(T item) {
return selectedCategory == item;
}
@override
Widget build(BuildContext context) {
final theme = FlutterFlowTheme.of(context);
final backgroundColor = theme.primary;
final overlayColor = WidgetStateProperty.all(Colors.transparent);
return Column(
return Stack(
children: [
Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
spacing: 20,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(15, 0, 50, 0),
@ -38,11 +59,13 @@ class EnhancedCarouselView<T> extends StatelessWidget {
),
),
FutureBuilder<List<T?>>(
future: dataProvider(),
future: widget.dataProvider(),
builder: (context, snapshot) {
if (!snapshot.hasData) return SizedBox();
final items =
snapshot.data!.map((item) => itemBuilder(item)).toList();
final items = snapshot.data!
.map((item) =>
widget.itemBuilder(item, itemIsSelected(item as T)))
.toList();
return SizedBox(
height: 130, // Set a specific height
child: CarouselView(
@ -54,15 +77,47 @@ class EnhancedCarouselView<T> extends StatelessWidget {
overlayColor: overlayColor,
padding: EdgeInsets.zero,
elevation: 0,
reverse: true,
shrinkExtent: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onTap: (index) => filter(snapshot.data![index] as T, context),
onTap: (index) async {
log('Selected: ${snapshot.data![index]}');
log('Selected Category: $selectedCategory');
final bool isSame =
itemIsSelected(snapshot.data![index]!);
setState(() {
if (isSame) {
selectedCategory = null;
} else {
selectedCategory = snapshot.data![index] as T;
}
});
if (isSame)
widget.filter(null, context);
else
widget.filter(snapshot.data![index] as T, context);
},
children: items,
),
);
}),
},
),
],
),
if (widget.showIndicator)
Positioned(
left: 0,
top: 50,
child: Icon(Icons.arrow_left, size: 30, color: Colors.grey),
),
if (widget.showIndicator)
Positioned(
right: 0,
top: 50,
child: Icon(Icons.arrow_right, size: 30, color: Colors.grey),
),
],
);
}

View File

@ -1,4 +1,10 @@
part of 'widgets.dart';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rx_bloc_list/rx_bloc_list.dart';
import 'package:rxdart/rxdart.dart';
/// [TypeDefs] ----------------------------------------------------
@ -206,6 +212,10 @@ class EnhancedListViewState<ItemType, HeaderType, FooterType, QueryType>
@override
Widget build(BuildContext context) {
final String defaultMessage = FFLocalizations.of(context).getVariableText(
ptText: 'Nenhum item encontrado',
enText: 'No items found',
);
final header = StreamBuilder<List<HeaderType>>(
stream: bloc.states.headerItems.cast<List<HeaderType>>(),
builder: (context, headerSnapshot) {
@ -245,7 +255,9 @@ class EnhancedListViewState<ItemType, HeaderType, FooterType, QueryType>
} else if (bodySnapshot.hasError) {
return EnhancedErrorWidget(error: bodySnapshot.error);
} else if (!bodySnapshot.hasData || bodySnapshot.data!.isEmpty) {
return const SizedBox.shrink();
return Center(
child: Text(defaultMessage),
);
} else {
return ListView.builder(
itemCount: bodySnapshot.data?.length ?? 0,

View File

@ -1,5 +1,3 @@
part of 'widgets.dart';
// class EnhancedSearchView<QueryType> extends StatelessWidget {
// const EnhancedSearchView({
// super.key,

View File

@ -1,3 +1 @@
part of 'widgets.dart';
abstract class Entity<T> {}

View File

@ -1,5 +1,3 @@
part of 'widgets.dart';
// typedef SearchKey = GlobalKey<EnhancedRemoteListViewState>;
// typedef Query<X extends Archive> = X?;

View File

@ -1,4 +1,4 @@
part of 'widgets.dart';
import 'package:flutter/material.dart';
class ModelWidget extends Widget {
const ModelWidget({super.key});

View File

@ -1,4 +1,6 @@
part of 'widgets.dart';
import 'package:flutter/widgets.dart';
import 'package:hub/shared/mixins/template_mixin.dart';
import 'package:hub/shared/widgets.dart';
/// [PageWidget]

View File

@ -1,6 +1,15 @@
part of 'widgets.dart';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
// Removed unnecessary import
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/utils/dialog_util.dart';
import 'package:hub/shared/widgets.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdfx/pdfx.dart';
import 'package:share_plus/share_plus.dart';
import 'package:http/http.dart' as http;
// typedef PDFViewerKey = GlobalKey<SfPdfViewerState>;
typedef ReadViewController = PdfController;
typedef DocumentType = PdfDocument;
@ -23,9 +32,11 @@ abstract interface class Viewer extends StatelessComponent {
class ReadView extends StatefulWidget {
final String url;
final String title;
final VoidCallback onError;
const ReadView({
super.key,
required this.onError,
required this.url,
required this.title,
});
@ -36,12 +47,22 @@ class ReadView extends StatefulWidget {
class ReadViewState extends State<ReadView> {
Future<ReadViewController> _initializePdf() async {
try {
final file = await downloadPdf(widget.url);
final Future<DocumentType> document = DocumentType.openFile(file.path);
return ReadViewController(document: document);
} catch (e) {
final message = FFLocalizations.of(context).getVariableText(
ptText: 'Erro ao baixar o PDF',
enText: 'Error downloading PDF',
);
return Future.error(message);
}
}
Future<File> downloadPdf(String url) async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final bytes = response.bodyBytes;
@ -52,42 +73,68 @@ class ReadViewState extends State<ReadView> {
} else {
throw Exception('Falha ao baixar o PDF');
}
} catch (e) {
final message = FFLocalizations.of(context).getVariableText(
ptText: 'Erro ao baixar o PDF',
enText: 'Error downloading PDF',
);
await throwError(message, e);
rethrow;
}
}
Future<void> throwError(String message, dynamic error) async {
log('$message: $error');
await DialogUtil.error(context, message)
.whenComplete(() => widget.onError());
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
_buildPDFViewer(),
buildShareButton(),
_buildPDFViewer(context),
buildShareButton(context),
],
);
}
Positioned buildShareButton() {
Positioned buildShareButton(BuildContext context) {
final theme = FlutterFlowTheme.of(context);
return Positioned(
bottom: 10,
right: 10,
child: IconButton(
icon: Icon(
Icons.share,
color: Colors.black,
color: theme.primaryText,
),
color: Colors.black,
color: theme.primaryText,
onPressed: onShare,
),
);
}
void onShare() async {
Future<void> onShare() async {
try {
final Uri uri = Uri.parse(widget.url);
final response = await http.get(uri);
if (response.statusCode == 200) {
final XFile xfile = XFile.fromData(response.bodyBytes,
name: '${widget.title}.pdf', mimeType: 'application/pdf');
await Share.shareXFiles([xfile], text: 'Confira este PDF!');
await Share.shareXFiles([xfile],
text: 'Confira este PDF!',
fileNameOverrides: ['${widget.title}.pdf']);
} else {
log('Erro ao baixar o arquivo: ${response.statusCode}');
throw Exception(
'Erro ao compartilhar o arquivo: ${response.statusCode}');
}
} catch (e) {
final message = FFLocalizations.of(context).getVariableText(
ptText: 'Erro ao compartilhar o arquivo',
enText: 'Error sharing file',
);
await throwError(message, e);
}
}
@ -104,12 +151,19 @@ class ReadViewState extends State<ReadView> {
);
}
Widget _buildPDFViewer() => Padding(
Widget _buildPDFViewer(BuildContext context) => Padding(
padding: EdgeInsets.all(10),
child: FutureBuilder<ReadViewController>(
future: _initializePdf(),
builder: (context, snapshot) {
if (!snapshot.hasData) return buildLoadingIndicator(context);
if (snapshot.error != null) {
return buildLoadingIndicator(context);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return buildLoadingIndicator(context);
}
return PdfView(
controller: snapshot.data!,
renderer: (PdfPage page) => page.render(
@ -126,6 +180,4 @@ class ReadViewState extends State<ReadView> {
);
}),
);
// Widget get progressIndicator => LoadingUtil.buildLoadingIndicator(context);
}

View File

@ -1,4 +1,6 @@
part of 'widgets.dart';
import 'package:flutter/material.dart';
import 'package:hub/shared/mixins/template_mixin.dart';
import 'package:hub/shared/widgets.dart';
abstract class ScreenWidget<T> extends Widget {
const ScreenWidget({super.key});

View File

@ -1,4 +1,4 @@
part of 'widgets.dart';
import 'package:auto_size_text/auto_size_text.dart';
class AutoText extends AutoSizeText {
const AutoText(

View File

@ -1,35 +0,0 @@
import 'dart:developer';
import 'dart:io';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/mixins/template_mixin.dart';
import 'package:hub/shared/utils/index.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdfx/pdfx.dart';
import 'package:share_plus/share_plus.dart';
import 'package:rx_bloc_list/rx_bloc_list.dart';
import 'package:rxdart/rxdart.dart';
import 'package:rx_bloc/rx_bloc.dart';
part 'widgets.rxb.g.dart';
/// [Base]
part 'page.dart';
part 'component.dart';
part 'screen.dart';
part 'model.dart';
part 'entity.dart';
/// [View]'s
part 'list_view.dart';
part 'read_view.dart';
part 'enhanced_carousel_view.dart';
part 'enhanced_list_view.dart';
part 'enhanced_search_view.dart';
/// [Component]'s
part 'text.dart';

View File

@ -1,108 +0,0 @@
// // dart format width=80
// // GENERATED CODE - DO NOT MODIFY BY HAND
// // **************************************************************************
// // Generator: RxBlocGeneratorForAnnotation
// // **************************************************************************
part of 'widgets.dart';
// /// Used as a contractor for the bloc, events and states classes
// /// @nodoc
// abstract class EnhancedListViewBlocType extends RxBlocTypeBase {
// EnhancedListViewEvents get events;
// EnhancedListViewStates get states;
// }
// /// [$EnhancedListViewBloc<BodyType, HeaderType, FooterType, QueryType>] extended by the [EnhancedListViewBloc<BodyType, HeaderType, FooterType, QueryType>]
// /// @nodoc
// abstract class $EnhancedListViewBloc<BodyType, HeaderType, FooterType, QueryType> extends RxBlocBase
// implements
// EnhancedListViewEvents,
// EnhancedListViewStates,
// EnhancedListViewBlocType {
// final _compositeSubscription = CompositeSubscription();
// /// Тhe [Subject] where events sink to by calling [loadBodyItems]
// final _$loadBodyItemsEvent = PublishSubject<({bool reset, dynamic query})>();
// /// Тhe [Subject] where events sink to by calling [loadHeaderItems]
// final _$loadHeaderItemsEvent = PublishSubject<void>();
// /// Тhe [Subject] where events sink to by calling [loadFooterItems]
// final _$loadFooterItemsEvent = PublishSubject<void>();
// /// The state of [bodyItems] implemented in [_mapToBodyItemsState]
// late final Stream<List<T>> _bodyItemsState = _mapToBodyItemsState();
// /// The state of [headerItems] implemented in [_mapToHeaderItemsState]
// late final Stream<List<H>> _headerItemsState = _mapToHeaderItemsState();
// /// The state of [footerItems] implemented in [_mapToFooterItemsState]
// late final Stream<List<F>> _footerItemsState = _mapToFooterItemsState();
// /// The state of [isLoading] implemented in [_mapToIsLoadingState]
// late final Stream<bool> _isLoadingState = _mapToIsLoadingState();
// /// The state of [errors] implemented in [_mapToErrorsState]
// late final Stream<String> _errorsState = _mapToErrorsState();
// @override
// void loadBodyItems({
// bool reset = false,
// dynamic query = null,
// }) =>
// _$loadBodyItemsEvent.add((
// reset: reset,
// query: query,
// ));
// @override
// void loadHeaderItems() => _$loadHeaderItemsEvent.add(null);
// @override
// void loadFooterItems() => _$loadFooterItemsEvent.add(null);
// @override
// Stream<List<T>> get bodyItems => _bodyItemsState;
// @override
// Stream<List<H>> get headerItems => _headerItemsState;
// @override
// Stream<List<F>> get footerItems => _footerItemsState;
// @override
// Stream<bool> get isLoading => _isLoadingState;
// @override
// Stream<String> get errors => _errorsState;
// Stream<List<T>> _mapToBodyItemsState();
// Stream<List<H>> _mapToHeaderItemsState();
// Stream<List<F>> _mapToFooterItemsState();
// Stream<bool> _mapToIsLoadingState();
// Stream<String> _mapToErrorsState();
// @override
// EnhancedListViewEvents get events => this;
// @override
// EnhancedListViewStates get states => this;
// @override
// void dispose() {
// _$loadBodyItemsEvent.close();
// _$loadHeaderItemsEvent.close();
// _$loadFooterItemsEvent.close();
// _compositeSubscription.dispose();
// super.dispose();
// }
// }
// // ignore: unused_element
// typedef _LoadBodyItemsEventArgs = ({bool reset, dynamic query});

View File

@ -3,8 +3,8 @@ name: hub
description: . # Descrição do projeto (adicione mais detalhes se necessário)
publish_to: "none" # Destino de publicação
# Versão do aplicativo
version: 1.3.5+24
version: 1.4.0+28
# Restrições de versão do SDK Dart
environment:
@ -113,7 +113,9 @@ dependencies:
url_launcher_platform_interface: 2.3.2
permission_handler: ^11.3.1
awesome_notifications: ^0.10.0
app_tracking_transparency: ^2.0.6
app_tracking_transparency: ^2.0.6+1
# dio: ^5.7.0
# crypto: ^3.0.5
freezed_annotation: ^2.4.4
package_info_plus: ^8.1.1
sliver_tools: ^0.2.12

30
scripts/httpie.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# Define the base URL for the API endpoint
BASE_URL="https://freaccess.com.br/freaccess/processRequest.php"
# Define common parameters
DEV_UUID="6b7b81849c115a8a"
USER_UUID="678aa05b0c2154.50583237"
CLI_ID="7"
ACTIVITY="getVehiclesByProperty"
PAGE_SIZE="10"
# Function to perform the HTTP request
perform_request() {
local page=$1
http --form POST "$BASE_URL" \
devUUID="$DEV_UUID" \
userUUID="$USER_UUID" \
cliID="$CLI_ID" \
atividade="$ACTIVITY" \
page="$page" \
pageSize="$PAGE_SIZE" \
--check-status \
--ignore-stdin \
--timeout=10
}
# Perform requests for pages 1 and 2
perform_request 1
perform_request 2

View File

@ -29,7 +29,7 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
await initializeApp();
await initializeBindings();
});
group('Test', () {