Merge branch 'develop' into feat/fd-465

This commit is contained in:
jantunesmessias 2025-02-20 08:48:32 -03:00
commit c0061eabe0
45 changed files with 2394 additions and 621 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();
@ -64,9 +67,7 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
// CachedNetworkImage.evictFromCache(widget.imagePath ?? '');
final double limitedBodyFontSize =
LimitedFontSizeUtil.getBodyFontSize(context);
return Material(
type: MaterialType.transparency,
child: Container(
return Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width,
maxHeight: MediaQuery.of(context).size.height,
@ -97,6 +98,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 +122,7 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
autofocus: false,
canRequestFocus: false,
readOnly: true,
initialValue: item.key,
obscureText: false,
decoration: InputDecoration(
isDense: true,
@ -122,7 +134,7 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
),
filled: true,
fillColor: item.value,
labelText: item.key,
// labelText: item.key,
labelStyle: FlutterFlowTheme.of(context)
.labelMedium
.override(
@ -131,8 +143,7 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
fontWeight: FontWeight.bold,
color: FlutterFlowTheme.of(context).info,
letterSpacing: 0.0,
useGoogleFonts:
GoogleFonts.asMap().containsKey(
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context)
.labelMediumFamily,
),
@ -145,8 +156,7 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
.labelMediumFamily,
color: FlutterFlowTheme.of(context).info,
letterSpacing: 0.0,
useGoogleFonts:
GoogleFonts.asMap().containsKey(
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context)
.labelMediumFamily,
),
@ -161,14 +171,15 @@ 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,
),
@ -284,7 +295,6 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
],
),
),
),
);
}
}

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

@ -75,6 +75,184 @@ 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();
}
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 = PhpGroup.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 = PhpGroup.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 = PhpGroup.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 = PhpGroup.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

@ -496,11 +496,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

@ -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

@ -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

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:developer';
import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
import 'package:hub/features/storage/index.dart';

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,5 +1,3 @@
import 'dart:developer';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
@ -29,7 +27,8 @@ Future<void> initializeApp() async {
Future<void> _initializeTracking() async {
log('Requesting tracking authorization...');
await AppTrackingTransparency.requestTrackingAuthorization();
log('Tracking authorization requested');
print('Tracking authorization requested');
}
Future<void> _initializeFirebase() async {
@ -51,12 +50,14 @@ void _initializeUrlStrategy() {
}
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 +73,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

@ -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 = PhpGroup.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/index.dart';
import 'package:hub/shared/utils/index.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 PhpGroup.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 PhpGroup.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 PhpGroup.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 PhpGroup.deleteVehicle.call(
vehicleId: item['vehicleId'],
licensePlate: item['licensePlate'],
model: item['model'],
color: item['color'],
);
}
Future<ApiCallResponse> processCancelUpdateRequest(dynamic item) async {
return await PhpGroup.deleteVehicle.call(
vehicleId: item['vehicleId'],
licensePlate: item['licensePlate'],
model: item['model'],
color: item['color'],
);
}
Future<ApiCallResponse> processCancelCreateRequest(dynamic item) async {
return await PhpGroup.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/index.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

@ -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';
}
}

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

@ -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

@ -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;
}
}

View File

@ -3,8 +3,9 @@ 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
publish_to: "none"
version: 1.4.0+27
# Restrições de versão do SDK Dart
environment:
@ -113,7 +114,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