catalog_page.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:intl/intl.dart';
  4. import '../../../database/database.dart';
  5. import '../../../providers/article_provider.dart';
  6. import '../../widgets/article_image.dart';
  7. import '../../widgets/empty_state.dart';
  8. import '../../widgets/search_bar.dart' as sw;
  9. import '../../../core/responsive.dart';
  10. class CatalogPage extends ConsumerStatefulWidget {
  11. const CatalogPage({super.key});
  12. @override
  13. ConsumerState<CatalogPage> createState() => _CatalogPageState();
  14. }
  15. class _CatalogPageState extends ConsumerState<CatalogPage> {
  16. String _filter = '';
  17. final _searchCtrl = TextEditingController();
  18. @override
  19. void dispose() {
  20. _searchCtrl.dispose();
  21. super.dispose();
  22. }
  23. @override
  24. Widget build(BuildContext context) {
  25. final articlesAsync = ref.watch(activeArticleSearchProvider(_filter));
  26. return Scaffold(
  27. appBar: AppBar(title: const Text('Catalogo')),
  28. body: SafeArea(
  29. child: Column(
  30. children: [
  31. sw.SearchBarWidget(
  32. controller: _searchCtrl,
  33. hint: 'Cerca articolo...',
  34. onChanged: (v) => setState(() => _filter = v),
  35. onClear: () { _searchCtrl.clear(); setState(() => _filter = ''); },
  36. ),
  37. Expanded(
  38. child: articlesAsync.when(
  39. data: (articles) {
  40. if (articles.isEmpty) return const EmptyState(icon: Icons.inventory_2_outlined, title: 'Nessun articolo');
  41. if (ResponsiveBreakpoints.isPhone(context)) {
  42. return ListView.builder(
  43. itemCount: articles.length,
  44. itemBuilder: (_, i) => _ArticleListItem(article: articles[i]),
  45. );
  46. }
  47. return GridView.builder(
  48. padding: const EdgeInsets.all(8),
  49. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  50. crossAxisCount: 3,
  51. childAspectRatio: 1.2,
  52. crossAxisSpacing: 8,
  53. mainAxisSpacing: 8,
  54. ),
  55. itemCount: articles.length,
  56. itemBuilder: (_, i) => _ArticleGridItem(article: articles[i]),
  57. );
  58. },
  59. loading: () => const Center(child: CircularProgressIndicator()),
  60. error: (e, _) => Center(child: Text('Errore: $e')),
  61. ),
  62. ),
  63. ],
  64. ),
  65. ),
  66. );
  67. }
  68. }
  69. class _ArticleListItem extends StatelessWidget {
  70. final Articolo article;
  71. const _ArticleListItem({required this.article});
  72. @override
  73. Widget build(BuildContext context) {
  74. return Card(
  75. margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
  76. child: ListTile(
  77. leading: ArticleImage(idArticolo: article.idArticolo, codArticolo: article.codArticolo),
  78. title: Text(article.descrizione ?? ''),
  79. subtitle: Text(article.codArticolo ?? ''),
  80. trailing: article.nonDisponibile == 1
  81. ? Container(
  82. padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
  83. decoration: BoxDecoration(color: Colors.red.shade100, borderRadius: BorderRadius.circular(8)),
  84. child: const Text('N.D.', style: TextStyle(color: Colors.red, fontSize: 12)),
  85. )
  86. : null,
  87. ),
  88. );
  89. }
  90. }
  91. class _ArticleGridItem extends StatelessWidget {
  92. final Articolo article;
  93. const _ArticleGridItem({required this.article});
  94. @override
  95. Widget build(BuildContext context) {
  96. return Card(
  97. child: InkWell(
  98. borderRadius: BorderRadius.circular(12),
  99. child: Padding(
  100. padding: const EdgeInsets.all(8),
  101. child: Column(
  102. mainAxisSize: MainAxisSize.min,
  103. children: [
  104. ArticleImage(idArticolo: article.idArticolo, codArticolo: article.codArticolo, size: 64),
  105. const SizedBox(height: 8),
  106. Text(article.descrizione ?? '', maxLines: 2, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall),
  107. Text(article.codArticolo ?? '', style: Theme.of(context).textTheme.labelSmall?.copyWith(color: Theme.of(context).colorScheme.outline)),
  108. if (article.nonDisponibile == 1)
  109. Container(
  110. padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
  111. decoration: BoxDecoration(color: Colors.red.shade100, borderRadius: BorderRadius.circular(8)),
  112. child: const Text('N.D.', style: TextStyle(color: Colors.red, fontSize: 10)),
  113. ),
  114. ],
  115. ),
  116. ),
  117. ),
  118. );
  119. }
  120. }