flutter-freaccess-hub/lib/pages/vehicles_on_the_property/vehicle_model.dart

559 lines
18 KiB
Dart

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