feat: Implements API-based user authentication

Replaces the mocked login flow with an actual API call to an authentication endpoint.

Updates the `AuthStruct` schema to align with the new API response, including fields for `accessToken`, `dbName`, `uid`, `name`, and `username`.

Introduces a `UserConnectedProvider` service for managing the storage and retrieval of authenticated user details, centralizing this logic and replacing prior direct local storage methods.

Integrates the new authentication process and user storage service into the login, reception, and profile pages for a unified experience.

Adjusts the splash screen duration to reflect the real-time nature of the authentication check.
This commit is contained in:
mandreshope 2025-07-25 17:06:36 +03:00
parent 4682be5b62
commit b5e83e5b7f
14 changed files with 537 additions and 92 deletions

View File

@ -1,7 +1,16 @@
import 'package:barcode_scanner/backend/schema/auth/auth_struct.dart';
import 'package:barcode_scanner/provider_container.dart';
import 'package:barcode_scanner/services/dio_service.dart';
import 'package:barcode_scanner/services/token_provider.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:multiple_result/multiple_result.dart';
class ApiCalls {
static DioService dioService = DioService(
tokenProvider: providerContainer.read(tokenProvider),
);
static Future<Map<String, dynamic>?> fetchProduct(String barcode) async {
final Dio dio = Dio(
BaseOptions(baseUrl: 'https://world.openfoodfacts.org'),
@ -26,4 +35,36 @@ class ApiCalls {
return null;
}
}
static Future<Result<AuthStruct, Error>> signIn({
required String email,
required String password,
}) async {
try {
final response = await dioService.post(
path: '/sign_in',
data: {
"params": {
"login": email,
"password": password,
"db": "bitnami_odoo",
},
},
);
if (response.statusCode == 200) {
final data = response.data;
if (data['result']['success'] == true) {
return Result.success(AuthStruct.fromJson(data['result']['data']));
} else {
return Result.error(Error(data['result']['success']));
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return Result.error(Error(response.statusMessage));
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return Result.error(Error(e));
}
}
}

View File

@ -1,4 +1,3 @@
import 'package:barcode_scanner/backend/schema/user/user_struct.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_struct.freezed.dart';
@ -7,9 +6,12 @@ part 'auth_struct.g.dart';
@Freezed(toJson: false)
abstract class AuthStruct with _$AuthStruct {
factory AuthStruct({
String? accessToken,
@JsonKey(name: 'access_token') String? accessToken,
@JsonKey(name: 'db_name') String? dbName,
int? uid,
String? refreshToken,
UserStruct? user,
String? name,
String? username,
}) = _AuthStruct;
factory AuthStruct.fromJson(Map<String, dynamic> json) =>

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AuthStruct {
String? get accessToken; String? get refreshToken; UserStruct? get user;
@JsonKey(name: 'access_token') String? get accessToken;@JsonKey(name: 'db_name') String? get dbName; int? get uid; String? get refreshToken; String? get name; String? get username;
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -27,16 +27,16 @@ $AuthStructCopyWith<AuthStruct> get copyWith => _$AuthStructCopyWithImpl<AuthStr
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthStruct&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.user, user) || other.user == user));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthStruct&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.dbName, dbName) || other.dbName == dbName)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.name, name) || other.name == name)&&(identical(other.username, username) || other.username == username));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,user);
int get hashCode => Object.hash(runtimeType,accessToken,dbName,uid,refreshToken,name,username);
@override
String toString() {
return 'AuthStruct(accessToken: $accessToken, refreshToken: $refreshToken, user: $user)';
return 'AuthStruct(accessToken: $accessToken, dbName: $dbName, uid: $uid, refreshToken: $refreshToken, name: $name, username: $username)';
}
@ -47,11 +47,11 @@ abstract mixin class $AuthStructCopyWith<$Res> {
factory $AuthStructCopyWith(AuthStruct value, $Res Function(AuthStruct) _then) = _$AuthStructCopyWithImpl;
@useResult
$Res call({
String? accessToken, String? refreshToken, UserStruct? user
@JsonKey(name: 'access_token') String? accessToken,@JsonKey(name: 'db_name') String? dbName, int? uid, String? refreshToken, String? name, String? username
});
$UserStructCopyWith<$Res>? get user;
}
/// @nodoc
@ -64,27 +64,18 @@ class _$AuthStructCopyWithImpl<$Res>
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = freezed,Object? refreshToken = freezed,Object? user = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = freezed,Object? dbName = freezed,Object? uid = freezed,Object? refreshToken = freezed,Object? name = freezed,Object? username = freezed,}) {
return _then(_self.copyWith(
accessToken: freezed == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
as String?,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
as String?,user: freezed == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
as UserStruct?,
as String?,dbName: freezed == dbName ? _self.dbName : dbName // ignore: cast_nullable_to_non_nullable
as String?,uid: freezed == uid ? _self.uid : uid // ignore: cast_nullable_to_non_nullable
as int?,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,username: freezed == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
as String?,
));
}
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserStructCopyWith<$Res>? get user {
if (_self.user == null) {
return null;
}
return $UserStructCopyWith<$Res>(_self.user!, (value) {
return _then(_self.copyWith(user: value));
});
}
}
@ -92,12 +83,15 @@ $UserStructCopyWith<$Res>? get user {
@JsonSerializable(createToJson: false)
class _AuthStruct implements AuthStruct {
_AuthStruct({this.accessToken, this.refreshToken, this.user});
_AuthStruct({@JsonKey(name: 'access_token') this.accessToken, @JsonKey(name: 'db_name') this.dbName, this.uid, this.refreshToken, this.name, this.username});
factory _AuthStruct.fromJson(Map<String, dynamic> json) => _$AuthStructFromJson(json);
@override final String? accessToken;
@override@JsonKey(name: 'access_token') final String? accessToken;
@override@JsonKey(name: 'db_name') final String? dbName;
@override final int? uid;
@override final String? refreshToken;
@override final UserStruct? user;
@override final String? name;
@override final String? username;
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@ -109,16 +103,16 @@ _$AuthStructCopyWith<_AuthStruct> get copyWith => __$AuthStructCopyWithImpl<_Aut
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthStruct&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.user, user) || other.user == user));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthStruct&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.dbName, dbName) || other.dbName == dbName)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.name, name) || other.name == name)&&(identical(other.username, username) || other.username == username));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,user);
int get hashCode => Object.hash(runtimeType,accessToken,dbName,uid,refreshToken,name,username);
@override
String toString() {
return 'AuthStruct(accessToken: $accessToken, refreshToken: $refreshToken, user: $user)';
return 'AuthStruct(accessToken: $accessToken, dbName: $dbName, uid: $uid, refreshToken: $refreshToken, name: $name, username: $username)';
}
@ -129,11 +123,11 @@ abstract mixin class _$AuthStructCopyWith<$Res> implements $AuthStructCopyWith<$
factory _$AuthStructCopyWith(_AuthStruct value, $Res Function(_AuthStruct) _then) = __$AuthStructCopyWithImpl;
@override @useResult
$Res call({
String? accessToken, String? refreshToken, UserStruct? user
@JsonKey(name: 'access_token') String? accessToken,@JsonKey(name: 'db_name') String? dbName, int? uid, String? refreshToken, String? name, String? username
});
@override $UserStructCopyWith<$Res>? get user;
}
/// @nodoc
@ -146,28 +140,19 @@ class __$AuthStructCopyWithImpl<$Res>
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = freezed,Object? refreshToken = freezed,Object? user = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = freezed,Object? dbName = freezed,Object? uid = freezed,Object? refreshToken = freezed,Object? name = freezed,Object? username = freezed,}) {
return _then(_AuthStruct(
accessToken: freezed == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
as String?,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
as String?,user: freezed == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
as UserStruct?,
as String?,dbName: freezed == dbName ? _self.dbName : dbName // ignore: cast_nullable_to_non_nullable
as String?,uid: freezed == uid ? _self.uid : uid // ignore: cast_nullable_to_non_nullable
as int?,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,username: freezed == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
as String?,
));
}
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserStructCopyWith<$Res>? get user {
if (_self.user == null) {
return null;
}
return $UserStructCopyWith<$Res>(_self.user!, (value) {
return _then(_self.copyWith(user: value));
});
}
}
// dart format on

