Files
BrandManager/lib/services/authentication_manager.dart
2026-01-19 12:09:57 +01:00

211 lines
5.1 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../models/device.dart';
import '../models/user_info.dart';
import '../models/static_talkgroup.dart';
import '../models/device_profile.dart';
import 'brandmeister_client.dart';
class AuthenticationManager extends ChangeNotifier {
static const String _tokenKey = 'apiToken';
late final FlutterSecureStorage _storage;
AuthenticationManager() {
_initStorage();
_loadToken();
}
void _initStorage() {
_storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
iOptions: IOSOptions(
accessibility: KeychainAccessibility.first_unlock,
),
webOptions: WebOptions(
dbName: 'bmmanager',
publicKey: 'bmmanager_public_key',
),
);
}
bool _isAuthenticated = false;
bool _isInitializing = true;
String _apiToken = '';
UserInfo? _userInfo;
BrandmeisterClient? _client;
bool get isAuthenticated => _isAuthenticated;
bool get isInitializing => _isInitializing;
String get apiToken => _apiToken;
UserInfo? get userInfo => _userInfo;
Future<void> _loadToken() async {
try {
final token = await _storage.read(key: _tokenKey);
if (token != null && token.isNotEmpty) {
_apiToken = token;
_client = BrandmeisterClient(apiToken: token);
// Try to verify the token is still valid
try {
_userInfo = await _client!.whoami();
_isAuthenticated = true;
} catch (e) {
// Token is invalid, clear it
await logout();
}
}
} catch (e) {
debugPrint('Error loading token: $e');
} finally {
_isInitializing = false;
notifyListeners();
}
}
Future<void> _saveToken(String token) async {
try {
await _storage.write(key: _tokenKey, value: token);
_apiToken = token;
_client = BrandmeisterClient(apiToken: token);
} catch (e) {
throw BrandmeisterError('Failed to save token: $e');
}
}
Future<UserInfo> verifyAndSaveToken(String token) async {
if (token.isEmpty) {
throw BrandmeisterError('Token cannot be empty');
}
final tempClient = BrandmeisterClient(apiToken: token);
final user = await tempClient.whoami();
await _saveToken(token);
_userInfo = user;
_isAuthenticated = true;
notifyListeners();
return user;
}
Future<void> logout() async {
try {
await _storage.delete(key: _tokenKey);
_apiToken = '';
_userInfo = null;
_isAuthenticated = false;
_client = null;
notifyListeners();
} catch (e) {
debugPrint('Error during logout: $e');
}
}
Future<void> refreshUserInfo() async {
if (_client == null) return;
try {
_userInfo = await _client!.whoami();
notifyListeners();
} catch (e) {
debugPrint('Error refreshing user info: $e');
rethrow;
}
}
// Device Operations
Future<List<Device>> getDevices() async {
if (_client == null || _userInfo == null) {
throw BrandmeisterError('Not authenticated');
}
if (_userInfo!.username == null) {
throw BrandmeisterError('Username not available');
}
return await _client!.getDevicesByCallsign(_userInfo!.username!);
}
Future<Device> getDevice(int dmrId) async {
if (_client == null) {
throw BrandmeisterError('Not authenticated');
}
return await _client!.getDevice(dmrId);
}
Future<DeviceProfile> getDeviceProfile(int dmrId) async {
if (_client == null) {
throw BrandmeisterError('Not authenticated');
}
return await _client!.getDeviceProfile(dmrId);
}
Future<List<StaticTalkgroup>> getTalkgroups(int dmrId) async {
if (_client == null) {
throw BrandmeisterError('Not authenticated');
}
return await _client!.getDeviceTalkgroups(dmrId);
}
Future<Map<String, String>> getAllTalkgroups() async {
if (_client == null) {
throw BrandmeisterError('Not authenticated');
}
return await _client!.getTalkgroups();
}
Future<void> linkTalkgroup({
required int talkgroupId,
required int dmrId,
required int timeslot,
}) async {
if (_client == null) {
throw BrandmeisterError('Not authenticated');
}
await _client!.addTalkgroup(
dmrId: dmrId,
talkgroupId: talkgroupId,
slot: timeslot,
);
}
Future<void> unlinkTalkgroup({
required String talkgroupId,
required int dmrId,
required String timeslot,
}) async {
if (_client == null) {
throw BrandmeisterError('Not authenticated');
}
final tgId = int.tryParse(talkgroupId);
final slot = int.tryParse(timeslot);
if (tgId == null || slot == null) {
throw BrandmeisterError('Invalid talkgroup ID or timeslot');
}
await _client!.removeTalkgroup(
dmrId: dmrId,
talkgroupId: tgId,
slot: slot,
);
}
Future<void> dropAutoStaticGroup(int dmrId) async {
if (_client == null) {
throw BrandmeisterError('Not authenticated');
}
await _client!.dropAutoStaticGroup(dmrId);
}
}