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/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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/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/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/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/backend/api_requests/index.dart';
import 'package:hub/features/local/index.dart'; import 'package:hub/features/local/index.dart';
import 'package:hub/features/menu/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' as ff;
import 'package:hub/flutter_flow/index.dart'; import 'package:hub/flutter_flow/index.dart';
import 'package:hub/main.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:material_symbols_icons/symbols.dart';
import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:flutter_web_plugins/url_strategy.dart';
@ -27,7 +31,6 @@ import 'app_test.dart';
import 'fuzzer/fuzzer.dart'; import 'fuzzer/fuzzer.dart';
import 'package:patrol_finders/patrol_finders.dart'; import 'package:patrol_finders/patrol_finders.dart';
import 'package:integration_test/integration_test.dart';
export 'package:flutter_test/flutter_test.dart'; export 'package:flutter_test/flutter_test.dart';
export 'package:patrol/patrol.dart'; export 'package:patrol/patrol.dart';
@ -56,6 +59,7 @@ part 'storage_test.dart';
part 'utils_test.dart'; part 'utils_test.dart';
part 'welcome_test.dart'; part 'welcome_test.dart';
part 'vehicle_test.dart';
late PatrolTester $; late PatrolTester $;
@ -83,5 +87,9 @@ void main() {
LocalsTest.setLocal(); LocalsTest.setLocal();
LocalsTest.unlinkLocal(); 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/flutter_flow/flutter_flow_theme.dart';
import 'package:hub/shared/utils/limited_text_size.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 // ignore: must_be_immutable
class CustomInputUtil extends StatefulWidget { class CustomInputUtil extends StatefulWidget {
final TextEditingController? controller; final TextEditingController? controller;
@ -20,22 +29,25 @@ class CustomInputUtil extends StatefulWidget {
final String? Function(String?)? validator; final String? Function(String?)? validator;
final bool haveMaxLength; final bool haveMaxLength;
final void Function(String)? onChanged; final void Function(String)? onChanged;
final List<TextInputFormatter>? inputFormatters;
CustomInputUtil( CustomInputUtil({
{super.key, super.key,
this.controller, this.controller,
required this.labelText, required this.labelText,
required this.hintText, required this.hintText,
required this.suffixIcon, required this.suffixIcon,
this.autoFocus = false, this.autoFocus = false,
required this.focusNode, required this.focusNode,
this.onChanged, this.onChanged,
this.textInputAction = TextInputAction.next, this.textInputAction = TextInputAction.next,
this.keyboardType = TextInputType.text, this.keyboardType = TextInputType.text,
this.maxLength = 80, this.maxLength = 80,
this.validator, this.validator,
this.obscureText, this.obscureText,
required this.haveMaxLength}); this.inputFormatters,
required this.haveMaxLength,
});
@override @override
State<CustomInputUtil> createState() => _CustomInputUtilState(); State<CustomInputUtil> createState() => _CustomInputUtilState();
@ -152,6 +164,7 @@ class _CustomInputUtilState extends State<CustomInputUtil> {
keyboardType: widget.keyboardType, keyboardType: widget.keyboardType,
inputFormatters: [ inputFormatters: [
LengthLimitingTextInputFormatter(widget.maxLength), LengthLimitingTextInputFormatter(widget.maxLength),
if (widget.inputFormatters != null) ...widget.inputFormatters!
], ],
), ),
], ],

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.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/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_theme.dart';
import 'package:hub/flutter_flow/flutter_flow_util.dart'; import 'package:hub/flutter_flow/flutter_flow_util.dart';
@ -14,6 +15,7 @@ class DetailsComponentWidget extends StatefulWidget {
required this.labelsHashMap, required this.labelsHashMap,
required this.statusHashMap, required this.statusHashMap,
this.imagePath, this.imagePath,
this.icon,
this.onTapCardItemAction, this.onTapCardItemAction,
required this.buttons, required this.buttons,
}); });
@ -23,6 +25,7 @@ class DetailsComponentWidget extends StatefulWidget {
final String? imagePath; final String? imagePath;
final Future Function()? onTapCardItemAction; final Future Function()? onTapCardItemAction;
final List<Widget>? buttons; final List<Widget>? buttons;
final FreCardIcon? icon;
@override @override
State<DetailsComponentWidget> createState() => _DetailsComponentWidgetState(); State<DetailsComponentWidget> createState() => _DetailsComponentWidgetState();
@ -64,225 +67,232 @@ class _DetailsComponentWidgetState extends State<DetailsComponentWidget> {
// CachedNetworkImage.evictFromCache(widget.imagePath ?? ''); // CachedNetworkImage.evictFromCache(widget.imagePath ?? '');
final double limitedBodyFontSize = final double limitedBodyFontSize =
LimitedFontSizeUtil.getBodyFontSize(context); LimitedFontSizeUtil.getBodyFontSize(context);
return Material( return Container(
type: MaterialType.transparency, constraints: BoxConstraints(
child: Container( maxWidth: MediaQuery.of(context).size.width,
constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height,
maxWidth: MediaQuery.of(context).size.width, ),
maxHeight: MediaQuery.of(context).size.height, decoration: BoxDecoration(
), color: FlutterFlowTheme.of(context).primaryBackground,
decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(25.0)),
color: FlutterFlowTheme.of(context).primaryBackground, ),
borderRadius: const BorderRadius.all(Radius.circular(25.0)), child: SingleChildScrollView(
), child: Column(
child: SingleChildScrollView( mainAxisSize: MainAxisSize.max,
child: Column( mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max, children: [
mainAxisAlignment: MainAxisAlignment.start, SizedBox(height: MediaQuery.of(context).size.height * 0.02),
children: [ if (widget.imagePath != null && widget.imagePath != '')
SizedBox(height: MediaQuery.of(context).size.height * 0.02), Container(
if (widget.imagePath != null && widget.imagePath != '') width: MediaQuery.of(context).size.width * 0.3,
Container( height: MediaQuery.of(context).size.width * 0.3,
width: MediaQuery.of(context).size.width * 0.3, clipBehavior: Clip.antiAlias,
height: MediaQuery.of(context).size.width * 0.3, decoration: const BoxDecoration(
clipBehavior: Clip.antiAlias, shape: BoxShape.circle,
decoration: const BoxDecoration(
shape: BoxShape.circle,
),
child: CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 100),
fadeOutDuration: const Duration(milliseconds: 100),
imageUrl: widget.imagePath!,
fit: BoxFit.cover,
useOldImageOnUrlChange: true,
),
), ),
SizedBox(height: MediaQuery.of(context).size.height * 0.03), child: CachedNetworkImage(
Row( fadeInDuration: const Duration(milliseconds: 100),
children: statusLinkedHashMap.expand((linkedHashMap) { fadeOutDuration: const Duration(milliseconds: 100),
return linkedHashMap.entries imageUrl: widget.imagePath!,
.map((MapEntry<String, Color> item) { fit: BoxFit.cover,
return Expanded( useOldImageOnUrlChange: true,
child: Padding( ),
padding: EdgeInsets.symmetric( ),
horizontal: MediaQuery.of(context).size.width * 0.05, if (widget.icon != null && widget.icon != '')
), Container(
child: TextFormField( width: MediaQuery.of(context).size.width * 0.3,
autofocus: false, height: MediaQuery.of(context).size.width * 0.3,
canRequestFocus: false, clipBehavior: Clip.antiAlias,
readOnly: true, decoration: const BoxDecoration(
obscureText: false, shape: BoxShape.circle,
decoration: InputDecoration( ),
isDense: true, child: widget.icon!,
enabledBorder: OutlineInputBorder( ),
borderRadius: BorderRadius.circular(10.0), SizedBox(height: MediaQuery.of(context).size.height * 0.03),
borderSide: BorderSide( Row(
color: item.value, children: statusLinkedHashMap.expand((linkedHashMap) {
), return linkedHashMap.entries
), .map((MapEntry<String, Color> item) {
filled: true, return Expanded(
fillColor: item.value, child: Padding(
labelText: item.key, padding: EdgeInsets.symmetric(
labelStyle: FlutterFlowTheme.of(context) horizontal: MediaQuery.of(context).size.width * 0.05,
.labelMedium ),
.override( child: TextFormField(
fontFamily: FlutterFlowTheme.of(context) autofocus: false,
.labelMediumFamily, canRequestFocus: false,
fontWeight: FontWeight.bold, readOnly: true,
color: FlutterFlowTheme.of(context).info, initialValue: item.key,
letterSpacing: 0.0, obscureText: false,
useGoogleFonts: decoration: InputDecoration(
GoogleFonts.asMap().containsKey( isDense: true,
FlutterFlowTheme.of(context) enabledBorder: OutlineInputBorder(
.labelMediumFamily, borderRadius: BorderRadius.circular(10.0),
), borderSide: BorderSide(
fontSize: limitedBodyFontSize, color: item.value,
),
hintStyle: FlutterFlowTheme.of(context)
.labelMedium
.override(
fontFamily: FlutterFlowTheme.of(context)
.labelMediumFamily,
color: FlutterFlowTheme.of(context).info,
letterSpacing: 0.0,
useGoogleFonts:
GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context)
.labelMediumFamily,
),
fontSize: limitedBodyFontSize,
),
focusedBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
suffixIcon: Icon(
Icons.info,
color: FlutterFlowTheme.of(context).info,
), ),
), ),
style: FlutterFlowTheme.of(context) filled: true,
.bodyMedium fillColor: item.value,
// labelText: item.key,
labelStyle: FlutterFlowTheme.of(context)
.labelMedium
.override( .override(
fontFamily: FlutterFlowTheme.of(context) fontFamily: FlutterFlowTheme.of(context)
.bodyMediumFamily, .labelMediumFamily,
fontWeight: FontWeight.bold,
color: FlutterFlowTheme.of(context).info, color: FlutterFlowTheme.of(context).info,
letterSpacing: 0.0, letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey( useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).bodyMediumFamily, FlutterFlowTheme.of(context)
.labelMediumFamily,
), ),
fontSize: limitedBodyFontSize, fontSize: limitedBodyFontSize,
), ),
textAlign: TextAlign.center, hintStyle: FlutterFlowTheme.of(context)
maxLines: null, .labelMedium
keyboardType: TextInputType.name, .override(
validator: _model.textController1Validator fontFamily: FlutterFlowTheme.of(context)
.asValidator(context), .labelMediumFamily,
), color: FlutterFlowTheme.of(context).info,
), letterSpacing: 0.0,
); useGoogleFonts: GoogleFonts.asMap().containsKey(
}).toList(); FlutterFlowTheme.of(context)
}).toList(), .labelMediumFamily,
), ),
SizedBox(height: MediaQuery.of(context).size.height * 0.03), fontSize: limitedBodyFontSize,
ListView.builder( ),
shrinkWrap: true, focusedBorder: InputBorder.none,
itemCount: labelsLinkedHashMap.length, errorBorder: InputBorder.none,
physics: const NeverScrollableScrollPhysics(), focusedErrorBorder: InputBorder.none,
itemBuilder: (context, index) { suffixIcon: Icon(
String key = labelsLinkedHashMap.keys.elementAt(index); Icons.info,
String value = labelsLinkedHashMap[key]!; color: FlutterFlowTheme.of(context).info,
// return Text('key: $key, value: $value');
return TextFormField(
readOnly: true,
initialValue: value,
style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily:
FlutterFlowTheme.of(context).bodyMediumFamily,
color: FlutterFlowTheme.of(context).primaryText,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).bodyMediumFamily,
), ),
fontSize: limitedBodyFontSize,
), ),
decoration: InputDecoration( style: FlutterFlowTheme.of(context)
labelText: key, .labelMedium
filled: true, .override(
fillColor: FlutterFlowTheme.of(context).primaryBackground, fontFamily: FlutterFlowTheme.of(context)
border: OutlineInputBorder( .labelMediumFamily,
borderRadius: BorderRadius.circular(10.0), fontWeight: FontWeight.bold,
borderSide: BorderSide( color: FlutterFlowTheme.of(context).info,
color: FlutterFlowTheme.of(context) letterSpacing: 0.0,
.primaryBackground, // Change border color here useGoogleFonts: GoogleFonts.asMap().containsKey(
),
),
labelStyle: FlutterFlowTheme.of(context)
.labelMedium
.override(
fontFamily:
FlutterFlowTheme.of(context).labelMediumFamily, FlutterFlowTheme.of(context).labelMediumFamily,
color: FlutterFlowTheme.of(context).primaryText, ),
letterSpacing: 0.0, fontSize: limitedBodyFontSize,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).labelMediumFamily,
), ),
), textAlign: TextAlign.center,
hintStyle: FlutterFlowTheme.of(context) maxLines: null,
.labelMedium keyboardType: TextInputType.name,
.override( validator: _model.textController1Validator
fontFamily: .asValidator(context),
FlutterFlowTheme.of(context).labelMediumFamily,
color: FlutterFlowTheme.of(context).primaryText,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).labelMediumFamily,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
), ),
), ),
); );
}, }).toList();
}).toList(),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.03),
ListView.builder(
shrinkWrap: true,
itemCount: labelsLinkedHashMap.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
String key = labelsLinkedHashMap.keys.elementAt(index);
String value = labelsLinkedHashMap[key]!;
// return Text('key: $key, value: $value');
return TextFormField(
readOnly: true,
initialValue: value,
style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily:
FlutterFlowTheme.of(context).bodyMediumFamily,
color: FlutterFlowTheme.of(context).primaryText,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).bodyMediumFamily,
),
fontSize: limitedBodyFontSize,
),
decoration: InputDecoration(
labelText: key,
filled: true,
fillColor: FlutterFlowTheme.of(context).primaryBackground,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
labelStyle: FlutterFlowTheme.of(context)
.labelMedium
.override(
fontFamily:
FlutterFlowTheme.of(context).labelMediumFamily,
color: FlutterFlowTheme.of(context).primaryText,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).labelMediumFamily,
),
),
hintStyle: FlutterFlowTheme.of(context)
.labelMedium
.override(
fontFamily:
FlutterFlowTheme.of(context).labelMediumFamily,
color: FlutterFlowTheme.of(context).primaryText,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).labelMediumFamily,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(
color: FlutterFlowTheme.of(context)
.primaryBackground, // Change border color here
),
),
),
);
},
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
if (widget.buttons!.isNotEmpty || widget.buttons != null)
OverflowBar(
overflowAlignment: OverflowBarAlignment.center,
alignment: MainAxisAlignment.center,
overflowSpacing: 2,
spacing: 2,
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: widget.buttons!,
), ),
SizedBox(height: MediaQuery.of(context).size.height * 0.02), SizedBox(height: MediaQuery.of(context).size.height * 0.02),
if (widget.buttons!.isNotEmpty || widget.buttons != null) ],
OverflowBar(
overflowAlignment: OverflowBarAlignment.center,
alignment: MainAxisAlignment.center,
overflowSpacing: 2,
spacing: 2,
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: widget.buttons!,
),
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
],
),
), ),
), ),
); );

View File

@ -34,6 +34,7 @@ class RegisiterVistorTemplateComponentWidget extends StatefulWidget {
class _RegisiterVistorTemplateComponentWidgetState class _RegisiterVistorTemplateComponentWidgetState
extends State<RegisiterVistorTemplateComponentWidget> { extends State<RegisiterVistorTemplateComponentWidget> {
late RegisiterVistorTemplateComponentModel _model; late RegisiterVistorTemplateComponentModel _model;
final bool _isLoading = false;
final scaffoldKey = GlobalKey<ScaffoldState>(); final scaffoldKey = GlobalKey<ScaffoldState>();
bool _isVisitorRegistered = false; 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 @override
GetLicense getLicense = GetLicense(); GetLicense getLicense = GetLicense();
static GetProvSchedules getProvSchedules = GetProvSchedules(); 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 GetCategories getCategories = GetCategories();
static GetDocuments getDocuments = GetDocuments(); static GetDocuments getDocuments = GetDocuments();
} }

View File

@ -496,11 +496,11 @@ class ApiManager {
result = ApiCallResponse(null, {}, -1, exception: e); result = ApiCallResponse(null, {}, -1, exception: e);
} }
log('API Call: $callName'); print('API Call: $callName');
log('URL: $apiUrl'); print('URL: $apiUrl');
log('Headers: $headers'); print('Headers: $headers');
log('Params: $params'); print('Params: $params');
log('Response: ${result.jsonBody}'); print('Response: ${result.jsonBody}');
return result; return result;
} }
} }