View File

@ -7,9 +7,10 @@ part of 'auth_struct.dart';
// **************************************************************************
_AuthStruct _$AuthStructFromJson(Map<String, dynamic> json) => _AuthStruct(
accessToken: json['accessToken'] as String?,
accessToken: json['access_token'] as String?,
dbName: json['db_name'] as String?,
uid: (json['uid'] as num?)?.toInt(),
refreshToken: json['refreshToken'] as String?,
user: json['user'] == null
? null
: UserStruct.fromJson(json['user'] as Map<String, dynamic>),
name: json['name'] as String?,
username: json['username'] as String?,
);

View File

@ -5,6 +5,10 @@ import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final _errorProvider = StateProvider.autoDispose<String?>((ref) {
return null;
});
class LoginPage extends ConsumerStatefulWidget {
const LoginPage({super.key});
@ -14,11 +18,9 @@ class LoginPage extends ConsumerStatefulWidget {
class _LoginPageState extends ConsumerState<LoginPage> {
final TextEditingController email = TextEditingController(
text: "user@yopmail.com",
);
final TextEditingController password = TextEditingController(
text: "password",
text: "user@example.com",
);
final TextEditingController password = TextEditingController(text: "odoo");
final _formKey = GlobalKey<FormState>();
bool obscureText = true;
@ -127,6 +129,34 @@ class _LoginPageState extends ConsumerState<LoginPage> {
return null;
},
),
Consumer(
builder: (context, ref, child) {
final errorState = ref.watch(_errorProvider);
if (errorState == null) return SizedBox.shrink();
return Column(
children: [
SizedBox(height: 10),
Container(
width: double.maxFinite,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: AppTheme.of(context).error,
),
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 16,
),
child: Text(
errorState,
style: AppTheme.of(context).bodyMedium.copyWith(
color: AppTheme.of(context).white,
),
),
),
],
);
},
),
const SizedBox(height: 24),
/// Sign In button
@ -147,6 +177,10 @@ class _LoginPageState extends ConsumerState<LoginPage> {
SplashRoute().go(context);
});
},
onError: () {
ref.read(_errorProvider.notifier).state =
"Authentication failed: Access Denied";
},
);
},
text: 'Sign In',

