From 28a8027e20e9ec73d1e0e2766ce404d73bb75e15 Mon Sep 17 00:00:00 2001 From: mandreshope Date: Thu, 3 Jul 2025 15:04:30 +0300 Subject: [PATCH] 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. --- lib/backend/schema/auth/auth_struct.dart | 17 ++ .../schema/auth/auth_struct.freezed.dart | 173 +++++++++++++++++ lib/backend/schema/auth/auth_struct.g.dart | 15 ++ .../schema/product/product_struct.dart | 19 ++ .../product/product_struct.freezed.dart | 163 ++++++++++++++++ .../schema/product/product_struct.g.dart | 27 +++ lib/backend/schema/user/user_struct.dart | 53 +++++ .../schema/user/user_struct.freezed.dart | 160 ++++++++++++++++ lib/backend/schema/user/user_struct.g.dart | 24 +++ lib/pages/home_page/home_page.dart | 95 +++++++-- lib/pages/home_page/home_page_model.dart | 42 ++++ .../home_page/home_page_model.freezed.dart | 181 ++++++++++++++++++ lib/pages/login_page/login_page_model.dart | 15 +- lib/pages/pages.dart | 1 + .../product_form_page/product_form_page.dart | 9 +- .../product_list_page/product_list_page.dart | 38 ++++ lib/router/go_secure_router_builder.dart | 9 + lib/router/go_secure_router_builder.g.dart | 26 +++ 18 files changed, 1046 insertions(+), 21 deletions(-) create mode 100644 lib/backend/schema/auth/auth_struct.dart create mode 100644 lib/backend/schema/auth/auth_struct.freezed.dart create mode 100644 lib/backend/schema/auth/auth_struct.g.dart create mode 100644 lib/backend/schema/product/product_struct.dart create mode 100644 lib/backend/schema/product/product_struct.freezed.dart create mode 100644 lib/backend/schema/product/product_struct.g.dart create mode 100644 lib/backend/schema/user/user_struct.dart create mode 100644 lib/backend/schema/user/user_struct.freezed.dart create mode 100644 lib/backend/schema/user/user_struct.g.dart create mode 100644 lib/pages/home_page/home_page_model.dart create mode 100644 lib/pages/home_page/home_page_model.freezed.dart create mode 100644 lib/pages/product_list_page/product_list_page.dart diff --git a/lib/backend/schema/auth/auth_struct.dart b/lib/backend/schema/auth/auth_struct.dart new file mode 100644 index 0000000..d3edd2b --- /dev/null +++ b/lib/backend/schema/auth/auth_struct.dart @@ -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 json) => + _$AuthStructFromJson(json); +} diff --git a/lib/backend/schema/auth/auth_struct.freezed.dart b/lib/backend/schema/auth/auth_struct.freezed.dart new file mode 100644 index 0000000..86c1f5b --- /dev/null +++ b/lib/backend/schema/auth/auth_struct.freezed.dart @@ -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 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 get copyWith => _$AuthStructCopyWithImpl(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 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 diff --git a/lib/backend/schema/auth/auth_struct.g.dart b/lib/backend/schema/auth/auth_struct.g.dart new file mode 100644 index 0000000..dd22839 --- /dev/null +++ b/lib/backend/schema/auth/auth_struct.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_struct.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_AuthStruct _$AuthStructFromJson(Map json) => _AuthStruct( + accessToken: json['accessToken'] as String?, + refreshToken: json['refreshToken'] as String?, + user: json['user'] == null + ? null + : UserStruct.fromJson(json['user'] as Map), +); diff --git a/lib/backend/schema/product/product_struct.dart b/lib/backend/schema/product/product_struct.dart new file mode 100644 index 0000000..0bb386d --- /dev/null +++ b/lib/backend/schema/product/product_struct.dart @@ -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 json) => + _$ProductStructFromJson(json); +} diff --git a/lib/backend/schema/product/product_struct.freezed.dart b/lib/backend/schema/product/product_struct.freezed.dart new file mode 100644 index 0000000..a49f41e --- /dev/null +++ b/lib/backend/schema/product/product_struct.freezed.dart @@ -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 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 get copyWith => _$ProductStructCopyWithImpl(this as ProductStruct, _$identity); + + /// Serializes this ProductStruct to a JSON map. + Map 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 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 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 diff --git a/lib/backend/schema/product/product_struct.g.dart b/lib/backend/schema/product/product_struct.g.dart new file mode 100644 index 0000000..8d51cef --- /dev/null +++ b/lib/backend/schema/product/product_struct.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'product_struct.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_ProductStruct _$ProductStructFromJson(Map 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 _$ProductStructToJson(_ProductStruct instance) => + { + 'id': instance.id, + 'code': instance.code, + 'name': instance.name, + 'description': instance.description, + 'price': instance.price, + 'quantity': instance.quantity, + }; diff --git a/lib/backend/schema/user/user_struct.dart b/lib/backend/schema/user/user_struct.dart new file mode 100644 index 0000000..e65871c --- /dev/null +++ b/lib/backend/schema/user/user_struct.dart @@ -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 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 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 deleteLocalStorage() { + final storage = providerContainer.read(sharedPrefsProvider); + return storage.delete(key: id ?? key); + } +} diff --git a/lib/backend/schema/user/user_struct.freezed.dart b/lib/backend/schema/user/user_struct.freezed.dart new file mode 100644 index 0000000..aad172b --- /dev/null +++ b/lib/backend/schema/user/user_struct.freezed.dart @@ -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 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 get copyWith => _$UserStructCopyWithImpl(this as UserStruct, _$identity); + + /// Serializes this UserStruct to a JSON map. + Map 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 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 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 diff --git a/lib/backend/schema/user/user_struct.g.dart b/lib/backend/schema/user/user_struct.g.dart new file mode 100644 index 0000000..0105995 --- /dev/null +++ b/lib/backend/schema/user/user_struct.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_struct.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_UserStruct _$UserStructFromJson(Map 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 _$UserStructToJson(_UserStruct instance) => + { + 'id': instance.id, + 'firstName': instance.firstName, + 'lastName': instance.lastName, + 'email': instance.email, + 'phone': instance.phone, + }; diff --git a/lib/pages/home_page/home_page.dart b/lib/pages/home_page/home_page.dart index d81d1b5..5155346 100644 --- a/lib/pages/home_page/home_page.dart +++ b/lib/pages/home_page/home_page.dart @@ -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 { + @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( diff --git a/lib/pages/home_page/home_page_model.dart b/lib/pages/home_page/home_page_model.dart new file mode 100644 index 0000000..fb314ea --- /dev/null +++ b/lib/pages/home_page/home_page_model.dart @@ -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((ref) { + return HomePageModel( + secureStorage: ref.read(sharedPrefsProvider), + tokenProvider: ref.read(tokenProvider), + ); + }); + +class HomePageModel extends StateNotifier { + /// 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; +} diff --git a/lib/pages/home_page/home_page_model.freezed.dart b/lib/pages/home_page/home_page_model.freezed.dart new file mode 100644 index 0000000..d545e2b --- /dev/null +++ b/lib/pages/home_page/home_page_model.freezed.dart @@ -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 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 get copyWith => _$HomePageStateCopyWithImpl(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 diff --git a/lib/pages/login_page/login_page_model.dart b/lib/pages/login_page/login_page_model.dart index 48dae94..6ee305d 100644 --- a/lib/pages/login_page/login_page_model.dart +++ b/lib/pages/login_page/login_page_model.dart @@ -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 { 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 { } } - Future setTokenInLocal(String token) async { + Future 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"); } diff --git a/lib/pages/pages.dart b/lib/pages/pages.dart index 3d6a5e9..2c8d06b 100644 --- a/lib/pages/pages.dart +++ b/lib/pages/pages.dart @@ -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'; diff --git a/lib/pages/product_form_page/product_form_page.dart b/lib/pages/product_form_page/product_form_page.dart index 7827b94..37f330b 100644 --- a/lib/pages/product_form_page/product_form_page.dart +++ b/lib/pages/product_form_page/product_form_page.dart @@ -53,10 +53,7 @@ class _ProductFormPageState extends State { 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 { /// Description TextFormField( controller: description, - decoration: _inputStyle("Description"), + decoration: _inputStyle("Commentaires"), maxLines: 3, ), const SizedBox(height: 16), @@ -96,7 +93,7 @@ class _ProductFormPageState extends State { /// Prix TextFormField( controller: price, - decoration: _inputStyle("Prix"), + decoration: _inputStyle("Quantités"), keyboardType: TextInputType.number, validator: (value) => (value == null || value.isEmpty) ? 'Champ requis' : null, diff --git a/lib/pages/product_list_page/product_list_page.dart b/lib/pages/product_list_page/product_list_page.dart new file mode 100644 index 0000000..4173c16 --- /dev/null +++ b/lib/pages/product_list_page/product_list_page.dart @@ -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 createState() => + _ProductListPageState(); +} + +class _ProductListPageState extends ConsumerState { + @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, + ), + ); + } +} diff --git a/lib/router/go_secure_router_builder.dart b/lib/router/go_secure_router_builder.dart index 9b00791..898571d 100644 --- a/lib/router/go_secure_router_builder.dart +++ b/lib/router/go_secure_router_builder.dart @@ -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( @@ -20,6 +21,7 @@ const String _productFormPage = 'ProductFormPage'; TypedGoRoute(path: _homePage), TypedGoRoute(path: _scannerPage), TypedGoRoute(path: _productFormPage), + TypedGoRoute(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(); +} diff --git a/lib/router/go_secure_router_builder.g.dart b/lib/router/go_secure_router_builder.g.dart index 59062c4..78f141f 100644 --- a/lib/router/go_secure_router_builder.g.dart +++ b/lib/router/go_secure_router_builder.g.dart @@ -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 push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +}