Implements complete refactor of Ionic Angular logistics app to Flutter/Dart with: - Svrnty dark mode console theme (Material Design 3) - Responsive layouts (mobile, tablet, desktop) following FRONTEND standards - CQRS API integration with Result<T> error handling - OAuth2/OIDC authentication support (mocked for initial testing) - Delivery route and delivery management features - Multi-language support (EN/FR) with i18n - Native integrations (camera, phone calls, maps) - Strict typing throughout codebase - Mock data for UI testing without backend Follows all FRONTEND style guides, design patterns, and conventions. App is running in dark mode and fully responsive across all device sizes. Co-Authored-By: Claude <noreply@anthropic.com>
119 lines
3.2 KiB
Dart
119 lines
3.2 KiB
Dart
import 'package:flutter_appauth/flutter_appauth.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:jwt_decoder/jwt_decoder.dart';
|
|
import '../models/user_profile.dart';
|
|
|
|
class AuthService {
|
|
static const String _tokenKey = 'auth_token';
|
|
static const String _refreshTokenKey = 'refresh_token';
|
|
|
|
final FlutterAppAuth _appAuth;
|
|
final FlutterSecureStorage _secureStorage;
|
|
|
|
AuthService({
|
|
FlutterAppAuth? appAuth,
|
|
FlutterSecureStorage? secureStorage,
|
|
}) : _appAuth = appAuth ?? const FlutterAppAuth(),
|
|
_secureStorage = secureStorage ?? const FlutterSecureStorage();
|
|
|
|
Future<AuthResult> login() async {
|
|
try {
|
|
final result = await _appAuth.authorizeAndExchangeCode(
|
|
AuthorizationTokenRequest(
|
|
'delivery-mobile-app',
|
|
'com.goutezplanb.delivery://callback',
|
|
discoveryUrl: 'https://auth.goutezplanb.com/realms/planb-internal/.well-known/openid-configuration',
|
|
scopes: const ['openid', 'profile', 'offline_access'],
|
|
promptValues: const ['login'],
|
|
),
|
|
);
|
|
|
|
// ignore: unnecessary_null_comparison
|
|
if (result == null) {
|
|
return const AuthResult.cancelled();
|
|
}
|
|
|
|
await _secureStorage.write(key: _tokenKey, value: result.accessToken ?? '');
|
|
if (result.refreshToken != null) {
|
|
await _secureStorage.write(key: _refreshTokenKey, value: result.refreshToken!);
|
|
}
|
|
|
|
return AuthResult.success(token: result.accessToken ?? '');
|
|
} catch (e) {
|
|
return AuthResult.error(error: e.toString());
|
|
}
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
await Future.wait([
|
|
_secureStorage.delete(key: _tokenKey),
|
|
_secureStorage.delete(key: _refreshTokenKey),
|
|
]);
|
|
}
|
|
|
|
Future<String?> getToken() async {
|
|
return await _secureStorage.read(key: _tokenKey);
|
|
}
|
|
|
|
Future<String?> getRefreshToken() async {
|
|
return await _secureStorage.read(key: _refreshTokenKey);
|
|
}
|
|
|
|
bool isTokenValid(String? token) {
|
|
if (token == null || token.isEmpty) return false;
|
|
try {
|
|
return !JwtDecoder.isExpired(token);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
UserProfile? decodeToken(String token) {
|
|
try {
|
|
final decodedToken = JwtDecoder.decode(token);
|
|
return UserProfile.fromJwtClaims(decodedToken);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<bool> isAuthenticated() async {
|
|
final token = await getToken();
|
|
return isTokenValid(token);
|
|
}
|
|
}
|
|
|
|
sealed class AuthResult {
|
|
const AuthResult();
|
|
|
|
factory AuthResult.success({required String token}) => _Success(token);
|
|
factory AuthResult.error({required String error}) => _Error(error);
|
|
const factory AuthResult.cancelled() = _Cancelled;
|
|
|
|
R when<R>({
|
|
required R Function(String token) success,
|
|
required R Function(String error) onError,
|
|
required R Function() cancelled,
|
|
}) {
|
|
return switch (this) {
|
|
_Success(:final token) => success(token),
|
|
_Error(:final error) => onError(error),
|
|
_Cancelled() => cancelled(),
|
|
};
|
|
}
|
|
}
|
|
|
|
final class _Success extends AuthResult {
|
|
final String token;
|
|
const _Success(this.token);
|
|
}
|
|
|
|
final class _Error extends AuthResult {
|
|
final String error;
|
|
const _Error(this.error);
|
|
}
|
|
|
|
final class _Cancelled extends AuthResult {
|
|
const _Cancelled();
|
|
}
|