import 'dart:developer'; 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 with _BaseVehiclePage, _VehicleHistoryScreenModel, _VehicleRegisterScreenModel, _VehicleUpdateScreenModel { /// [VehicleModel] is a singleton class that contains the business logic of the vehicle page. static VehicleModel? _instance = VehicleModel._internal(); VehicleModel._internal(); factory VehicleModel() => _instance ?? VehicleModel._internal(); static void resetInstance() => _instance = null; @override void initState(BuildContext context) { resetInstance(); initAsync(); tabBarController = TabController( vsync: Navigator.of(context), length: 2, ); textFieldFocusLicensePlate = FocusNode(); textFieldControllerLicensePlate = TextEditingController(); textFieldFocusColor = FocusNode(); textFieldControllerColor = TextEditingController(); textFieldFocusModel = FocusNode(); textFieldControllerModel = TextEditingController(); WidgetsBinding.instance.addPostFrameCallback((_) async { amountRegister = '0'; }); } @override void dispose() { tabBarController.dispose(); textFieldFocusLicensePlate!.dispose(); textFieldControllerLicensePlate!.dispose(); textFieldFocusColor!.dispose(); textFieldControllerColor!.dispose(); textFieldFocusModel!.dispose(); textFieldControllerModel!.dispose(); } final GlobalKey registerFormKey = GlobalKey(); final GlobalKey updateFormKey = GlobalKey(); Future initAsync() async { amountRegister = await StorageHelper().get(LocalsStorageKey.vehicleAmountRegister.key); } bool isFormValid(BuildContext context) { if (registerFormKey.currentState == null) return false; return registerFormKey.currentState!.validate(); } } /// [_BaseVehiclePage] is a mixin that contains the base logic of the vehicle page. mixin class _BaseVehiclePage { int count = 0; late final VehicleModel model; late final TabController tabBarController; // dynamic item; BuildContext context = navigatorKey.currentContext!; bool isEditing = false; String? vehicleId; ApiCallResponse? vehicleResponse; String? amountRegister = '0'; VoidCallback? onUpdateVehicle; VoidCallback? onRegisterVehicle; VoidCallback? safeSetState; FocusNode? textFieldFocusLicensePlate; TextEditingController? textFieldControllerLicensePlate; String? textControllerLicensePlateValidator( BuildContext context, String? value) { if (value == null || value.isEmpty) { return FFLocalizations.of(context).getVariableText( ptText: 'Placa é obrigatória', enText: 'License Plate is required', ); } // (ABC-1234) final brazilianPlateRegex = RegExp(r'^[A-Z]{3}\d{4}$'); // (ABC1D23) 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) { if (value == null || value.isEmpty) { return FFLocalizations.of(context).getVariableText( ptText: 'Cor é obrigatória', enText: 'Color is required', ); } return null; } FocusNode? textFieldFocusModel; TextEditingController? textFieldControllerModel; String? textControllerModelValidator(BuildContext contexnt, String? value) { if (value == null || value.isEmpty) { return FFLocalizations.of(context).getVariableText( ptText: 'Modelo é obrigatório', enText: 'Model is required', ); } return null; } void switchTab(int index) { tabBarController.animateTo(index); if (index == 0) handleEditingChanged(false); safeSetState?.call(); } void clearFields() async { textFieldControllerLicensePlate = TextEditingController(text: ''); textFieldControllerColor = TextEditingController(text: ''); textFieldControllerModel = TextEditingController(text: ''); } void handleEditingChanged(bool editing) { isEditing = editing; clearFields(); } void setEditForm(dynamic item) { if (item != null) { vehicleId = item['vehicleId']; log("setEditForm "); log("setEditForm -> ${item.toString()}"); textFieldControllerLicensePlate = TextEditingController( text: item != null ? item['licensePlate'] ?? '' : ''); textFieldControllerColor = TextEditingController(text: item != null ? item['color'] ?? '' : ''); textFieldControllerModel = TextEditingController(text: item != null ? item['model'] ?? '' : ''); } } } /// [_VehicleRegisterScreenModel] is a mixin that contains the business logic of the vehicle register page. mixin _VehicleRegisterScreenModel on _BaseVehiclePage { Future registerVehicle() async { final response = await PhpGroup.registerVehicle.call( licensePlate: textFieldControllerLicensePlate!.text, color: textFieldControllerColor!.text, model: textFieldControllerModel!.text, ); if (response.jsonBody['error'] == false) { await DialogUtil.success( context, FFLocalizations.of(context).getVariableText( ptText: 'Veículo cadastrado com sucesso', enText: 'Vehicle registered successfully', )).then((_) async { switchTab(0); }); } else { String errorMessage; try { errorMessage = response.jsonBody['message']; } catch (e) { errorMessage = FFLocalizations.of(context).getVariableText( ptText: 'Erro ao cadastrar veículo', enText: 'Error registering vehicle', ); } await DialogUtil.error(context, errorMessage); } } } /// [_VehicleUpdateScreenModel] is a mixin that contains the business logic of the vehicle update page. mixin _VehicleUpdateScreenModel on _BaseVehiclePage { Future updateVehicle() async { final response = await PhpGroup.updateVehicle.call( licensePlate: textFieldControllerLicensePlate!.text, color: textFieldControllerColor!.text, model: textFieldControllerModel!.text, vehicleId: vehicleId!, ); if (response.jsonBody['error'] == false) { await DialogUtil.success( context, FFLocalizations.of(context).getVariableText( ptText: 'Veículo atualizado com sucesso', enText: 'Vehicle updated successfully', )).then((_) async { switchTab(0); }); } else { String errorMessage; try { errorMessage = response.jsonBody['message']; } catch (e) { errorMessage = FFLocalizations.of(context).getVariableText( ptText: 'Erro ao atualizar veículo', enText: 'Error updating vehicle', ); } await DialogUtil.error(context, errorMessage); } } } /// [_VehicleHistoryScreenModel] is a mixin that contains the business logic of the vehicle history page. mixin _VehicleHistoryScreenModel on _BaseVehiclePage { Future?> generateStatusColorMap(dynamic uItem, int count) async { final autoApproval = await StorageHelper().get(LocalsStorageKey.vehicleAutoApproval.key); if(autoApproval.toBoolean == true) return null; final theme = FlutterFlowTheme.of(context); final localization = FFLocalizations.of(context); byLanguage(en, pt) => localization.getVariableText(enText: en, ptText: pt); final preFixStatusMap = { "APR_CREATE": { "text": byLanguage('Awaiting', 'Aguardando'), "color": theme.warning, }, "APR_DELETE": { "text": byLanguage('Awaiting', 'Aguardando'), "color": theme.warning, }, "APR_UPDATE": { "text": byLanguage('Awaiting', 'Aguardando'), "color": theme.warning, }, "AGU_CHANGE": { "text": byLanguage('Awaiting', 'Aguardando'), "color": theme.accent2, }, }; final vehicleStatusMap = { "ATI": { "text": byLanguage('Active', 'Ativo'), "color": theme.success, }, "INA": { "text": byLanguage('Inactive', 'Inativo'), "color": theme.accent2, }, "APR_CREATE": { "text": byLanguage('Creation', 'Criação'), "color": theme.success, }, "APR_DELETE": { "text": byLanguage('Deletion', 'Exclusão'), "color": theme.warning, }, "APR_UPDATE": { "text": byLanguage('Update', 'Atualização'), "color": theme.warning, }, "AGU_CHANGE": { "text": byLanguage('Correction', 'Correção'), "color": theme.accent2, }, }; final ownerStatusMap = { true: { "text": byLanguage('My Vehicle', 'Meu Veículo'), "color": theme.primaryText, }, false: { "text": byLanguage('', ''), "color": theme.accent2, }, }; final status = uItem['status']; final isOwner = uItem['isOwnerVehicle']; if (vehicleStatusMap.containsKey(status)) { if(count > 1) return { if (preFixStatusMap.containsKey(status)) preFixStatusMap[status]!['text'] as String: preFixStatusMap[status]!['color'] as Color, vehicleStatusMap[status]!['text'] as String: vehicleStatusMap[status]!['color'] as Color, // if (ownerStatusMap.containsKey(isOwner) && (status != 'ATI' || status != 'INA')) // ownerStatusMap[isOwner]!['text'] as String: ownerStatusMap[isOwner]!['color'] as Color }; if(status == 'ATI' || status == 'INA') return { vehicleStatusMap[status]!['text'] as String: vehicleStatusMap[status]!['color'] as Color, }; return { "${preFixStatusMap[status]!['text']} ${vehicleStatusMap[status]!['text']}": vehicleStatusMap[status]!['color'] as Color }; } return {}; } Future?> generateActionButtons(dynamic item) async { 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, ), ); final updateText = FFLocalizations.of(context) .getVariableText(ptText: 'Editar', enText: 'Edit'); final updateIcon = Icon(Icons.edit, color: iconButtonColor); Future updateOnPressed() async { context.pop(); isEditing = true; switchTab(1); 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 => await 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 processDeleteRequest(dynamic item) async { log('processDeleteRequest -> item[$item]'); return await PhpGroup.deleteVehicle.call( vehicleId: item['vehicleId'], licensePlate: item['licensePlate'], model: item['model'], color: item['color'], ) .then((value) { context.pop(value); context.pop(value); // ignore: unrelated_type_equality_checks if (value.jsonBody['error'] == true) { final String errorMsg = value.jsonBody['error_msg']; return showSnackbar( context, FFLocalizations.of(context).getVariableText( ptText: errorMsg, enText: 'Error deleting vehicle', ), true, ); // ignore: unrelated_type_equality_checks } return showSnackbar( context, FFLocalizations.of(context).getVariableText( enText: 'Success deleting vehicle', ptText: 'Succeso ao excluir veículo', ), false, ); }).catchError((err, stack) { context.pop(); return showSnackbar( context, FFLocalizations.of(context).getVariableText( enText: 'Error deleting vehicle', ptText: 'Erro ao excluir veículo', ), true, ); }); } Future processCancelRequest(String status, dynamic item) async { late final ApiCallResponse value; try { 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: break; } context.pop(value); context.pop(value); if (value.jsonBody['error'] == true) { final String errorMsg = value.jsonBody['error_msg'] ; return showSnackbar( context, FFLocalizations.of(context).getVariableText( ptText: errorMsg, enText: 'Error canceling request', ), true, ); } return showSnackbar( context, FFLocalizations.of(context).getVariableText( enText: 'Success canceling request', ptText: 'Succeso ao cancelar solicitação', ), false, ); } catch (err) { context.pop(); return showSnackbar( context, FFLocalizations.of(context).getVariableText( enText: 'Error canceling request', ptText: 'Erro ao cancelar solicitação', ), true, ); } } Future processCancelDeleteRequest(dynamic item) async { return await PhpGroup.deleteVehicle.call( vehicleId: item['vehicleId'], licensePlate: item['licensePlate'], model: item['model'], color: item['color'], ); } Future processCancelUpdateRequest(dynamic item) async { return await PhpGroup.deleteVehicle.call( vehicleId: item['vehicleId'], licensePlate: item['licensePlate'], model: item['model'], color: item['color'], ); } Future processCancelCreateRequest(dynamic item) async { return await PhpGroup.deleteVehicle.call( vehicleId: item['vehicleId'], licensePlate: item['licensePlate'], model: item['model'], color: item['color'], ); } Future> generateLabelsHashMap(dynamic item) async { 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(), }; } Future buildVehicleDetails({ required dynamic item, required BuildContext context, required VehicleModel model, required FreCardIcon? icon, }) async { final status = await generateStatusColorMap(item, 1); final buttons = await generateActionButtons(item); final labels = await generateLabelsHashMap(item); return DetailsComponentWidget( icon: icon, buttons: buttons, labelsHashMap: labels, statusHashMap: [status], ); } }