feat: Adds home page navigation drawer

Introduces a side navigation drawer on the home page to improve navigation and centralize user actions.

Displays the logged-in user's information (name, email) in the drawer header.
Adds a link to the Product List page ("Inventaire") within the drawer.
Moves the logout functionality to the drawer, ensuring user data is also cleared from local storage upon logout.
Ensures user data is saved to local storage during the login process and fetched when the home page loads.

Includes minor text updates on the Product Form page.
This commit is contained in:
mandreshope 2025-07-03 15:04:30 +03:00
parent 6f2f94b2da
commit 28a8027e20
18 changed files with 1046 additions and 21 deletions

View File

@ -0,0 +1,17 @@
import 'package:barcode_scanner/backend/schema/user/user_struct.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_struct.freezed.dart';
part 'auth_struct.g.dart';
@Freezed(toJson: false)
abstract class AuthStruct with _$AuthStruct {
factory AuthStruct({
String? accessToken,
String? refreshToken,
UserStruct? user,
}) = _AuthStruct;
factory AuthStruct.fromJson(Map<String, dynamic> json) =>
_$AuthStructFromJson(json);
}

View File

@ -0,0 +1,173 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'auth_struct.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AuthStruct {
String? get accessToken; String? get refreshToken; UserStruct? get user;
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$AuthStructCopyWith<AuthStruct> get copyWith => _$AuthStructCopyWithImpl<AuthStruct>(this as AuthStruct, _$identity);
@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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,user);
@override
String toString() {
return 'AuthStruct(accessToken: $accessToken, refreshToken: $refreshToken, user: $user)';
}
}
/// @nodoc
abstract mixin class $AuthStructCopyWith<$Res> {
factory $AuthStructCopyWith(AuthStruct value, $Res Function(AuthStruct) _then) = _$AuthStructCopyWithImpl;
@useResult
$Res call({
String? accessToken, String? refreshToken, UserStruct? user
});
$UserStructCopyWith<$Res>? get user;
}
/// @nodoc
class _$AuthStructCopyWithImpl<$Res>
implements $AuthStructCopyWith<$Res> {
_$AuthStructCopyWithImpl(this._self, this._then);
final AuthStruct _self;
final $Res Function(AuthStruct) _then;
/// 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,}) {
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?,
));
}
/// 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));
});
}
}
/// @nodoc
@JsonSerializable(createToJson: false)
class _AuthStruct implements AuthStruct {
_AuthStruct({this.accessToken, this.refreshToken, this.user});
factory _AuthStruct.fromJson(Map<String, dynamic> json) => _$AuthStructFromJson(json);
@override final String? accessToken;
@override final String? refreshToken;
@override final UserStruct? user;
/// Create a copy of AuthStruct
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$AuthStructCopyWith<_AuthStruct> get copyWith => __$AuthStructCopyWithImpl<_AuthStruct>(this, _$identity);
@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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,user);
@override
String toString() {
return 'AuthStruct(accessToken: $accessToken, refreshToken: $refreshToken, user: $user)';
}
}
/// @nodoc
abstract mixin class _$AuthStructCopyWith<$Res> implements $AuthStructCopyWith<$Res> {
factory _$AuthStructCopyWith(_AuthStruct value, $Res Function(_AuthStruct) _then) = __$AuthStructCopyWithImpl;
@override @useResult
$Res call({
String? accessToken, String? refreshToken, UserStruct? user
});
@override $UserStructCopyWith<$Res>? get user;
}
/// @nodoc
class __$AuthStructCopyWithImpl<$Res>
implements _$AuthStructCopyWith<$Res> {
__$AuthStructCopyWithImpl(this._self, this._then);
final _AuthStruct _self;
final $Res Function(_AuthStruct) _then;
/// 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,}) {
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?,
));
}
/// 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

@ -0,0 +1,15 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_struct.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_AuthStruct _$AuthStructFromJson(Map<String, dynamic> json) => _AuthStruct(
accessToken: json['accessToken'] as String?,
refreshToken: json['refreshToken'] as String?,
user: json['user'] == null
? null
: UserStruct.fromJson(json['user'] as Map<String, dynamic>),
);

View File

@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product_struct.freezed.dart';
part 'product_struct.g.dart';
@Freezed(toJson: true)
abstract class ProductStruct with _$ProductStruct {
factory ProductStruct({
String? id,
String? code,
String? name,
String? description,
String? price,
String? quantity,
}) = _ProductStruct;
factory ProductStruct.fromJson(Map<String, dynamic> json) =>
_$ProductStructFromJson(json);
}

View File

@ -0,0 +1,163 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'product_struct.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ProductStruct {
String? get id; String? get code; String? get name; String? get description; String? get price; String? get quantity;
/// Create a copy of ProductStruct
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ProductStructCopyWith<ProductStruct> get copyWith => _$ProductStructCopyWithImpl<ProductStruct>(this as ProductStruct, _$identity);
/// Serializes this ProductStruct to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProductStruct&&(identical(other.id, id) || other.id == id)&&(identical(other.code, code) || other.code == code)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.price, price) || other.price == price)&&(identical(other.quantity, quantity) || other.quantity == quantity));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,code,name,description,price,quantity);
@override
String toString() {
return 'ProductStruct(id: $id, code: $code, name: $name, description: $description, price: $price, quantity: $quantity)';
}
}
/// @nodoc
abstract mixin class $ProductStructCopyWith<$Res> {
factory $ProductStructCopyWith(ProductStruct value, $Res Function(ProductStruct) _then) = _$ProductStructCopyWithImpl;
@useResult
$Res call({
String? id, String? code, String? name, String? description, String? price, String? quantity
});
}
/// @nodoc
class _$ProductStructCopyWithImpl<$Res>
implements $ProductStructCopyWith<$Res> {
_$ProductStructCopyWithImpl(this._self, this._then);
final ProductStruct _self;
final $Res Function(ProductStruct) _then;
/// Create a copy of ProductStruct
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? code = freezed,Object? name = freezed,Object? description = freezed,Object? price = freezed,Object? quantity = freezed,}) {
return _then(_self.copyWith(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,price: freezed == price ? _self.price : price // ignore: cast_nullable_to_non_nullable
as String?,quantity: freezed == quantity ? _self.quantity : quantity // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _ProductStruct implements ProductStruct {
_ProductStruct({this.id, this.code, this.name, this.description, this.price, this.quantity});
factory _ProductStruct.fromJson(Map<String, dynamic> json) => _$ProductStructFromJson(json);
@override final String? id;
@override final String? code;
@override final String? name;
@override final String? description;
@override final String? price;
@override final String? quantity;
/// Create a copy of ProductStruct
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ProductStructCopyWith<_ProductStruct> get copyWith => __$ProductStructCopyWithImpl<_ProductStruct>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ProductStructToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProductStruct&&(identical(other.id, id) || other.id == id)&&(identical(other.code, code) || other.code == code)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.price, price) || other.price == price)&&(identical(other.quantity, quantity) || other.quantity == quantity));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,code,name,description,price,quantity);
@override
String toString() {
return 'ProductStruct(id: $id, code: $code, name: $name, description: $description, price: $price, quantity: $quantity)';
}
}
/// @nodoc
abstract mixin class _$ProductStructCopyWith<$Res> implements $ProductStructCopyWith<$Res> {
factory _$ProductStructCopyWith(_ProductStruct value, $Res Function(_ProductStruct) _then) = __$ProductStructCopyWithImpl;
@override @useResult
$Res call({
String? id, String? code, String? name, String? description, String? price, String? quantity
});
}
/// @nodoc
class __$ProductStructCopyWithImpl<$Res>
implements _$ProductStructCopyWith<$Res> {
__$ProductStructCopyWithImpl(this._self, this._then);
final _ProductStruct _self;
final $Res Function(_ProductStruct) _then;
/// Create a copy of ProductStruct
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? code = freezed,Object? name = freezed,Object? description = freezed,Object? price = freezed,Object? quantity = freezed,}) {
return _then(_ProductStruct(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,price: freezed == price ? _self.price : price // ignore: cast_nullable_to_non_nullable
as String?,quantity: freezed == quantity ? _self.quantity : quantity // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'product_struct.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ProductStruct _$ProductStructFromJson(Map<String, dynamic> json) =>
_ProductStruct(
id: json['id'] as String?,
code: json['code'] as String?,
name: json['name'] as String?,
description: json['description'] as String?,
price: json['price'] as String?,
quantity: json['quantity'] as String?,
);
Map<String, dynamic> _$ProductStructToJson(_ProductStruct instance) =>
<String, dynamic>{
'id': instance.id,
'code': instance.code,
'name': instance.name,
'description': instance.description,
'price': instance.price,
'quantity': instance.quantity,
};

View File

@ -0,0 +1,53 @@
import 'dart:convert';
import 'package:barcode_scanner/provider_container.dart';
import 'package:barcode_scanner/services/secure_storage.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_struct.freezed.dart';
part 'user_struct.g.dart';
@Freezed(toJson: true)
abstract class UserStruct with _$UserStruct {
factory UserStruct({
String? id,
String? firstName,
String? lastName,
String? email,
String? phone,
}) = _UserStruct;
factory UserStruct.fromJson(Map<String, dynamic> json) =>
_$UserStructFromJson(json);
}
extension UserStructExt on UserStruct {
String get key => 'user';
String get fullName {
final sb = StringBuffer();
sb.write(firstName);
if (firstName != null) {
sb.write(' ');
}
sb.write(lastName);
return sb.toString();
}
Future setToLocalStorage() {
final storage = providerContainer.read(sharedPrefsProvider);
return storage.write(key: id ?? key, value: jsonEncode(toJson()));
}
Future<UserStruct?> getFromLocalStorage() async {
final storage = providerContainer.read(sharedPrefsProvider);
final jsonString = await storage.read(key: id ?? key);
if (jsonString == null) return null;
return UserStruct.fromJson(jsonDecode(jsonString));
}
Future<void> deleteLocalStorage() {
final storage = providerContainer.read(sharedPrefsProvider);
return storage.delete(key: id ?? key);
}
}

View File

@ -0,0 +1,160 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'user_struct.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$UserStruct {
String? get id; String? get firstName; String? get lastName; String? get email; String? get phone;
/// Create a copy of UserStruct
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$UserStructCopyWith<UserStruct> get copyWith => _$UserStructCopyWithImpl<UserStruct>(this as UserStruct, _$identity);
/// Serializes this UserStruct to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is UserStruct&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.email, email) || other.email == email)&&(identical(other.phone, phone) || other.phone == phone));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,firstName,lastName,email,phone);
@override
String toString() {
return 'UserStruct(id: $id, firstName: $firstName, lastName: $lastName, email: $email, phone: $phone)';
}
}
/// @nodoc
abstract mixin class $UserStructCopyWith<$Res> {
factory $UserStructCopyWith(UserStruct value, $Res Function(UserStruct) _then) = _$UserStructCopyWithImpl;
@useResult
$Res call({
String? id, String? firstName, String? lastName, String? email, String? phone
});
}
/// @nodoc
class _$UserStructCopyWithImpl<$Res>
implements $UserStructCopyWith<$Res> {
_$UserStructCopyWithImpl(this._self, this._then);
final UserStruct _self;
final $Res Function(UserStruct) _then;
/// Create a copy of UserStruct
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? firstName = freezed,Object? lastName = freezed,Object? email = freezed,Object? phone = freezed,}) {
return _then(_self.copyWith(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
as String?,email: freezed == email ? _self.email : email // ignore: cast_nullable_to_non_nullable
as String?,phone: freezed == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _UserStruct implements UserStruct {
_UserStruct({this.id, this.firstName, this.lastName, this.email, this.phone});
factory _UserStruct.fromJson(Map<String, dynamic> json) => _$UserStructFromJson(json);
@override final String? id;
@override final String? firstName;
@override final String? lastName;
@override final String? email;
@override final String? phone;
/// Create a copy of UserStruct
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$UserStructCopyWith<_UserStruct> get copyWith => __$UserStructCopyWithImpl<_UserStruct>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$UserStructToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserStruct&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.email, email) || other.email == email)&&(identical(other.phone, phone) || other.phone == phone));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,firstName,lastName,email,phone);
@override
String toString() {
return 'UserStruct(id: $id, firstName: $firstName, lastName: $lastName, email: $email, phone: $phone)';
}
}
/// @nodoc
abstract mixin class _$UserStructCopyWith<$Res> implements $UserStructCopyWith<$Res> {
factory _$UserStructCopyWith(_UserStruct value, $Res Function(_UserStruct) _then) = __$UserStructCopyWithImpl;
@override @useResult
$Res call({
String? id, String? firstName, String? lastName, String? email, String? phone
});
}
/// @nodoc
class __$UserStructCopyWithImpl<$Res>
implements _$UserStructCopyWith<$Res> {
__$UserStructCopyWithImpl(this._self, this._then);
final _UserStruct _self;
final $Res Function(_UserStruct) _then;
/// Create a copy of UserStruct
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? firstName = freezed,Object? lastName = freezed,Object? email = freezed,Object? phone = freezed,}) {
return _then(_UserStruct(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
as String?,email: freezed == email ? _self.email : email // ignore: cast_nullable_to_non_nullable
as String?,phone: freezed == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_struct.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_UserStruct _$UserStructFromJson(Map<String, dynamic> json) => _UserStruct(
id: json['id'] as String?,
firstName: json['firstName'] as String?,
lastName: json['lastName'] as String?,
email: json['email'] as String?,
phone: json['phone'] as String?,
);
Map<String, dynamic> _$UserStructToJson(_UserStruct instance) =>
<String, dynamic>{
'id': instance.id,
'firstName': instance.firstName,
'lastName': instance.lastName,
'email': instance.email,
'phone': instance.phone,
};

View File

@ -1,8 +1,11 @@
import 'package:barcode_scanner/backend/schema/user/user_struct.dart';
import 'package:barcode_scanner/pages/home_page/home_page_model.dart';
import 'package:barcode_scanner/pages/login_page/login_page_model.dart';
import 'package:barcode_scanner/router/go_router_builder.dart';
import 'package:barcode_scanner/router/go_secure_router_builder.dart';
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomePage extends ConsumerStatefulWidget {
@ -13,23 +16,91 @@ class HomePage extends ConsumerStatefulWidget {
}
class _HomePageState extends ConsumerState<HomePage> {
@override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
ref.read(homePageModelProvider.notifier).getUserConnected();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
backgroundColor: AppTheme.of(context).primaryBackground,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
child: Column(
children: [
SizedBox(height: 50),
Consumer(
builder: (context, ref, child) {
final state = ref.watch(homePageModelProvider);
return ListTile(
contentPadding: EdgeInsets.zero,
leading: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: AppTheme.of(context).secondaryBackground,
borderRadius: BorderRadius.circular(60),
),
),
title: Text(
state.user?.fullName ?? '',
style: AppTheme.of(context).titleMedium,
),
subtitle: Text(
state.user?.email ?? '',
style: AppTheme.of(context).bodyMedium,
),
);
},
),
Divider(),
ListTile(
leading: Icon(Icons.person),
title: Text('Profil', style: AppTheme.of(context).bodyLarge),
),
ListTile(
onTap: () {
Navigator.of(context).pop();
ProductListRoute().push(context);
},
leading: Icon(Icons.inventory),
title: Text(
'Inventaire',
style: AppTheme.of(context).bodyLarge,
),
),
Spacer(),
SafeArea(
child: ListTile(
onTap: () async {
await ref.read(loginPageModelProvider.notifier).logOut();
await UserStruct(id: '1').deleteLocalStorage();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
SplashRoute().go(context);
});
},
leading: Icon(Icons.logout),
title: Text(
'Se deconnecter',
style: AppTheme.of(context).bodyLarge,
),
),
),
],
),
),
),
backgroundColor: AppTheme.of(context).primaryBackground,
appBar: AppBar(
automaticallyImplyLeading: false,
actions: [
IconButton(
onPressed: () async {
await ref.read(loginPageModelProvider.notifier).logOut();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
SplashRoute().go(context);
});
},
icon: Icon(Icons.login),
),
],
title: Text('Barcode Scanner', style: AppTheme.of(context).titleLarge),
centerTitle: true,
backgroundColor: AppTheme.of(context).primaryBackground,
actions: [],
),
body: Center(
child: SingleChildScrollView(

View File

@ -0,0 +1,42 @@
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';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'home_page_model.freezed.dart';
/// The provider for the AuthViewModel, using Riverpod's StateNotifierProvider
/// with autoDispose to manage the lifecycle of the view model.
final homePageModelProvider =
StateNotifierProvider<HomePageModel, HomePageState>((ref) {
return HomePageModel(
secureStorage: ref.read(sharedPrefsProvider),
tokenProvider: ref.read(tokenProvider),
);
});
class HomePageModel extends StateNotifier<HomePageState> {
/// Constructor initializes the TaskRepository using the provider reference.
HomePageModel({required this.secureStorage, required this.tokenProvider})
: super(const HomePageState());
late FlutterSecureStorage secureStorage;
late TokenProvider tokenProvider;
Future getUserConnected() async {
state = state.copyWith(loading: true);
final user = await UserStruct(id: '1').getFromLocalStorage();
state = state.copyWith(user: user);
}
}
@freezed
abstract class HomePageState with _$HomePageState {
const factory HomePageState({
UserStruct? user,
@Default(false) bool loading,
}) = _HomePageState;
}

View File

@ -0,0 +1,181 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'home_page_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$HomePageState implements DiagnosticableTreeMixin {
UserStruct? get user; bool get loading;
/// Create a copy of HomePageState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$HomePageStateCopyWith<HomePageState> get copyWith => _$HomePageStateCopyWithImpl<HomePageState>(this as HomePageState, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'HomePageState'))
..add(DiagnosticsProperty('user', user))..add(DiagnosticsProperty('loading', loading));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is HomePageState&&(identical(other.user, user) || other.user == user)&&(identical(other.loading, loading) || other.loading == loading));
}
@override
int get hashCode => Object.hash(runtimeType,user,loading);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'HomePageState(user: $user, loading: $loading)';
}
}
/// @nodoc
abstract mixin class $HomePageStateCopyWith<$Res> {
factory $HomePageStateCopyWith(HomePageState value, $Res Function(HomePageState) _then) = _$HomePageStateCopyWithImpl;
@useResult
$Res call({
UserStruct? user, bool loading
});
$UserStructCopyWith<$Res>? get user;
}
/// @nodoc
class _$HomePageStateCopyWithImpl<$Res>
implements $HomePageStateCopyWith<$Res> {
_$HomePageStateCopyWithImpl(this._self, this._then);
final HomePageState _self;
final $Res Function(HomePageState) _then;
/// Create a copy of HomePageState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? user = freezed,Object? loading = null,}) {
return _then(_self.copyWith(
user: freezed == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
as UserStruct?,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of HomePageState
/// 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));
});
}
}
/// @nodoc
class _HomePageState with DiagnosticableTreeMixin implements HomePageState {
const _HomePageState({this.user, this.loading = false});
@override final UserStruct? user;
@override@JsonKey() final bool loading;
/// Create a copy of HomePageState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$HomePageStateCopyWith<_HomePageState> get copyWith => __$HomePageStateCopyWithImpl<_HomePageState>(this, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'HomePageState'))
..add(DiagnosticsProperty('user', user))..add(DiagnosticsProperty('loading', loading));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _HomePageState&&(identical(other.user, user) || other.user == user)&&(identical(other.loading, loading) || other.loading == loading));
}
@override
int get hashCode => Object.hash(runtimeType,user,loading);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'HomePageState(user: $user, loading: $loading)';
}
}
/// @nodoc
abstract mixin class _$HomePageStateCopyWith<$Res> implements $HomePageStateCopyWith<$Res> {
factory _$HomePageStateCopyWith(_HomePageState value, $Res Function(_HomePageState) _then) = __$HomePageStateCopyWithImpl;
@override @useResult
$Res call({
UserStruct? user, bool loading
});
@override $UserStructCopyWith<$Res>? get user;
}
/// @nodoc
class __$HomePageStateCopyWithImpl<$Res>
implements _$HomePageStateCopyWith<$Res> {
__$HomePageStateCopyWithImpl(this._self, this._then);
final _HomePageState _self;
final $Res Function(_HomePageState) _then;
/// Create a copy of HomePageState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? user = freezed,Object? loading = null,}) {
return _then(_HomePageState(
user: freezed == user ? _self.user : user // ignore: cast_nullable_to_non_nullable
as UserStruct?,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of HomePageState
/// 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

@ -1,3 +1,4 @@
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';
import 'package:flutter/foundation.dart';
@ -57,7 +58,15 @@ class LoginPageModel extends StateNotifier<LoginPageState> {
state = state.copyWith(loading: true);
await Future.delayed(Duration(seconds: 5));
if (email == "user@yopmail.com" && password == "password") {
setTokenInLocal('token');
setTokenInLocal(
'token',
UserStruct(
id: '1',
firstName: 'User',
lastName: 'Anonymous',
email: 'user@yopmail.com',
),
);
state = state.copyWith(
loading: false,
status: LoginPageStateStatus.logOut,
@ -79,12 +88,12 @@ class LoginPageModel extends StateNotifier<LoginPageState> {
}
}
Future<void> setTokenInLocal(String token) async {
Future<void> setTokenInLocal(String token, UserStruct user) async {
await Future.wait([
tokenProvider.setToken(token),
tokenProvider.setRefreshToken(token),
user.setToLocalStorage(),
]);
state = state.copyWith(loading: false, status: LoginPageStateStatus.logged);
debugPrint("$token");
}

View File

@ -3,3 +3,4 @@ export 'login_page/login_page.dart';
export 'product_form_page/product_form_page.dart';
export 'scanner_page/scanner_page.dart';
export 'splash_page/splash_page.dart';
export 'product_list_page/product_list_page.dart';

View File

@ -53,10 +53,7 @@ class _ProductFormPageState extends State<ProductFormPage> {
Navigator.of(context).pop();
},
),
title: Text(
'Formulaire Produit',
style: AppTheme.of(context).titleLarge,
),
title: Text('Fiche Produit', style: AppTheme.of(context).titleLarge),
centerTitle: true,
backgroundColor: AppTheme.of(context).primaryBackground,
elevation: 0,
@ -88,7 +85,7 @@ class _ProductFormPageState extends State<ProductFormPage> {
/// Description
TextFormField(
controller: description,
decoration: _inputStyle("Description"),
decoration: _inputStyle("Commentaires"),
maxLines: 3,
),
const SizedBox(height: 16),
@ -96,7 +93,7 @@ class _ProductFormPageState extends State<ProductFormPage> {
/// Prix
TextFormField(
controller: price,
decoration: _inputStyle("Prix"),
decoration: _inputStyle("Quantités"),
keyboardType: TextInputType.number,
validator: (value) =>
(value == null || value.isEmpty) ? 'Champ requis' : null,

View File

@ -0,0 +1,38 @@
import 'package:barcode_scanner/themes/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ProductListPage extends ConsumerStatefulWidget {
const ProductListPage({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
_ProductListPageState();
}
class _ProductListPageState extends ConsumerState<ProductListPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
appBar: AppBar(
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: AppTheme.of(context).primaryText,
),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
'Inventaire des produits',
style: AppTheme.of(context).titleLarge,
),
centerTitle: true,
backgroundColor: AppTheme.of(context).primaryBackground,
elevation: 0,
),
);
}
}

View File

@ -12,6 +12,7 @@ const String _securePage = 'SecurePage';
const String _homePage = 'HomePage';
const String _scannerPage = 'ScannerPage';
const String _productFormPage = 'ProductFormPage';
const String _productListPage = 'ProductListPage';
// Groupe des routes sécurisées
@TypedGoRoute<SecureRoute>(
@ -20,6 +21,7 @@ const String _productFormPage = 'ProductFormPage';
TypedGoRoute<HomeRoute>(path: _homePage),
TypedGoRoute<ScannerRoute>(path: _scannerPage),
TypedGoRoute<ProductFormRoute>(path: _productFormPage),
TypedGoRoute<ProductListRoute>(path: _productListPage),
],
)
class SecureRoute extends GoRouteData with _$SecureRoute {
@ -59,3 +61,10 @@ class ProductFormRoute extends GoRouteData with _$ProductFormRoute {
@override
Widget build(BuildContext context, GoRouterState state) => ProductFormPage();
}
class ProductListRoute extends GoRouteData with _$ProductListRoute {
const ProductListRoute();
@override
Widget build(BuildContext context, GoRouterState state) => ProductListPage();
}

View File

@ -20,6 +20,11 @@ RouteBase get $secureRoute => GoRouteData.$route(
factory: _$ProductFormRoute._fromState,
),
GoRouteData.$route(
path: 'ProductListPage',
factory: _$ProductListRoute._fromState,
),
],
);
@ -103,3 +108,24 @@ mixin _$ProductFormRoute on GoRouteData {
@override
void replace(BuildContext context) => context.replace(location);
}
mixin _$ProductListRoute on GoRouteData {
static ProductListRoute _fromState(GoRouterState state) =>
const ProductListRoute();
@override
String get location => GoRouteData.$location('/SecurePage/ProductListPage');
@override
void go(BuildContext context) => context.go(location);
@override
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
@override
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
@override
void replace(BuildContext context) => context.replace(location);
}