diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..2c06e87 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -23,6 +23,13 @@ linter: rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + - sort_constructors_first +analyzer: + exclude: + - "**/*.g.dart" + - "**/*.freezed.dart" + errors: + invalid_annotation_target: ignore # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/lib/backend/objectbox/entities/product/product_entity.dart b/lib/backend/objectbox/entities/product/product_entity.dart index 3e85c5a..faf2de1 100644 --- a/lib/backend/objectbox/entities/product/product_entity.dart +++ b/lib/backend/objectbox/entities/product/product_entity.dart @@ -4,16 +4,6 @@ import 'package:objectbox/objectbox.dart'; /// Modèle de base de données ObjectBox @Entity() class ProductEntity { - @Id(assignable: true) - int id; - - String? code; - String? name; - String? description; - String? price; - String? quantity; - String? image; - ProductEntity({ this.id = 0, this.code, @@ -23,6 +13,15 @@ class ProductEntity { this.quantity, this.image, }); + @Id(assignable: true) + int id; + + String? code; + String? name; + String? description; + String? price; + String? quantity; + String? image; /// Convertir vers ProductStruct ProductStruct toStruct() { diff --git a/lib/backend/objectbox/objectbox_manager.dart b/lib/backend/objectbox/objectbox_manager.dart index c01c6d7..f3fa472 100644 --- a/lib/backend/objectbox/objectbox_manager.dart +++ b/lib/backend/objectbox/objectbox_manager.dart @@ -8,11 +8,11 @@ export 'package:barcode_scanner/backend/objectbox/objectbox.g.dart'; late ObjectboxManager objectboxManager; class ObjectboxManager { + ObjectboxManager._create(this.store); + /// The Store of this app. late final Store store; - ObjectboxManager._create(this.store); - /// Create an instance of ObjectBox to use throughout the app. static Future create() async { Directory directory; diff --git a/lib/pages/product_form_page/product_form_page.dart b/lib/pages/product_form_page/product_form_page.dart index 37f330b..5c83072 100644 --- a/lib/pages/product_form_page/product_form_page.dart +++ b/lib/pages/product_form_page/product_form_page.dart @@ -1,22 +1,47 @@ +import 'package:barcode_scanner/components/loading_progress_component.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:barcode_scanner/components/primary_button_component.dart'; +import 'package:barcode_scanner/pages/product_form_page/product_form_page_model.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'; -class ProductFormPage extends StatefulWidget { - const ProductFormPage({super.key}); +class ProductFormPage extends ConsumerStatefulWidget { + const ProductFormPage({super.key, required this.id}); + final int id; @override - State createState() => _ProductFormPageState(); + ConsumerState createState() => _ProductFormPageState(); } -class _ProductFormPageState extends State { +class _ProductFormPageState extends ConsumerState { final _formKey = GlobalKey(); final TextEditingController name = TextEditingController(); final TextEditingController code = TextEditingController(); final TextEditingController description = TextEditingController(); final TextEditingController price = TextEditingController(); + final TextEditingController quantity = TextEditingController(); + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.addPostFrameCallback((_) { + ref + .read(productFormPageModelProvider.notifier) + .getProductLocal( + id: widget.id, + onSuccess: (value) { + name.text = value?.name ?? ''; + code.text = value?.id.toString() ?? ''; + description.text = value?.description ?? ''; + price.text = value?.price ?? ''; + quantity.text = value?.quantity ?? ''; + }, + ); + }); + } @override void dispose() { @@ -41,6 +66,7 @@ class _ProductFormPageState extends State { @override Widget build(BuildContext context) { + final state = ref.watch(productFormPageModelProvider); return Scaffold( backgroundColor: AppTheme.of(context).primaryBackground, appBar: AppBar( @@ -58,63 +84,70 @@ class _ProductFormPageState extends State { backgroundColor: AppTheme.of(context).primaryBackground, elevation: 0, ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Form( - key: _formKey, - child: ListView( - children: [ - /// Nom du produit - TextFormField( - controller: name, - decoration: _inputStyle("Nom du produit"), - validator: (value) => - (value == null || value.isEmpty) ? 'Champ requis' : null, - ), - const SizedBox(height: 16), + body: state.loading + ? Center(child: LoadingProgressComponent()) + : Padding( + padding: const EdgeInsets.all(24), + child: Form( + key: _formKey, + child: ListView( + children: [ + /// Nom du produit + TextFormField( + controller: name, + decoration: _inputStyle("Nom du produit"), + validator: (value) => (value == null || value.isEmpty) + ? 'Champ requis' + : null, + ), + const SizedBox(height: 16), - /// Code barre / code produit - TextFormField( - controller: code, - decoration: _inputStyle("Code produit / Code barre"), - validator: (value) => - (value == null || value.isEmpty) ? 'Champ requis' : null, - ), - const SizedBox(height: 16), + /// Code barre / code produit + TextFormField( + controller: code, + decoration: _inputStyle("Code produit / Code barre"), + validator: (value) => (value == null || value.isEmpty) + ? 'Champ requis' + : null, + ), + const SizedBox(height: 16), - /// Description - TextFormField( - controller: description, - decoration: _inputStyle("Commentaires"), - maxLines: 3, - ), - const SizedBox(height: 16), + /// Description + TextFormField( + controller: description, + decoration: _inputStyle("Commentaires"), + maxLines: 3, + ), + const SizedBox(height: 16), - /// Prix - TextFormField( - controller: price, - decoration: _inputStyle("Quantités"), - keyboardType: TextInputType.number, - validator: (value) => - (value == null || value.isEmpty) ? 'Champ requis' : null, - ), - const SizedBox(height: 24), + /// Prix + TextFormField( + controller: quantity, + decoration: _inputStyle("Quantités"), + keyboardType: TextInputType.number, + validator: (value) => (value == null || value.isEmpty) + ? 'Champ requis' + : null, + ), + const SizedBox(height: 24), - /// Bouton sauvegarder - PrimaryButtonComponent( - onPressed: () { - if (_formKey.currentState!.validate()) { - // Traitement ici - debugPrint("Produit : ${name.text}, Code : ${code.text}"); - HomeRoute().go(context); - } - }, - text: 'Sauvegarder', + /// Bouton sauvegarder + PrimaryButtonComponent( + onPressed: () { + if (_formKey.currentState!.validate()) { + // Traitement ici + debugPrint( + "Produit : ${name.text}, Code : ${code.text}", + ); + HomeRoute().go(context); + } + }, + text: 'Sauvegarder', + ), + ], + ), ), - ], - ), - ), - ), + ), ); } } diff --git a/lib/pages/product_form_page/product_form_page_model.dart b/lib/pages/product_form_page/product_form_page_model.dart new file mode 100644 index 0000000..9d6b0ff --- /dev/null +++ b/lib/pages/product_form_page/product_form_page_model.dart @@ -0,0 +1,39 @@ +import 'package:barcode_scanner/backend/objectbox/entities/product/product_entity.dart'; +import 'package:barcode_scanner/backend/objectbox/objectbox_manager.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'product_form_page_model.freezed.dart'; + +/// The provider for the AuthViewModel, using Riverpod's StateNotifierProvider +/// with autoDispose to manage the lifecycle of the view model. +final productFormPageModelProvider = + StateNotifierProvider((ref) { + return ProductFormPageModel(); + }); + +class ProductFormPageModel extends StateNotifier { + /// Constructor initializes the TaskRepository using the provider reference. + ProductFormPageModel() : super(const ProductFormPageState()); + + final productStore = objectboxManager.store.box(); + + Future getProductLocal({ + required int id, + Function(ProductEntity? value)? onSuccess, + }) async { + state = state.copyWith(loading: true); + final product = await productStore.getAsync(id); + onSuccess?.call(product); + state = state.copyWith(product: product, loading: false); + } +} + +@freezed +abstract class ProductFormPageState with _$ProductFormPageState { + const factory ProductFormPageState({ + ProductEntity? product, + @Default(false) bool loading, + }) = _ProductFormPageState; +} diff --git a/lib/pages/product_form_page/product_form_page_model.freezed.dart b/lib/pages/product_form_page/product_form_page_model.freezed.dart new file mode 100644 index 0000000..780cf66 --- /dev/null +++ b/lib/pages/product_form_page/product_form_page_model.freezed.dart @@ -0,0 +1,157 @@ +// 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_form_page_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ProductFormPageState implements DiagnosticableTreeMixin { + + ProductEntity? get product; bool get loading; +/// Create a copy of ProductFormPageState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ProductFormPageStateCopyWith get copyWith => _$ProductFormPageStateCopyWithImpl(this as ProductFormPageState, _$identity); + + +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'ProductFormPageState')) + ..add(DiagnosticsProperty('product', product))..add(DiagnosticsProperty('loading', loading)); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProductFormPageState&&(identical(other.product, product) || other.product == product)&&(identical(other.loading, loading) || other.loading == loading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,product,loading); + +@override +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { + return 'ProductFormPageState(product: $product, loading: $loading)'; +} + + +} + +/// @nodoc +abstract mixin class $ProductFormPageStateCopyWith<$Res> { + factory $ProductFormPageStateCopyWith(ProductFormPageState value, $Res Function(ProductFormPageState) _then) = _$ProductFormPageStateCopyWithImpl; +@useResult +$Res call({ + ProductEntity? product, bool loading +}); + + + + +} +/// @nodoc +class _$ProductFormPageStateCopyWithImpl<$Res> + implements $ProductFormPageStateCopyWith<$Res> { + _$ProductFormPageStateCopyWithImpl(this._self, this._then); + + final ProductFormPageState _self; + final $Res Function(ProductFormPageState) _then; + +/// Create a copy of ProductFormPageState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? product = freezed,Object? loading = null,}) { + return _then(_self.copyWith( +product: freezed == product ? _self.product : product // ignore: cast_nullable_to_non_nullable +as ProductEntity?,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// @nodoc + + +class _ProductFormPageState with DiagnosticableTreeMixin implements ProductFormPageState { + const _ProductFormPageState({this.product, this.loading = false}); + + +@override final ProductEntity? product; +@override@JsonKey() final bool loading; + +/// Create a copy of ProductFormPageState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProductFormPageStateCopyWith<_ProductFormPageState> get copyWith => __$ProductFormPageStateCopyWithImpl<_ProductFormPageState>(this, _$identity); + + +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'ProductFormPageState')) + ..add(DiagnosticsProperty('product', product))..add(DiagnosticsProperty('loading', loading)); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProductFormPageState&&(identical(other.product, product) || other.product == product)&&(identical(other.loading, loading) || other.loading == loading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,product,loading); + +@override +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { + return 'ProductFormPageState(product: $product, loading: $loading)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProductFormPageStateCopyWith<$Res> implements $ProductFormPageStateCopyWith<$Res> { + factory _$ProductFormPageStateCopyWith(_ProductFormPageState value, $Res Function(_ProductFormPageState) _then) = __$ProductFormPageStateCopyWithImpl; +@override @useResult +$Res call({ + ProductEntity? product, bool loading +}); + + + + +} +/// @nodoc +class __$ProductFormPageStateCopyWithImpl<$Res> + implements _$ProductFormPageStateCopyWith<$Res> { + __$ProductFormPageStateCopyWithImpl(this._self, this._then); + + final _ProductFormPageState _self; + final $Res Function(_ProductFormPageState) _then; + +/// Create a copy of ProductFormPageState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? product = freezed,Object? loading = null,}) { + return _then(_ProductFormPageState( +product: freezed == product ? _self.product : product // ignore: cast_nullable_to_non_nullable +as ProductEntity?,loading: null == loading ? _self.loading : loading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +// dart format on diff --git a/lib/pages/product_list_page/product_list_page.dart b/lib/pages/product_list_page/product_list_page.dart index ff32bb4..33a8214 100644 --- a/lib/pages/product_list_page/product_list_page.dart +++ b/lib/pages/product_list_page/product_list_page.dart @@ -60,7 +60,7 @@ class _ProductListPageState extends ConsumerState { final product = state.products[index]; return ListTile( onTap: () { - ProductFormRoute().push(context); + ProductFormRoute(id: product.id).push(context); }, leading: Container( height: 60, @@ -89,6 +89,17 @@ class _ProductListPageState extends ConsumerState { ), ], ), + trailing: IconButton( + onPressed: () { + ref + .read(productListPageModelProvider.notifier) + .deleteProductLocal(product.id); + }, + icon: Icon( + Icons.delete, + color: AppTheme.of(context).error, + ), + ), ); }, ); diff --git a/lib/pages/product_list_page/product_list_page_model.dart b/lib/pages/product_list_page/product_list_page_model.dart index 7099634..7b390db 100644 --- a/lib/pages/product_list_page/product_list_page_model.dart +++ b/lib/pages/product_list_page/product_list_page_model.dart @@ -17,13 +17,18 @@ class ProductListPageModel extends StateNotifier { /// Constructor initializes the TaskRepository using the provider reference. ProductListPageModel() : super(const ProductListPageState()); + final productStore = objectboxManager.store.box(); + Future getProductsLocal() async { - Box productStore = objectboxManager.store - .box(); state = state.copyWith(loading: true); final products = await productStore.getAllAsync(); state = state.copyWith(products: products, loading: false); } + + void deleteProductLocal(int id) { + productStore.remove(id); + getProductsLocal(); + } } @freezed diff --git a/lib/pages/scanner_page/scanner_page.dart b/lib/pages/scanner_page/scanner_page.dart index 171c521..af05cd4 100644 --- a/lib/pages/scanner_page/scanner_page.dart +++ b/lib/pages/scanner_page/scanner_page.dart @@ -150,7 +150,7 @@ class _ScannerPageState extends ConsumerState unawaited(_subscription?.cancel()); _subscription = null; unawaited(mobileScannerController.stop()); - ProductFormRoute().push(context); + ProductFormRoute(id: productStruct.id).push(context); }, ), ), @@ -224,7 +224,7 @@ class _ScannerPageState extends ConsumerState unawaited(_subscription?.cancel()); _subscription = null; unawaited(mobileScannerController.stop()); - ProductFormRoute().push(context); + ProductFormRoute(id: productStruct.id).push(context); }, ), ), @@ -285,7 +285,7 @@ class _ScannerPageState extends ConsumerState child: MobileScanner( controller: mobileScannerController, onDetectError: (error, stackTrace) { - print("===========> $error"); + debugPrint("===========> $error"); }, errorBuilder: (c, p2) { return Center(child: Icon(Icons.error)); diff --git a/lib/router/go_secure_router_builder.dart b/lib/router/go_secure_router_builder.dart index 898571d..1da9010 100644 --- a/lib/router/go_secure_router_builder.dart +++ b/lib/router/go_secure_router_builder.dart @@ -56,10 +56,12 @@ class ScannerRoute extends GoRouteData with _$ScannerRoute { } class ProductFormRoute extends GoRouteData with _$ProductFormRoute { - const ProductFormRoute(); + const ProductFormRoute({required this.id}); + final int id; @override - Widget build(BuildContext context, GoRouterState state) => ProductFormPage(); + Widget build(BuildContext context, GoRouterState state) => + ProductFormPage(id: id); } class ProductListRoute extends GoRouteData with _$ProductListRoute { diff --git a/lib/router/go_secure_router_builder.g.dart b/lib/router/go_secure_router_builder.g.dart index 78f141f..5161d06 100644 --- a/lib/router/go_secure_router_builder.g.dart +++ b/lib/router/go_secure_router_builder.g.dart @@ -90,10 +90,15 @@ mixin _$ScannerRoute on GoRouteData { mixin _$ProductFormRoute on GoRouteData { static ProductFormRoute _fromState(GoRouterState state) => - const ProductFormRoute(); + ProductFormRoute(id: int.parse(state.uri.queryParameters['id']!)!); + + ProductFormRoute get _self => this as ProductFormRoute; @override - String get location => GoRouteData.$location('/SecurePage/ProductFormPage'); + String get location => GoRouteData.$location( + '/SecurePage/ProductFormPage', + queryParams: {'id': _self.id.toString()}, + ); @override void go(BuildContext context) => context.go(location); diff --git a/pubspec.lock b/pubspec.lock index 40cfd72..b318d48 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -290,10 +290,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 00681e2..e10728d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,7 +67,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec