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:
parent
4682be5b62
commit
b5e83e5b7f
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) =>
|
||||
|
@ -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
|
||||
|
@ -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?,
|
||||
);
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
319
lib/services/dio_service.dart
Normal file
319
lib/services/dio_service.dart
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
4
lib/utils/app_constants.dart
Normal file
4
lib/utils/app_constants.dart
Normal file
@ -0,0 +1,4 @@
|
||||
class AppConstants {
|
||||
static const String domain = "https://ethupos-odoo.ethumeo.com";
|
||||
static const String baseUrl = "$domain/simpos/v1";
|
||||
}
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user