todos-contra-el-fuego-mobile/lib/models/firesApi.dart
vjrj 1864e5b105 fix: resolve 78 strict analysis errors with explicit type annotations
Implemented systematic type safety improvements across the codebase to eliminate all 78 strict analysis errors:

## Phase 1: Quick Wins (Redux & Model Fixes)
- Added explicit type annotations to Redux reducer parameters
- Fixed copyWith() methods in FireNotification and YourLocation models with proper nullable types
- Fixed BasicLocation field initializers with type casts (num to double)
- Fixed context parameter inference in IntroPage and PrivacyPage
- Regenerated .g.dart files with build_runner

## Phase 2: JSON Deserialization
- Fixed homePage.dart Firebase message parameter casting (9 errors)
- Fixed firesApi.dart JSON parsing:
  - Added explicit casts for num to double/int conversions
  - Fixed array iteration with proper List<dynamic> casting
  - Added return type annotation to objectIdFromJson()
  - Fixed nested JSON access in getMonitoredAreas()
- Fixed globalFiresBottomStats.dart dynamic to int/String casting
- Fixed locationUtils.dart string assignments

## Phase 3: Config & Redux Middleware
- Fixed mainCommon.dart config map access with explicit String casts
- Fixed mainProd.dart Sentry DSN casting
- Fixed fetchDataMiddleware.dart:
  - Added Store<AppState>, String type annotations to createUser()
  - Added Function(YourLocation) type to subscribeViaApi callback
  - Added Exception type to catchError handler
- Fixed fireMapReducer.dart restoreStatusAfterSave parameter type
- Fixed persist files with Map<String, dynamic> type annotations

## Phase 4: Edge Cases
- Fixed slider.dart method return types (Widget sizeText, Widget warningText)
- Fixed mainDrawer.dart Key? parameter type annotation

Result: 78 errors → 0 errors
2026-03-06 10:47:12 +01:00

