catalog_page.dart 5.2 KB

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