View File

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

View File

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

View File

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

View File

@ -17,10 +17,6 @@ abstract class MenuLocalDataSource {
Future<void> handleMenu(EnumMenuItem item, EnumDisplay display, MenuEntry opt, Future<void> handleMenu(EnumMenuItem item, EnumDisplay display, MenuEntry opt,
List<MenuItem?> entries); List<MenuItem?> entries);
Future<bool> processStartDate(String startDate, MenuEntry entry);
Future<bool> processExpirationDate(String expirationDate, MenuEntry entry);
} }
class MenuLocalDataSourceImpl implements MenuLocalDataSource { class MenuLocalDataSourceImpl implements MenuLocalDataSource {
@ -92,30 +88,4 @@ class MenuLocalDataSourceImpl implements MenuLocalDataSource {
log('Error processing display for module ${opt.key}: $e'); 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/module/index.dart';
import 'package:hub/features/storage/index.dart'; import 'package:hub/features/storage/index.dart';
import 'package:hub/flutter_flow/custom_functions.dart'; import 'package:hub/flutter_flow/custom_functions.dart';
import 'package:hub/shared/utils/datetime_util.dart';
class MenuRepositoryImpl implements MenuRepository { class MenuRepositoryImpl implements MenuRepository {
final MenuLocalDataSource menuDataSource = MenuLocalDataSourceImpl(); final MenuLocalDataSource menuDataSource = MenuLocalDataSourceImpl();
@ -25,10 +26,9 @@ class MenuRepositoryImpl implements MenuRepository {
final display = EnumDisplay.fromString(licenseMap['display']); final display = EnumDisplay.fromString(licenseMap['display']);
final startDate = licenseMap['startDate'] ?? ''; final startDate = licenseMap['startDate'] ?? '';
final expirationDate = licenseMap['expirationDate'] ?? ''; final expirationDate = licenseMap['expirationDate'] ?? '';
final isStarted = final isStarted = await DateTimeUtil.processStartDate(startDate);
await menuDataSource.processStartDate(startDate, entry);
final isExpired = final isExpired =
await menuDataSource.processExpirationDate(expirationDate, entry); await DateTimeUtil.processExpirationDate(expirationDate);
if (isStarted && !isExpired) { if (isStarted && !isExpired) {
await menuDataSource.handleMenu(menuItem, display, entry, entries); await menuDataSource.handleMenu(menuItem, display, entry, entries);
} }

View File

@ -76,6 +76,16 @@ class MenuEntry implements BaseModule {
route: '/residentsOnThePropertyPage', route: '/residentsOnThePropertyPage',
types: [MenuEntryType.Property], 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( MenuEntry(
key: 'FRE-HUB-VEHICLES', key: 'FRE-HUB-VEHICLES',
icon: Icons.directions_car, icon: Icons.directions_car,

View File

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

View File

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

View File

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

View File

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

View File

@ -529,7 +529,7 @@ void setAppLanguage(BuildContext context, String language) =>
void setDarkModeSetting(BuildContext context, ThemeMode themeMode) => void setDarkModeSetting(BuildContext context, ThemeMode themeMode) =>
App.of(context).setThemeMode(themeMode); App.of(context).setThemeMode(themeMode);
void showSnackbar( void showSnackbarMessenger(
BuildContext context, BuildContext context,
String message, String message,
bool error, { bool error, {
@ -537,38 +537,47 @@ void showSnackbar(
int duration = 4, int duration = 4,
}) { }) {
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context)
SnackBar( .showSnackBar(showSnackbar(context, message, error));
content: Row( }
children: [
if (loading) SnackBar showSnackbar(
Padding( BuildContext context,
padding: const EdgeInsetsDirectional.only(end: 10.0), String message,
child: SizedBox( bool error, {
height: 20, bool loading = false,
width: 20, int duration = 4,
child: CircularProgressIndicator( }) {
color: FlutterFlowTheme.of(context).info, return SnackBar(
), content: Row(
children: [
if (loading)
Padding(
padding: const EdgeInsetsDirectional.only(end: 10.0),
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: FlutterFlowTheme.of(context).info,
), ),
), ),
Text(
message,
style: TextStyle(
color: FlutterFlowTheme.of(context).info,
fontSize: LimitedFontSizeUtil.getBodyFontSize(context),
),
), ),
], Text(
), message,
duration: Duration(seconds: duration), style: TextStyle(
backgroundColor: error color: FlutterFlowTheme.of(context).info,
? FlutterFlowTheme.of(context).error fontSize: LimitedFontSizeUtil.getBodyFontSize(context),
: FlutterFlowTheme.of(context).success, ),
behavior: SnackBarBehavior.floating, ),
shape: RoundedRectangleBorder( ],
borderRadius: BorderRadius.circular(30), ),
), duration: Duration(seconds: duration),
backgroundColor: error
? FlutterFlowTheme.of(context).error
: FlutterFlowTheme.of(context).success,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
), ),
); );
} }

View File

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

View File

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:app_tracking_transparency/app_tracking_transparency.dart'; import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart';
@ -29,7 +27,8 @@ Future<void> initializeApp() async {
Future<void> _initializeTracking() async { Future<void> _initializeTracking() async {
log('Requesting tracking authorization...'); log('Requesting tracking authorization...');
await AppTrackingTransparency.requestTrackingAuthorization(); await AppTrackingTransparency.requestTrackingAuthorization();
log('Tracking authorization requested');
print('Tracking authorization requested');
} }
Future<void> _initializeFirebase() async { Future<void> _initializeFirebase() async {
@ -51,12 +50,14 @@ void _initializeUrlStrategy() {
} }
Future<void> _initializeSystemSettings() async { Future<void> _initializeSystemSettings() async {
log('Initializing System Settings...'); print('Initializing System Settings...');
final crashlyticsInstance = FirebaseCrashlytics.instance;
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
final crashlyticsInstance = FirebaseCrashlytics.instance;
if (kDebugMode) { if (kDebugMode) {
log('Debug mode'); print('Debug mode');
await crashlyticsInstance.setCrashlyticsCollectionEnabled(false);
} else { } else {
log('Release mode'); log('Release mode');
@ -72,10 +73,22 @@ Future<void> _initializeSystemSettings() async {
// } // }
await crashlyticsInstance.setCrashlyticsCollectionEnabled(true); await crashlyticsInstance.setCrashlyticsCollectionEnabled(true);
// if (crashlyticsInstance.isCrashlyticsCollectionEnabled) { if (crashlyticsInstance.isCrashlyticsCollectionEnabled) {
FlutterError.onError = crashlyticsInstance.recordFlutterError; // Configura o tratamento de erros não capturados
log('Crashlytics enabled'); FlutterError.onError = crashlyticsInstance.recordFlutterError;
// }
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) { .then((message) {
if (message != null || message != '') { if (message != null || message != '') {
showSnackbar( showSnackbarMessenger(
context, context,
FFLocalizations.of(context).getVariableText( FFLocalizations.of(context).getVariableText(
enText: 'Successfully resolved visit', enText: 'Successfully resolved visit',
@ -308,7 +308,7 @@ class _LiberationHistoryWidgetState extends State<LiberationHistoryWidget> {
false, false,
); );
} else { } else {
showSnackbar(context, message, true); showSnackbarMessenger(context, message, true);
} }
}).whenComplete(() { }).whenComplete(() {
safeSetState(() { safeSetState(() {

View File

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

View File

@ -86,9 +86,9 @@ class _PetsPageWidgetState extends State<PetsPageWidget>
); );
} }
void onEditingChanged(bool value) { void onEditingChanged([bool? value]) {
setState(() { 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:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';

View File

@ -472,7 +472,7 @@ class ScheduleCompleteVisitPageModel
context.pop(value); context.pop(value);
if (value == false) { if (value == false) {
showSnackbar( showSnackbarMessenger(
context, context,
FFLocalizations.of(context).getVariableText( FFLocalizations.of(context).getVariableText(
enText: 'Error blocking visit', enText: 'Error blocking visit',
@ -481,7 +481,7 @@ class ScheduleCompleteVisitPageModel
true, true,
); );
} else if (value == true) { } else if (value == true) {
showSnackbar( showSnackbarMessenger(
context, context,
FFLocalizations.of(context).getVariableText( FFLocalizations.of(context).getVariableText(
enText: 'Success canceling visit', enText: 'Success canceling visit',
@ -492,7 +492,7 @@ class ScheduleCompleteVisitPageModel
} }
}).catchError((err, stack) { }).catchError((err, stack) {
context.pop(); context.pop();
showSnackbar( showSnackbarMessenger(
context, context,
FFLocalizations.of(context).getVariableText( FFLocalizations.of(context).getVariableText(
enText: 'Error blocking visit', 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,54 +1,558 @@
import 'package:flutter/material.dart'; import 'dart:developer';
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';
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(); static VehicleModel? _instance = VehicleModel._internal();
VehicleModel._internal(); VehicleModel._internal();
factory VehicleModel() => _instance ?? VehicleModel._internal(); factory VehicleModel() => _instance ?? VehicleModel._internal();
static void resetInstance() => _instance = null; static void resetInstance() => _instance = null;
dynamic item; final GlobalKey<FormState> registerFormKey = GlobalKey<FormState>();
final GlobalKey<FormState> updateFormKey = GlobalKey<FormState>();
@override @override
void initState(BuildContext context) { void initState(BuildContext context) {
log('VehicleModel -> initState');
resetInstance(); resetInstance();
initAsync(); 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 @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({ Future<void> initAsync() async {
required dynamic item, amountRegister =
required BuildContext context, await StorageHelper().get(LocalsStorageKey.vehicleAmountRegister.key);
required VehicleModel model, autoApproval =
}) { await StorageHelper().get(LocalsStorageKey.vehicleAutoApproval.key);
return DetailsComponentWidget( }
buttons: [],
labelsHashMap: Map<String, String>.from({ bool isFormValid(BuildContext context, GlobalKey<FormState> formKey) {
if (item['model'] != null && item['model'] != '') if (formKey.currentState == null) return false;
'${FFLocalizations.of(context).getVariableText(ptText: "Modelo", enText: "Model")}:': return formKey.currentState!.validate();
item['model'].toString().toUpperCase(), }
if (item['licensePlate'] != null && item['licensePlate'] != '') }
'${FFLocalizations.of(context).getVariableText(ptText: "Placa", enText: "License Plate")}:':
item['licensePlate'].toString().toUpperCase(), /// [_BaseVehiclePage] is a mixin that contains the base logic of the vehicle page.
if (item['color'] != null && item['color'] != '') mixin class _BaseVehiclePage {
'${FFLocalizations.of(context).getVariableText(ptText: "Cor", enText: "Color")}:': int count = 0;
item['color'].toString().toUpperCase(), late final TabController tabBarController;
if (item['personName'] != null && item['personName'] != '') // dynamic item;
'${FFLocalizations.of(context).getVariableText(ptText: "Proprietário", enText: "Owner")}:': late int vehicleId;
item['personName'].toString().toUpperCase(), BuildContext context = navigatorKey.currentContext!;
if (item['tag'] != null && item['tag'] != '') bool isEditing = false;
'${FFLocalizations.of(context).getVariableText(ptText: "Tag", enText: "Tag")}:': ApiCallResponse? vehicleResponse;
item['tag'].toString().toUpperCase(), String? amountRegister = '0';
}), late final String? autoApproval;
statusHashMap: [],
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(),
if (item['licensePlate'] != null && item['licensePlate'] != '')
'${FFLocalizations.of(context).getVariableText(ptText: "Placa", enText: "License Plate")}:':
item['licensePlate'].toString().toUpperCase(),
if (item['color'] != null && item['color'] != '')
'${FFLocalizations.of(context).getVariableText(ptText: "Cor", enText: "Color")}:':
item['color'].toString().toUpperCase(),
if (item['personName'] != null && item['personName'] != '')
'${FFLocalizations.of(context).getVariableText(ptText: "Proprietário", enText: "Owner")}:':
item['personName'].toString().toUpperCase(),
if (item['tag'] != null && item['tag'] != '')
'${FFLocalizations.of(context).getVariableText(ptText: "Tag", enText: "Tag")}:':
item['tag'].toString().toUpperCase(),
};
}
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/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.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/components/templates_components/card_item_template_component/card_item_template_component_widget.dart';
import 'package:hub/features/backend/index.dart'; import 'package:hub/features/backend/index.dart';
import 'package:hub/flutter_flow/flutter_flow_icon_button.dart'; import 'package:hub/features/module/index.dart';
import 'package:hub/flutter_flow/flutter_flow_theme.dart'; import 'package:hub/flutter_flow/index.dart';
import 'package:hub/flutter_flow/flutter_flow_util.dart';
import 'package:hub/pages/vehicles_on_the_property/vehicle_model.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/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/limited_text_size.dart';
import 'package:hub/shared/utils/log_util.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 { part 'vehicle_history_screen.dart';
const VehicleOnTheProperty({super.key}); 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 @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 { with TickerProviderStateMixin {
late ScrollController _scrollController;
int _pageNumber = 1;
bool _hasData = false;
bool _loading = false;
int count = 0; int count = 0;
late final VehicleModel model; late final VehicleModel _model;
late Future<void> _future;
List<dynamic> _wrap = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
model = createModel(context, () => VehicleModel()); _model = createModel(context, () => VehicleModel());
_future = _fetchVisits();
_scrollController = ScrollController() _model.updateOnChange = true;
..addListener(() { _model.onUpdateVehicle = () {
if (_scrollController.position.atEdge && safeSetState(() {
_scrollController.position.pixels != 0) { _model.clearFields();
_loadMore();
}
}); });
};
_model.onRegisterVehicle = () {
safeSetState(() {
_model.clearFields();
});
};
_model.safeSetState = () {
safeSetState(() {});
};
} }
@override // @override
void dispose() { // void dispose() {
_scrollController.dispose(); // super.dispose();
super.dispose(); // }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
late final limitedHeaderTextSize = final backgroundColor = FlutterFlowTheme.of(context).primaryBackground;
LimitedFontSizeUtil.getHeaderFontSize(context);
return Scaffold( return Scaffold(
backgroundColor: FlutterFlowTheme.of(context).primaryBackground, backgroundColor: backgroundColor,
appBar: _appBar(context), appBar: _buildHeader(context),
body: Column( body: _buildBody(context),
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,
),
);
} 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(
valueColor: AlwaysStoppedAnimation<Color>(
FlutterFlowTheme.of(context).primary,
),
),
),
)
].addToStart(const SizedBox(height: 0)),
),
); );
} }
PreferredSizeWidget _appBar(BuildContext context) { /// [Body] of the page.
FutureBuilder<bool> _buildBody(BuildContext context) {
Widget progressIndicator() {
return CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
FlutterFlowTheme.of(context).primary,
),
);
}
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(theme.headlineMediumFamily),
);
final backButton = _backButton(context, theme);
return AppBar( return AppBar(
backgroundColor: FlutterFlowTheme.of(context).primaryBackground, backgroundColor: backgroundColor,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: Text( title: Text(title, style: titleStyle),
FFLocalizations.of(context) leading: backButton,
.getVariableText(enText: 'Vehicles', ptText: 'Veículos'),
style: FlutterFlowTheme.of(context).headlineMedium.override(
fontFamily: FlutterFlowTheme.of(context).headlineMediumFamily,
color: FlutterFlowTheme.of(context).primaryText,
fontSize: 16.0,
fontWeight: FontWeight.bold,
letterSpacing: 0.0,
useGoogleFonts: GoogleFonts.asMap().containsKey(
FlutterFlowTheme.of(context).headlineMediumFamily),
),
),
leading: _backButton(context, FlutterFlowTheme.of(context)),
centerTitle: true, centerTitle: true,
elevation: 0.0, elevation: 0.0,
actions: [], actions: [],
@ -151,131 +186,22 @@ class _VehicleOnThePropertyState extends State<VehicleOnTheProperty>
} }
Widget _backButton(BuildContext context, FlutterFlowTheme theme) { 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( return FlutterFlowIconButton(
key: ValueKey<String>('BackNavigationAppBar'), key: ValueKey<String>('BackNavigationAppBar'),
borderColor: Colors.transparent, borderColor: Colors.transparent,
borderRadius: 30.0, borderRadius: 30.0,
borderWidth: 1.0, borderWidth: 1.0,
buttonSize: 60.0, buttonSize: 60.0,
icon: Icon( icon: icon,
Icons.keyboard_arrow_left, onPressed: onPressed,
color: theme.primaryText,
size: 30.0,
),
onPressed: () => Navigator.of(context).pop(),
); );
} }
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/flutter_flow/internationalization.dart';
import 'package:hub/pages/vehicles_on_the_property/vehicles_on_the_property.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; static VisitsModel? _instance;
VisitsModel._internal({this.onRefresh}); VisitsModel._internal({this.onRefresh});

View File

@ -1,7 +1,7 @@
import 'dart:ui'; import 'dart:ui';
extension StringNullableExtensions on String? { extension StringNullableExtensions on String? {
bool toBoolean() { bool get toBoolean {
if (this == null) return false; if (this == null) return false;
return this!.toLowerCase() == 'true'; return this!.toLowerCase() == 'true';
} }
@ -11,10 +11,16 @@ extension StringNullableExtensions on String? {
if (this == '') return true; if (this == '') return true;
return false; return false;
} }
bool get isNotNullAndEmpty {
if (this == null) return false;
if (this == '') return false;
return true;
}
} }
extension StringExtensions on String { extension StringExtensions on String {
bool toBoolean() { bool get toBoolean {
return toLowerCase() == 'true'; 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) description: . # Descrição do projeto (adicione mais detalhes se necessário)
publish_to: "none" # Destino de publicação publish_to: "none" # Destino de publicação
# Versão do aplicativo publish_to: "none"
version: 1.3.5+24
version: 1.4.0+27
# Restrições de versão do SDK Dart # Restrições de versão do SDK Dart
environment: environment:
@ -113,7 +114,9 @@ dependencies:
url_launcher_platform_interface: 2.3.2 url_launcher_platform_interface: 2.3.2
permission_handler: ^11.3.1 permission_handler: ^11.3.1
awesome_notifications: ^0.10.0 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 freezed_annotation: ^2.4.4
package_info_plus: ^8.1.1 package_info_plus: ^8.1.1
sliver_tools: ^0.2.12 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