HTTP-клиент к /api/v1 с Sanctum-токеном (хранится в secure storage), модели зеркалят Laravel API Resources. Экраны: логин, список устройств с живым статусом, детальная страница устройства (история телеметрии, turn-on/turn-off/set-level с проверкой capability), списки зон и правил автоматизации (пока только чтение — формы создания/редактирования впереди).
84 lines
2.5 KiB
Dart
84 lines
2.5 KiB
Dart
import 'dart:io' show Platform;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
|
|
import '../models/user.dart';
|
|
import 'api_client.dart';
|
|
|
|
/// Session state + token persistence. The token is a Sanctum personal
|
|
/// access token, named per-device on the backend (see `/login`'s
|
|
/// `device_name`) so a user could later revoke one device without others.
|
|
class AuthService extends ChangeNotifier {
|
|
AuthService(this._apiClient);
|
|
|
|
final ApiClient _apiClient;
|
|
final _storage = const FlutterSecureStorage();
|
|
static const _tokenKey = 'auth_token';
|
|
|
|
User? _user;
|
|
bool _isLoading = true; // true until restoreSession() resolves once at startup
|
|
|
|
User? get user => _user;
|
|
bool get isLoading => _isLoading;
|
|
bool get isAuthenticated => _user != null;
|
|
|
|
/// Call once at app startup: restores a saved token and validates it
|
|
/// against `/me` — a saved token can be stale if it was revoked
|
|
/// server-side (e.g. logged out from another device's session list).
|
|
Future<void> restoreSession() async {
|
|
final token = await _storage.read(key: _tokenKey);
|
|
if (token == null) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
return;
|
|
}
|
|
|
|
_apiClient.setToken(token);
|
|
try {
|
|
final json = await _apiClient.get('/me');
|
|
_user = User.fromJson(json['data'] as Map<String, dynamic>);
|
|
} on ApiException {
|
|
await _storage.delete(key: _tokenKey);
|
|
_apiClient.setToken(null);
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> login(String email, String password) async {
|
|
final json = await _apiClient.post('/login', {
|
|
'email': email,
|
|
'password': password,
|
|
'device_name': _deviceName(),
|
|
});
|
|
|
|
final token = json['token'] as String;
|
|
await _storage.write(key: _tokenKey, value: token);
|
|
_apiClient.setToken(token);
|
|
_user = User.fromJson(json['user'] as Map<String, dynamic>);
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
try {
|
|
await _apiClient.post('/logout');
|
|
} on ApiException {
|
|
// Token may already be invalid server-side (expired/revoked) —
|
|
// still clear local state so the user isn't stuck logged in.
|
|
}
|
|
await _storage.delete(key: _tokenKey);
|
|
_apiClient.setToken(null);
|
|
_user = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
String _deviceName() {
|
|
if (kIsWeb) return 'web';
|
|
if (Platform.isIOS) return 'iOS device';
|
|
if (Platform.isAndroid) return 'Android device';
|
|
return Platform.operatingSystem;
|
|
}
|
|
}
|