343 lines
9.5 KiB
Dart
343 lines
9.5 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../config.dart';
|
|
|
|
import '../models/vehicle.dart';
|
|
import '../models/refuel.dart';
|
|
import '../models/service.dart';
|
|
|
|
class SessionManager extends ChangeNotifier {
|
|
static final SessionManager _instance = SessionManager._internal();
|
|
factory SessionManager() => _instance;
|
|
SessionManager._internal();
|
|
|
|
bool _loggedIn = false;
|
|
String? _token;
|
|
String? _email;
|
|
String? _name;
|
|
|
|
List<Vehicle> _vehicles = [];
|
|
List<Refuel> _refuels = [];
|
|
List<ServiceRecord> _services = [];
|
|
|
|
bool get isLoggedIn => _loggedIn;
|
|
String? get token => _token;
|
|
String? get email => _email;
|
|
String? get name => _name;
|
|
|
|
List<Vehicle> get vehicles => List.unmodifiable(_vehicles);
|
|
List<Refuel> get refuels => List.unmodifiable(_refuels);
|
|
List<ServiceRecord> get services => List.unmodifiable(_services);
|
|
Vehicle? get defaultVehicle {
|
|
try {
|
|
return _vehicles.firstWhere((v) => v.isDefault);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
final _prefs = SharedPreferencesAsync(); // ✅ New API
|
|
|
|
Future<void> init() async {
|
|
_token = await _prefs.getString('token');
|
|
_email = await _prefs.getString('email');
|
|
_name = await _prefs.getString('name');
|
|
|
|
if (_token != null) {
|
|
final valid = await _validateToken();
|
|
if (valid) {
|
|
_loggedIn = true;
|
|
await fetchVehicles();
|
|
await fetchRefuels();
|
|
await fetchServices();
|
|
notifyListeners();
|
|
} else {
|
|
await logout();
|
|
}
|
|
} else {
|
|
_loggedIn = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> login({
|
|
required String token,
|
|
required String email,
|
|
String? name,
|
|
}) async {
|
|
await _prefs.setString('token', token);
|
|
await _prefs.setString('email', email);
|
|
if (name != null) await _prefs.setString('name', name);
|
|
|
|
_token = token;
|
|
_email = email;
|
|
_name = name;
|
|
_loggedIn = true;
|
|
|
|
// Ensure we have the latest user info and that the token is valid
|
|
await _validateToken();
|
|
|
|
await fetchVehicles();
|
|
await fetchRefuels();
|
|
await fetchServices();
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
await _prefs.remove('token');
|
|
await _prefs.remove('email');
|
|
await _prefs.remove('name');
|
|
_token = null;
|
|
_email = null;
|
|
_name = null;
|
|
_vehicles.clear();
|
|
_refuels.clear();
|
|
_services.clear();
|
|
_loggedIn = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
Map<String, String> _authHeaders() => {
|
|
'Content-Type': 'application/json',
|
|
if (_token != null) 'Authorization': 'Bearer $_token',
|
|
};
|
|
|
|
Future<bool> _validateToken() async {
|
|
if (_token == null) return false;
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$apiBaseUrl/api/v1/user/me'),
|
|
headers: _authHeaders(),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
_email = data['email'] ?? _email;
|
|
_name = data['username'] ?? data['name'] ?? _name;
|
|
if (_email != null) await _prefs.setString('email', _email!);
|
|
if (_name != null) await _prefs.setString('name', _name!);
|
|
return true;
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
|
|
Future<void> fetchVehicles() async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$apiBaseUrl/api/v1/vehicles'),
|
|
headers: _authHeaders(),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final List<dynamic> data = jsonDecode(response.body);
|
|
_vehicles = data.map((e) => Vehicle.fromApi(e)).toList();
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {
|
|
// ignore for now
|
|
}
|
|
}
|
|
|
|
Future<void> addVehicle(Vehicle vehicle) async {
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('$apiBaseUrl/api/v1/vehicles'),
|
|
headers: _authHeaders(),
|
|
body: jsonEncode(vehicle.toApiMap()),
|
|
);
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
final data = jsonDecode(response.body);
|
|
_vehicles.add(Vehicle.fromApi(data));
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> updateVehicle(int index, Vehicle vehicle) async {
|
|
final id = _vehicles[index].id;
|
|
if (id == null) return;
|
|
try {
|
|
final response = await http.put(
|
|
Uri.parse('$apiBaseUrl/api/v1/vehicles/$id'),
|
|
headers: _authHeaders(),
|
|
body: jsonEncode(vehicle.toApiMap()),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
_vehicles[index] = Vehicle.fromApi(data);
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> removeVehicle(int index) async {
|
|
final id = _vehicles[index].id;
|
|
if (id == null) return;
|
|
try {
|
|
final response = await http.delete(
|
|
Uri.parse('$apiBaseUrl/api/v1/vehicles/$id'),
|
|
headers: _authHeaders(),
|
|
);
|
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
|
_vehicles.removeAt(index);
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> setDefaultVehicle(String? id) async {
|
|
if (id == null) {
|
|
// Unset default from any vehicle currently marked as default
|
|
for (var i = 0; i < _vehicles.length; i++) {
|
|
if (_vehicles[i].isDefault) {
|
|
await updateVehicle(i, _vehicles[i].copyWith(isDefault: false));
|
|
}
|
|
}
|
|
} else {
|
|
// Clear default flag on all other vehicles first
|
|
for (var i = 0; i < _vehicles.length; i++) {
|
|
final vehicle = _vehicles[i];
|
|
if (vehicle.isDefault && vehicle.id != id) {
|
|
await updateVehicle(i, vehicle.copyWith(isDefault: false));
|
|
}
|
|
}
|
|
// Finally mark the selected vehicle as default
|
|
final idx = _vehicles.indexWhere((v) => v.id == id);
|
|
if (idx != -1) {
|
|
await updateVehicle(idx, _vehicles[idx].copyWith(isDefault: true));
|
|
}
|
|
}
|
|
await fetchVehicles();
|
|
}
|
|
|
|
// Refuel records
|
|
Future<void> fetchRefuels() async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$apiBaseUrl/api/v1/refuels'),
|
|
headers: _authHeaders(),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final List<dynamic> data = jsonDecode(response.body);
|
|
_refuels = data.map((e) => Refuel.fromApi(e)).toList();
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> addRefuel(Refuel refuel) async {
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('$apiBaseUrl/api/v1/refuels'),
|
|
headers: _authHeaders(),
|
|
body: jsonEncode(refuel.toApiMap()),
|
|
);
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
final data = jsonDecode(response.body);
|
|
_refuels.add(Refuel.fromApi(data));
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> updateRefuel(String id, Refuel refuel) async {
|
|
try {
|
|
final response = await http.put(
|
|
Uri.parse('$apiBaseUrl/api/v1/refuels/$id'),
|
|
headers: _authHeaders(),
|
|
body: jsonEncode(refuel.toApiMap()),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
final idx = _refuels.indexWhere((r) => r.id == id);
|
|
if (idx != -1) {
|
|
_refuels[idx] = Refuel.fromApi(data);
|
|
notifyListeners();
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> removeRefuel(String id) async {
|
|
try {
|
|
final response = await http.delete(
|
|
Uri.parse('$apiBaseUrl/api/v1/refuels/$id'),
|
|
headers: _authHeaders(),
|
|
);
|
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
|
_refuels.removeWhere((r) => r.id == id);
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
// Service records
|
|
Future<void> fetchServices() async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$apiBaseUrl/api/v1/services'),
|
|
headers: _authHeaders(),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final List<dynamic> data = jsonDecode(response.body);
|
|
_services = data.map((e) => ServiceRecord.fromApi(e)).toList();
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<bool> addService(ServiceRecord service) async {
|
|
try {
|
|
final response = await http.post(
|
|
Uri.parse('$apiBaseUrl/api/v1/services'),
|
|
headers: _authHeaders(),
|
|
body: jsonEncode(service.toApiMap()),
|
|
);
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
final data = jsonDecode(response.body);
|
|
_services.add(ServiceRecord.fromApi(data));
|
|
notifyListeners();
|
|
return true;
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
|
|
Future<bool> updateService(String id, ServiceRecord service) async {
|
|
try {
|
|
final response = await http.put(
|
|
Uri.parse('$apiBaseUrl/api/v1/services/$id'),
|
|
headers: _authHeaders(),
|
|
body: jsonEncode(service.toApiMap()),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
final idx = _services.indexWhere((s) => s.id == id);
|
|
if (idx != -1) {
|
|
_services[idx] = ServiceRecord.fromApi(data);
|
|
notifyListeners();
|
|
}
|
|
return true;
|
|
}
|
|
} catch (_) {}
|
|
return false;
|
|
}
|
|
|
|
Future<void> removeService(String id) async {
|
|
try {
|
|
final response = await http.delete(
|
|
Uri.parse('$apiBaseUrl/api/v1/services/$id'),
|
|
headers: _authHeaders(),
|
|
);
|
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
|
_services.removeWhere((s) => s.id == id);
|
|
notifyListeners();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|