Initial commit: Plan B Logistics Flutter app with dark mode and responsive design
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>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user