View File

@ -1,3 +1,5 @@
import 'package:barcode_scanner/backend/api/api_calls.dart';
import 'package:barcode_scanner/backend/schema/auth/auth_struct.dart';
import 'package:barcode_scanner/backend/schema/user/user_struct.dart';
import 'package:barcode_scanner/services/secure_storage.dart';
import 'package:barcode_scanner/services/token_provider.dart';
@ -15,16 +17,21 @@ final loginPageModelProvider =
return LoginPageModel(
secureStorage: ref.read(sharedPrefsProvider),
tokenProvider: ref.read(tokenProvider),
userConnectedProvider: ref.read(userConnectedProvider),
);
});
class LoginPageModel extends StateNotifier<LoginPageState> {
/// Constructor initializes the TaskRepository using the provider reference.
LoginPageModel({required this.secureStorage, required this.tokenProvider})
: super(const LoginPageState());
LoginPageModel({
required this.secureStorage,
required this.tokenProvider,
required this.userConnectedProvider,
}) : super(const LoginPageState());
late FlutterSecureStorage secureStorage;
late TokenProvider tokenProvider;
final UserConnectedProvider userConnectedProvider;
Future<void> checkHasUserConnected() async {
try {
@ -56,28 +63,20 @@ class LoginPageModel extends StateNotifier<LoginPageState> {
}) async {
try {
state = state.copyWith(loading: true);
await Future.delayed(Duration(seconds: 5));
if (email == "user@yopmail.com" && password == "password") {
setTokenInLocal(
'token',
UserStruct(
id: '1',
firstName: 'User',
lastName: 'Anonymous',
email: 'user@yopmail.com',
),
);
state = state.copyWith(
loading: false,
status: LoginPageStateStatus.logOut,
);
onSuccess?.call();
} else {
state = state.copyWith(
loading: false,
status: LoginPageStateStatus.logOut,
);
}
final res = await ApiCalls.signIn(email: email, password: password);
res.when(
(auth) async {
setTokenInLocal(auth.accessToken ?? '', auth);
onSuccess?.call();
},
(error) {
state = state.copyWith(
loading: false,
status: LoginPageStateStatus.logOut,
);
onError?.call();
},
);
} catch (e) {
state = state.copyWith(
loading: false,
@ -88,12 +87,20 @@ class LoginPageModel extends StateNotifier<LoginPageState> {
}
}
Future<void> setTokenInLocal(String token, UserStruct user) async {
Future<void> setTokenInLocal(String token, AuthStruct auth) async {
await Future.wait([
tokenProvider.setToken(token),
tokenProvider.setRefreshToken(token),
user.setToLocalStorage(),
userConnectedProvider.set(
UserStruct(
id: auth.uid.toString(),
firstName: auth.name,
lastName: auth.name,
email: auth.username,
),
),
]);
state = state.copyWith(loading: false, status: LoginPageStateStatus.logged);
debugPrint(token);
}

View File

@ -13,20 +13,25 @@ final receptionPageModelProvider =
return ReceptionPageModel(
secureStorage: ref.read(sharedPrefsProvider),
tokenProvider: ref.read(tokenProvider),
userConnectedProvider: ref.read(userConnectedProvider),
);
});
class ReceptionPageModel extends StateNotifier<ReceptionPageState> {
/// Constructor initializes the TaskRepository using the provider reference.
ReceptionPageModel({required this.secureStorage, required this.tokenProvider})
: super(const ReceptionPageState());
ReceptionPageModel({
required this.secureStorage,
required this.tokenProvider,
required this.userConnectedProvider,
}) : super(const ReceptionPageState());
late FlutterSecureStorage secureStorage;
late TokenProvider tokenProvider;
final UserConnectedProvider userConnectedProvider;
Future getUserConnected() async {
state = state.copyWith(loading: true);
final user = await UserStruct(id: '1').getFromLocalStorage();
final user = await userConnectedProvider.get();
state = state.copyWith(user: user, loading: false);
}
}

View File

@ -1,6 +1,7 @@
import 'package:barcode_scanner/backend/objectbox/entities/product/product_entity.dart';
import 'package:barcode_scanner/backend/objectbox/objectbox_manager.dart';
import 'package:barcode_scanner/backend/schema/user/user_struct.dart';
import 'package:barcode_scanner/services/token_provider.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -13,20 +14,24 @@ final profilePageModelProvider =
StateNotifierProvider.autoDispose<ProfilePageModel, ProfilePageState>((
ref,
) {
return ProfilePageModel();
return ProfilePageModel(
userConnectedProvider: ref.read(userConnectedProvider),
);
});
class ProfilePageModel extends StateNotifier<ProfilePageState> {
/// Constructor initializes the TaskRepository using the provider reference.
ProfilePageModel() : super(const ProfilePageState());
ProfilePageModel({required this.userConnectedProvider})
: super(const ProfilePageState());
final productStore = objectboxManager.store.box<ProductEntity>();
final UserConnectedProvider userConnectedProvider;
Future getMe({Function(UserStruct? value)? onSuccess}) async {
state = state.copyWith(loading: true);
final me = await UserStruct(id: '1').getFromLocalStorage();
onSuccess?.call(me);
state = state.copyWith(user: me, loading: false);
final user = await userConnectedProvider.get();
onSuccess?.call(user);
state = state.copyWith(user: user, loading: false);
}
}

View File

@ -11,7 +11,7 @@ class SplashPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
Future.delayed(Durations.extralong2).then((value) {
Future.delayed(Durations.short1).then((value) {
final authViewModel = ref.watch(loginPageModelProvider);
if (authViewModel.status.isLogged) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {

View File

@ -0,0 +1,319 @@
import 'dart:io';
import 'dart:async';
import 'dart:convert';
import 'package:barcode_scanner/services/token_provider.dart';
import 'package:barcode_scanner/utils/app_constants.dart';
import 'package:barcode_scanner/utils/utils.dart';
import 'package:dio/io.dart';
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
class DioService {
DioService({bool addAuthorization = false, required this.tokenProvider}) {
_options = BaseOptions(
baseUrl: AppConstants.baseUrl,
connectTimeout: const Duration(seconds: 45),
receiveTimeout: const Duration(seconds: 45),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
'DNT': '1',
'Referer': AppConstants.domain
},
);
_dio = Dio(_options);
//check bad certificate
// ignore: deprecated_member_use
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(HttpClient client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
};
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
final token = await tokenProvider.getToken();
if (token != null) {
options.headers.addAll({"Authorization": "Bearer $token"});
debugPrint('Authorization ${options.headers["Authorization"]}');
final url = Uri.parse(
"${options.baseUrl}${options.path}",
).replace(queryParameters: options.queryParameters).toString();
debugPrint('REQUEST[${options.method}] => PATH: $url');
debugPrint(
'REQUEST[${options.method}] => QUERY PARAMS: ${options.queryParameters}',
);
}
// Do something before request is sent
return handler.next(options); //continue
// If you want to resolve the request with some custom data
// you can resolve a `Response` object eg: `handler.resolve(response)`.
// If you want to reject the request with a error message,
// you can reject a `DioError` object eg: `handler.reject(dioError)`
},
onResponse: (response, handler) {
debugPrint(
'RESPONSE[${response.statusCode}] => DATA: ${jsonEncode(response.data)}',
);
// Do something with response data
return handler.next(response); // continue
// If you want to reject the request with a error message,
// you can reject a `DioError` object eg: `handler.reject(dioError)`
},
onError: (DioException e, handler) async {
debugPrint(
'ERROR[$e] => PATH: ${e.requestOptions.baseUrl}${e.requestOptions.path}',
);
// Handling global errors (401 Unauthorized)
if (e.response?.statusCode == 401) {
debugPrint("Token expired !!!");
await tokenProvider.deleteToken();
// final refreshToken = await tokenProvider.getRefreshToken();
// try {
// final response = await _dio.post(
// AuthEndPoint.refreshToken,
// data: {"refresh_token": refreshToken},
// );
// final newAccessToken = response.data["accessToken"];
// final newRefreshToken = response.data["refreshToken"];
// await tokenProvider.setToken(newAccessToken);
// await tokenProvider.setRefreshToken(newRefreshToken);
// // Réessayer la requête initiale avec le nouveau token
// e.requestOptions.headers["Authorization"] =
// "Bearer $newAccessToken";
// final retryResponse = await _dio.fetch(e.requestOptions);
// return handler.resolve(retryResponse);
// } catch (e) {
// // Échec du refresh, l'utilisateur doit se reconnecter
// await tokenProvider.deleteToken();
// }
}
// Do something with response error
return handler.next(e); //continue
// If you want to resolve the request with some custom data
// you can resolve a `Response` object eg: `handler.resolve(response)`.
},
),
);
}
late Dio _dio;
late BaseOptions _options;
late TokenProvider tokenProvider;
Future<Response<dynamic>> post({
required String path,
Map<String, dynamic>? data,
bool isFormData = false,
}) async {
late Response<dynamic> response;
if (await checkInternetConnexion()) {
try {
response = await _dio.post(
path,
data: data != null
? (isFormData ? FormData.fromMap(data) : data)
: null,
);
} catch (error) {
_exception(error);
}
} else {
throw Exception("Vous n'êtes pas connecté à Internet");
}
return response;
}
Future<Response<dynamic>> get({
required String path,
Map<String, dynamic>? queryParameters,
}) async {
late Response response;
if (await checkInternetConnexion()) {
try {
response = await _dio.get(path, queryParameters: queryParameters);
} catch (error) {
_exception(error);
}
} else {
throw Exception("Vous n'êtes pas connecté à Internet");
}
return response;
}
Future<Response<dynamic>> patch({
required String path,
Map<String, dynamic>? data,
Options? options,
bool isFormData = false,
}) async {
late Response response;
if (await checkInternetConnexion()) {
try {
response = await _dio.patch(
path,
data: data != null
? (isFormData ? FormData.fromMap(data) : data)
: null,
options: options,
);
} catch (error) {
_exception(error);
}
} else {
throw Exception("Vous n'êtes pas connecté à Internet");
}
return response;
}
Future<Response<dynamic>> put({
required String path,
Map<String, dynamic>? data,
Options? options,
bool isFormData = false,
}) async {
late Response response;
if (await checkInternetConnexion()) {
try {
response = await _dio.put(
path,
data: data != null
? (isFormData ? FormData.fromMap(data) : data)
: null,
options: options,
);
} catch (error) {
_exception(error);
}
} else {
throw Exception("Vous n'êtes pas connecté à Internet");
}
return response;
}
Future<Response<dynamic>> delete({
required String path,
Map<String, dynamic>? queryParameters,
Options? options,
bool isFormData = false,
}) async {
late Response response;
if (await checkInternetConnexion()) {
try {
response = await _dio.delete(
path,
options: options,
queryParameters: queryParameters,
);
} catch (error) {
_exception(error);
}
} else {
throw Exception("Vous n'êtes pas connecté à Internet");
}
return response;
}
void _exception(error) {
if (error is DioException) {
throw error;
}
if (error is SocketException) {
throw Exception("Vous n'êtes pas connecté à Internet");
}
if (error is TimeoutException) {
throw throw Exception("Time out");
}
}
Future<Response<dynamic>> download(
String urlPath,
savePath, {
ProgressCallback? onReceiveProgress,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
String lengthHeader = Headers.contentLengthHeader,
data,
options,
}) async {
late Response response;
try {
response = await _dio.download(
urlPath,
savePath,
onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters,
cancelToken: cancelToken,
deleteOnError: deleteOnError,
lengthHeader: lengthHeader,
data: data,
options: options,
);
} catch (error) {
_exception(error);
}
return response;
}
Future<Response<dynamic>> uploadPatch(
String endPoint, {
ProgressCallback? onSendProgress,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
String lengthHeader = Headers.contentLengthHeader,
data,
options,
}) async {
late Response response;
try {
response = await _dio.request(
endPoint,
onSendProgress: onSendProgress,
data: data,
options: Options(
method: 'PATCH', // or 'PUT'
),
);
} catch (error) {
_exception(error);
}
return response;
}
Future<Response<dynamic>> uploadPost(
String endPoint, {
ProgressCallback? onSendProgress,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
String lengthHeader = Headers.contentLengthHeader,
data,
options,
}) async {
late Response response;
try {
response = await _dio.post(
endPoint,
onSendProgress: onSendProgress,
data: data,
);
} catch (error) {
_exception(error);
}
return response;
}
}

View File

@ -1,3 +1,6 @@
import 'dart:convert';
import 'package:barcode_scanner/backend/schema/user/user_struct.dart';
import 'package:barcode_scanner/services/secure_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -41,3 +44,33 @@ class TokenProvider {
await _storage.delete(key: refreshTokenKey);
}
}
final userConnectedProvider = Provider((ref) {
final storage = ref.watch(sharedPrefsProvider);
return UserConnectedProvider(storage);
});
class UserConnectedProvider {
UserConnectedProvider(this._storage);
final FlutterSecureStorage _storage;
static const String key = "userConnectedKey";
Future<void> set(UserStruct user) async {
return _storage.write(key: key, value: jsonEncode(user.toJson()));
}
// Method to get the token from secure storage and check if it's expired
Future<UserStruct?> get() async {
final res = await _storage.read(key: key);
if (res == null) {
return null;
} else {
return UserStruct.fromJson(jsonDecode(res));
}
}
// Method to delete the token from secure storage
Future<void> delete() async {
await _storage.delete(key: key);
}
}

View File

@ -0,0 +1,4 @@
class AppConstants {
static const String domain = "https://ethupos-odoo.ethumeo.com";
static const String baseUrl = "$domain/simpos/v1";
}

View File

@ -581,6 +581,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
multiple_result:
dependency: "direct main"
description:
name: multiple_result
sha256: "7a1f3c2ee7de44a545fffbad0eefa3f3ac64641932316cd5663bb3cdcf53399b"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
nm:
dependency: transitive
description:

View File

@ -54,6 +54,7 @@ dependencies:
objectbox_flutter_libs: any
path_provider: ^2.1.5
path: ^1.9.1
multiple_result: ^5.1.0
dev_dependencies:
flutter_test: