Files
cacto 45b8733d22 Каркас приложения: авторизация, зоны/устройства/правила, управление устройствами
HTTP-клиент к /api/v1 с Sanctum-токеном (хранится в secure storage),
модели зеркалят Laravel API Resources. Экраны: логин, список устройств
с живым статусом, детальная страница устройства (история телеметрии,
turn-on/turn-off/set-level с проверкой capability), списки зон и правил
автоматизации (пока только чтение — формы создания/редактирования впереди).
2026-08-12 01:12:39 +05:00

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;
}
}