// ignore_for_file: use_build_context_synchronously import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import '../shared/model/province_model.dart'; import '../utils/app_logger_utils.dart'; import '../../feature/device_log/device_logs_model.dart'; import '../../feature/devices/device_model.dart'; import '../../feature/home/device_alias_model.dart'; import '../../feature/inter_family/group_detail/group_detail_model.dart'; import '../../feature/inter_family/groups/groups_model.dart'; import '../../feature/settings/device_notification_settings/device_notification_settings_model.dart'; import '../../feature/settings/profile/profile_model.dart'; import '../constant/app/api_path_constant.dart'; import '../shared/model/district_model.dart'; import '../shared/model/ward_model.dart'; import '../shared/shared_snack_bar.dart'; import '../constant/enums/app_route_enums.dart'; import 'language_services.dart'; import '../../feature/bell/bell_model.dart'; import '../cache/local_manager.dart'; import '../constant/app/app_constants.dart'; import '../constant/enums/local_keys_enums.dart'; import '../network/network_manager.dart'; class APIServices { Map headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', "Access-Control-Allow-Credentials": "false", "Access-Control-Allow-Headers": "Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale", 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE', }; Future> getHeaders() async { String? token = LocaleManager.instance.getStringValue(PreferencesKeys.TOKEN); Map headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', "Access-Control-Allow-Credentials": "false", "Access-Control-Allow-Headers": "Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,locale", 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE', 'Authorization': token, }; return headers; } Future executeApiCall(Future Function() apiCall, {T Function(dynamic)? parser, String errorMessage = 'Lỗi khi gọi API', T Function(int)? statusCodeHandler}) async { try { final response = await apiCall().timeout( Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT), onTimeout: () => throw TimeoutException('Yêu cầu hết thời gian'), ); if (statusCodeHandler != null && response is int) { return statusCodeHandler(response); } if (response is String && response != "") { if (parser != null) { try { return parser(jsonDecode(response)); } catch (e) { throw Exception('Lỗi parsing dữ liệu: $e'); } } return response as T; } else { throw Exception('Dữ liệu trả về rỗng'); } } catch (e, stackTrace) { AppLoggerUtils.error("Lỗi gọi API", e, stackTrace); throw Exception('$errorMessage: $e'); } } /// Most Used Function // Future execute(Future Function() apiCall) async { // try { // return await apiCall(); // } catch (e) { // AppLoggerUtils.error(e.toString()); // return Future.error(e); // } // } Future execute( BuildContext context, Future Function() apiCall, { bool checkMounted = true, }) async { try { // Quick connectivity check before attempting network calls. final conn = await Connectivity().checkConnectivity(); if (conn == ConnectivityResult.none) { AppLoggerUtils.warning('No network connectivity'); if (checkMounted && context.mounted) { showErrorTopSnackBarCustom(context, "Không có kết nối mạng"); } return Future.error(const SocketException('No network connectivity')); } return await apiCall(); } on SocketException catch (e, stackTrace) { // Network-related errors (DNS, timeout, host lookup...) AppLoggerUtils.warning('Network error when calling API: $e'); if (checkMounted && context.mounted) { AppLoggerUtils.error("Không có kết nối mạng"); } return Future.error(e); } catch (e, stackTrace) { // If widget was unmounted (e.g. background isolate), preserve previous behavior if (checkMounted && !context.mounted) { return Future.error('Widget not mounted'); } AppLoggerUtils.error("Lỗi hệ thống khi gọi API", e, stackTrace); if (checkMounted && context.mounted) { showErrorTopSnackBarCustom(context, "Lỗi hệ thống"); } return Future.error(e); } } Future login(String path, Map loginRequest) async { final url = Uri.https(ApplicationConstants.DOMAIN, path); final headers = await getHeaders(); final response = await http.post(url, headers: headers, body: jsonEncode(loginRequest)); return response.body; } Future sendNotificationToken(String token) async { String uid = await getUID(); Map body = {"user_id": uid, "app_token": token}; int statusCode = await NetworkManager.instance! .updateDataInServer(APIPathConstants.NOTIFICATION_TOKEN_PATH, body); return statusCode; } Future logOut(BuildContext context) async { showDialog( context: context, builder: (context) => AlertDialog( title: Text(appLocalization(context).log_out_content, textAlign: TextAlign.center), actions: [ TextButton( onPressed: () async { var url = Uri.https(ApplicationConstants.DOMAIN, APIPathConstants.LOGOUT_PATH); final headers = await NetworkManager.instance!.getHeaders(); final response = await http.post(url, headers: headers); if (response.statusCode == 200) { LocaleManager.instance .deleteStringValue(PreferencesKeys.UID); LocaleManager.instance .deleteStringValue(PreferencesKeys.TOKEN); LocaleManager.instance .deleteStringValue(PreferencesKeys.EXP); LocaleManager.instance .deleteStringValue(PreferencesKeys.ROLE); context.goNamed(AppRoutes.LOGIN.name); } else { showErrorTopSnackBarCustom( context, "Error: ${response.statusCode}"); } }, child: Text(appLocalization(context).log_out, style: const TextStyle(color: Colors.red)), ), TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text(appLocalization(context).cancel_button_content), ), ], )); } Future getUID() async { String uid = LocaleManager.instance.getStringValue(PreferencesKeys.UID); return uid; } Future getUserRole() async { String role = LocaleManager.instance.getStringValue(PreferencesKeys.ROLE); return role; } Future checkTheme() async { String theme = LocaleManager.instance.getStringValue(PreferencesKeys.THEME); return theme; } Future checkLanguage() async { String language = LocaleManager.instance.getStringValue(PreferencesKeys.LANGUAGE_CODE); return language; } Future getBellNotifications(String offset, String pageSize) async { final params = {"offset": offset, "page_size": pageSize}; return executeApiCall( () => NetworkManager.instance!.getDataFromServerWithParams( APIPathConstants.BELL_NOTIFICATIONS_PATH, params), parser: (json) => Bell.fromJson(json), errorMessage: 'Lỗi khi GET /${APIPathConstants.BELL_NOTIFICATIONS_PATH}', ); } Future updateStatusOfNotification(List notificationID) async { Map body = { "event_ids": notificationID, }; return executeApiCall( () => NetworkManager.instance!.updateDataInServer( APIPathConstants.BELL_UPDATE_READ_NOTIFICATIONS_PATH, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.BELL_NOTIFICATIONS_PATH}', ); } Future getUserDetail() async { String uid = await getUID(); return executeApiCall( () => NetworkManager.instance! .getDataFromServer('${APIPathConstants.USER_PATH}/$uid'), parser: (json) => User.fromJson(json), errorMessage: 'Lỗi khi GET /${APIPathConstants.USER_PATH}', ); } Future updateUserProfile(Map body) async { String uid = await getUID(); return executeApiCall( () => NetworkManager.instance!.updateDataInServer( "${APIPathConstants.USER_PROFILE_PATH}/$uid", body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.USER_PROFILE_PATH}', ); } Future updateUserPassword(Map body) async { String uid = await getUID(); // int statusCode = await NetworkManager.instance!.updateDataInServer( // "${APIPathConstants.USER_PATH}/$uid/password", body); return executeApiCall( () => NetworkManager.instance!.updateDataInServer( "${APIPathConstants.USER_PATH}/$uid/password", body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.USER_PATH}'); } Future> getAllSettingsNotificationOfDevices() async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer(APIPathConstants.DEVICE_NOTIFICATION_SETTINGS), parser: (json) => DeviceNotificationSettings.mapFromJson(json['data']).values.toList(), errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}', ); } Future updateDeviceNotificationSettings( String thingID, Map data) async { Map body = {"thing_id": thingID, "notifi_settings": data}; return executeApiCall( () => NetworkManager.instance!.updateDataInServer( APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}'); } Future> getDashBoardDevices() async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer(APIPathConstants.DASHBOARD_DEVICES), parser: (json) => DeviceWithAlias.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.DASHBOARD_DEVICES}', ); } Future setupDeviceNotification(String thingID, String deviceName) async { Map body = { "thing_id": thingID, "alias": deviceName, "notifi_settings": { "001": 1, "002": 1, "003": 1, "004": 1, "005": 1, "006": 1, "101": 1, "102": 1, "103": 1, "104": 1, } }; return executeApiCall( () => NetworkManager.instance!.updateDataInServer( APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}'); } Future> getAllProvinces() async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer(APIPathConstants.PROVINCES_PATH), parser: (json) => Province.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}'); } Future> getProvincesByName(String name) async { final params = {'name': name}; return executeApiCall( () => NetworkManager.instance! .getDataFromServerWithParams(APIPathConstants.PROVINCES_PATH, params), parser: (json) => Province.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$name', ); } Future getProvinceByID(String provinceID) async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer("${APIPathConstants.PROVINCES_PATH}/$provinceID"), parser: (json) => Province.fromJson(json['data']), errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$provinceID}', ); } Future> getAllDistricts(String provinceID) async { final params = {"parent": provinceID}; return executeApiCall( () => NetworkManager.instance! .getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params), parser: (json) => District.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH} by parentCode $provinceID', ); } Future> getDistrictsByName(String districtName) async { final params = {"name": districtName}; return executeApiCall( () => NetworkManager.instance! .getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params), parser: (json) => District.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH} by name $districtName', ); } Future getDistrictByID(String districtID) async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer("${APIPathConstants.DISTRICTS_PATH}/$districtID"), parser: (json) => District.fromJson(json['data']), errorMessage: 'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH}/$districtID', ); } Future> getAllWards(String districtID) async { final params = {'parent': districtID}; return executeApiCall( () => NetworkManager.instance! .getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params), parser: (json) => Ward.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.WARDS_PATH} by parent $districtID', ); } Future> getWardsByName(String wardName) async { final params = {"name": wardName}; return executeApiCall( () => NetworkManager.instance! .getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params), parser: (json) => Ward.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.WARDS_PATH} by name $wardName', ); } Future getWardByID(String wardID) async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer("${APIPathConstants.WARDS_PATH}/$wardID"), parser: (json) => Ward.fromJson(json['data']), errorMessage: 'Lỗi khi GET /${APIPathConstants.WARDS_PATH}/$wardID', ); } Future confirmFakeFireByUser(String thingID) async { Map body = { "state": 3, "note": "Người dùng xác nhận cháy giả!" }; return executeApiCall( () => NetworkManager.instance!.updateDataInServer( "${APIPathConstants.DEVICE_PATH}/$thingID", body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_PATH}/$thingID'); } Future> getOwnerDevices() async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer(APIPathConstants.DEVICE_PATH), parser: (json) => Device.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}', ); } Future> getOwnerDeviceByState( Map params) async { return executeApiCall( () => NetworkManager.instance! .getDataFromServerWithParams(APIPathConstants.DEVICE_PATH, params), parser: (json) => Device.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}', ); } Future createDeviceByAdmin(Map body) async { return executeApiCall( () => NetworkManager.instance! .createDataInServer(APIPathConstants.DEVICE_PATH, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_PATH}'); } Future registerDevice(Map body) async { return executeApiCall( () => NetworkManager.instance! .createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_REGISTER_PATH}'); } Future deleteDeviceByAdmin(String thingID) async { return executeApiCall( () => NetworkManager.instance! .deleteDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID"), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi DELETE /${APIPathConstants.DEVICE_PATH}/$thingID'); } Future unregisterDevice(Map body) async { return executeApiCall( () => NetworkManager.instance! .createDataInServer(APIPathConstants.DEVICE_UNREGISTER_PATH, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi DELETE /${APIPathConstants.DEVICE_UNREGISTER_PATH} by USER'); } Future getDeviceInformation(String thingID) async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID"), parser: (json) => Device.fromJson(json), errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}/$thingID', ); } Future> getAllGroups() async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer(APIPathConstants.ALL_GROUPS_PATH), parser: (json) => Group.fromJsonDynamicList(json['items']), errorMessage: 'Lỗi khi GET /${APIPathConstants.USER_PATH}', ); } Future createGroup(Map body) async { return executeApiCall( () => NetworkManager.instance! .createDataInServer(APIPathConstants.GROUPS_PATH, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi POST /${APIPathConstants.GROUPS_PATH}'); } Future updateGroup(Map body, String groupID) async { return executeApiCall( () => NetworkManager.instance!.updateDataInServer( "${APIPathConstants.GROUPS_PATH}/$groupID", body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.GROUPS_PATH}/$groupID'); } Future joinGroup(String groupID, Map body) async { return executeApiCall( () => NetworkManager.instance! .createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi POST /${APIPathConstants.JOIN_GROUP_PATH}'); } Future deleteGroup(String groupID) async { return executeApiCall( () => NetworkManager.instance! .deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID"), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID'); } Future getGroupDetail(String groupID) async { return executeApiCall( () => NetworkManager.instance! .getDataFromServer("${APIPathConstants.GROUPS_PATH}/$groupID"), parser: (json) => GroupDetail.fromJson(json), errorMessage: 'Lỗi khi GET /${APIPathConstants.GROUPS_PATH}/$groupID', ); } Future approveGroup(Map body) async { return executeApiCall( () => NetworkManager.instance! .createDataInServer(APIPathConstants.APPROVE_GROUP_PATH, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi POST /${APIPathConstants.APPROVE_GROUP_PATH}'); } Future deleteUserInGroup(String groupID, String userID) async { return executeApiCall( () => NetworkManager.instance!.deleteDataInServer( "${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID"), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID'); } Future deleteDeviceInGroup(String groupID, String thingID) async { return executeApiCall( () => NetworkManager.instance!.deleteDataInServer( "${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID"), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID'); } Future updateDeviceAlias(Map body) async { return executeApiCall( () => NetworkManager.instance!.updateDataInServer( APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}'); } Future addDeviceToGroup( String groupID, Map body) async { return executeApiCall( () => NetworkManager.instance!.createDataInServer( "${APIPathConstants.GROUPS_PATH}/$groupID/things", body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.GROUPS_PATH}/$groupID/things'); } Future updateOwnerDevice( String thingID, Map body) async { return executeApiCall( () => NetworkManager.instance!.updateDataInServer( "${APIPathConstants.DEVICE_PATH}/$thingID", body), statusCodeHandler: (statusCode) => statusCode, errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_PATH}/$thingID'); } Future getLogsOfDevice( String thingID, Map params) async { return executeApiCall( () => NetworkManager.instance!.getDataFromServerWithParams( APIPathConstants.DEVICE_LOGS_PATH, params), parser: (json) => DeviceLog.fromJson(json), errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_LOGS_PATH}', ); } }