207 lines
6.8 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import '../globals.dart' as globals;
import '../objectIdUtils.dart';
import '../redux/actions.dart';
import 'appState.dart';
import 'falsePositiveTypes.dart';
class FiresApi {
late final Dio _dio;
FiresApi() {
_dio = Dio();
}
Future<String> createUser(
AppState state, String mobileToken, String lang) async {
final params = {
"token": state.firesApiKey,
"mobileToken": mobileToken,
"lang": lang
};
final String url = '${state.firesApiUrl}mobile/users';
try {
final response = await _dio.post(url, data: params);
if (response.statusCode == 200) {
return response.data['data']['userId'] as String;
} else {
throw "Unexpected error on create user";
}
} catch (e) {
throw "Error creating user: $e";
}
}
Future<List<YourLocation>> fetchYourLocations(AppState state) async {
final apiKey = state.firesApiKey;
final mobileToken = state.user.token;
final String url =
'${state.firesApiUrl}mobile/subscriptions/all/$apiKey/$mobileToken';
try {
final response = await _dio.get(url);
if (response.statusCode == 200) {
final dataSubscriptions =
response.data['data']['subscriptions'] as List<dynamic>;
List<YourLocation> subscribed = [];
for (int i = 0; i < dataSubscriptions.length; i++) {
var el = dataSubscriptions[i] as Map<String, dynamic>;
var lat = (el['location']['lat'] as num).toDouble();
var lon = (el['location']['lon'] as num).toDouble();
subscribed.add(YourLocation(
id: objectIdFromJson(el['_id']['_str'] as String),
lat: lat,
lon: lon,
subscribed: true,
distance: (el['distance'] as num).toInt()));
}
return subscribed;
} else {
throw "Unexpected error fetching your locations";
}
} catch (e) {
throw "Error fetching locations: $e";
}
}
Future<String> subscribe(AppState state, YourLocation loc) async {
final params = {
"token": state.firesApiKey,
"mobileToken": state.user.token,
"id": loc.id.hexString,
"lat": loc.lat,
"lon": loc.lon,
"distance": loc.distance
};
final String url = '${state.firesApiUrl}mobile/subscriptions';
try {
final response = await _dio.post(url, data: params);
if (response.statusCode == 200) {
return response.data['data']['subsId'] as String;
} else {
print(response.data);
throw "Unexpected error on subscribe";
}
} catch (e) {
throw "Error subscribing: $e";
}
}
Future<bool> unsubscribe(AppState state, String subsId) async {
final apiKey = state.firesApiKey;
final mobileToken = state.user.token;
final String url =
'${state.firesApiUrl}mobile/subscriptions/$apiKey/$mobileToken/$subsId';
try {
final response = await _dio.delete(url);
if (response.statusCode == 200) {
return true;
} else {
throw "Unexpected error on unsubscribe";
}
} catch (e) {
throw "Error unsubscribing: $e";
}
}
Future<UpdateFireMapStatsAction> getFiresInLocation(
{required AppState state,
required double lat,
required double lon,
required int distance}) async {
var url =
'${state.firesApiUrl}fires-in-full/${state.firesApiKey}/$lat/$lon/$distance';
if (globals.isDevelopment) print(url);
try {
final response = await _dio.get(url);
if (response.statusCode == 200) {
var resultDecoded = response.data;
int numFires = (resultDecoded['real'] as num).toInt();
List<dynamic> fires = resultDecoded['fires'] as List<dynamic>;
List<dynamic> falsePos = resultDecoded['falsePos'] as List<dynamic>;
List<dynamic> industries = resultDecoded['industries'] as List<dynamic>;
if (globals.isDevelopment) {
var firesCount = fires.length;
var industriesCount = industries.length;
var falsePosCount = falsePos.length;
print(
'(Pos: $lat, $lon) real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount');
}
return UpdateFireMapStatsAction(
numFires: numFires,
fires: fires,
falsePos: falsePos,
industries: industries);
} else
throw Exception('Wrong response trying to get fire data');
} catch (e) {
throw Exception('Error getting fires: $e');
}
}
Future<List<Polyline>> getMonitoredAreas({required AppState state}) async {
var url =
'${state.firesApiUrl}status/subs-public-union/${state.firesApiKey}';
var color = const Color(0xFF145A32);
try {
final response = await _dio.get(url);
if (response.statusCode == 200) {
var resultDecoded = response.data;
List<Polyline> union = [];
final multipolygon =
(json.decode(resultDecoded['data']['union']['value'] as String)
as Map<String, dynamic>)['geometry']['coordinates']
as List<dynamic>;
for (dynamic polygonDynamic in multipolygon) {
var polygon = polygonDynamic as List<dynamic>;
for (dynamic holeDynamic in polygon) {
var hole = holeDynamic as List<dynamic>;
List<LatLng> points = [];
for (dynamic pointDynamic in hole) {
var point = pointDynamic as List<dynamic>;
points.add(LatLng(
(point[1] as num).toDouble(), (point[0] as num).toDouble()));
}
union.add(Polyline(points: points, color: color, strokeWidth: 3.0));
}
}
return union;
} else
throw Exception('Wrong response trying to get fire data');
} catch (e) {
throw Exception('Error getting monitored areas: $e');
}
}
Future<bool> markFalsePositive(AppState state, String mobileToken,
String sealed, FalsePositiveType type) async {
final params = {
"token": state.firesApiKey,
"mobileToken": mobileToken,
"sealed": sealed,
"type": type.toString().split('.')[1]
};
final String url = '${state.firesApiUrl}mobile/falsepositive';
try {
final response = await _dio.post(url, data: params);
if (response.statusCode == 200) {
if (globals.isDevelopment) print(response.data['data']['upsert']);
return true;
} else {
debugPrint(response.data.toString());
return false;
}
} catch (e) {
debugPrint("Error marking false positive: $e");
return false;
}
}
}