pedido_form.dart 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. import 'dart:async';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:yoshi_papas_app/widgets/widgets_components.dart';
  5. import '../../themes/themes.dart';
  6. import '../../models/models.dart';
  7. import '../../viewmodels/viewmodels.dart';
  8. import 'package:collection/collection.dart';
  9. class PedidoForm extends StatefulWidget {
  10. @override
  11. _PedidoFormState createState() => _PedidoFormState();
  12. }
  13. class ItemCarrito {
  14. Producto producto;
  15. int cantidad;
  16. Map<String, dynamic>? customizaciones;
  17. ItemCarrito(
  18. {required this.producto, this.cantidad = 1, this.customizaciones});
  19. }
  20. class _PedidoFormState extends State<PedidoForm> {
  21. CategoriaProductoViewModel cvm = CategoriaProductoViewModel();
  22. ProductoViewModel pvm = ProductoViewModel();
  23. bool _isLoading = false;
  24. ScrollController horizontalScrollController = ScrollController();
  25. CategoriaProducto? categoriaSeleccionada;
  26. List<CategoriaProducto> categorias = [];
  27. List<Producto> productos = [];
  28. List<ItemCarrito> carrito = [];
  29. TopingViewModel _topingViewModel = TopingViewModel();
  30. TopingCategoriaViewModel _categoriaTopingViewModel =
  31. TopingCategoriaViewModel();
  32. List<Toping> _topingsDisponibles = []; // Lista de topings disponibles
  33. Producto? _productoActual; // Producto actual seleccionado para customización
  34. bool isCustomizingProduct = false;
  35. Map<String, dynamic>? currentProductForCustomization;
  36. String? selectedBase;
  37. List<String> selectedSauce = [];
  38. List<String> selectedDressing = [];
  39. List<String> selectedToppings = [];
  40. Map<String, bool> baseOptions = {};
  41. Map<String, bool> sauceOptions = {};
  42. Map<String, bool> dressingOptions = {};
  43. List<TopingCategoria> categoriasDeTopings = []; // Añade esta línea
  44. TopingCategoria? _currentTopingCategoriaSeleccionada; // Añade esta línea
  45. List<Toping> _topingsFiltrados = []; // Añade esta línea
  46. Map<int, List<int>> selectedToppingsByCategory = {};
  47. Map<String, bool> toppingOptions = {};
  48. List<int> _selectedToppings = [];
  49. double calcularTotalPedido() {
  50. double total = 0;
  51. for (var item in carrito) {
  52. total += double.parse(item.producto.precio!) * item.cantidad;
  53. }
  54. return total;
  55. }
  56. @override
  57. void initState() {
  58. super.initState();
  59. cargarCategoriasIniciales();
  60. _cargarCategoriasDeTopings();
  61. _cargarTopingsDisponibles();
  62. }
  63. void _cargarCategoriasDeTopings() async {
  64. var categoriasObtenidas = await _categoriaTopingViewModel.fetchRegistros();
  65. setState(() {
  66. categoriasDeTopings = categoriasObtenidas;
  67. });
  68. }
  69. // Función para cargar topings disponibles
  70. void _cargarTopingsDisponibles() async {
  71. _topingsDisponibles = await _topingViewModel.fetchRegistros();
  72. var categorias = await _categoriaTopingViewModel.fetchRegistros();
  73. var categoriaBase = categorias.firstWhereOrNull(
  74. (cat) => cat.nombre == 'Base',
  75. );
  76. if (categoriaBase != null) {
  77. var topingsBase = _topingsDisponibles.where((toping) {
  78. return toping.idCategoria == categoriaBase.id;
  79. }).toList();
  80. setState(() {
  81. baseOptions = {
  82. for (var toping in topingsBase) toping.nombre!: false,
  83. };
  84. });
  85. }
  86. }
  87. void _onTopingCategoriaSelected(TopingCategoria categoriaSeleccionada) {
  88. setState(() {
  89. _currentTopingCategoriaSeleccionada = categoriaSeleccionada;
  90. // Filtrar y actualizar la lista de toppings para mostrar basada en la categoría seleccionada
  91. _topingsFiltrados = _topingsDisponibles
  92. .where((t) => t.idCategoria == categoriaSeleccionada.id)
  93. .toList();
  94. });
  95. }
  96. void _onToppingSelected(Toping topping) {
  97. // Verifica si el topping ya está seleccionado
  98. bool isSelected = _selectedToppings.contains(topping.id);
  99. final List<int> currentSelectedToppings =
  100. selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] ??
  101. [];
  102. if (isSelected) {
  103. // Si ya está seleccionado, lo deseleccionamos
  104. setState(() {
  105. _selectedToppings.remove(topping.id);
  106. currentSelectedToppings.remove(topping.id);
  107. selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] =
  108. List.from(currentSelectedToppings);
  109. });
  110. } else {
  111. // Si no está seleccionado, verifica las restricciones antes de seleccionar
  112. if (_currentTopingCategoriaSeleccionada!.id == 1 &&
  113. currentSelectedToppings.isNotEmpty) {
  114. // Si es la categoría 'Base', solo permite una selección
  115. setState(() {
  116. currentSelectedToppings.clear();
  117. currentSelectedToppings.add(topping.id);
  118. _selectedToppings.clear();
  119. _selectedToppings.add(topping.id);
  120. });
  121. } else {
  122. // Si la lista ya tiene dos toppings, quita el primero y agrega el nuevo
  123. if (currentSelectedToppings.length >= 2) {
  124. setState(() {
  125. _selectedToppings.remove(currentSelectedToppings.first);
  126. currentSelectedToppings
  127. .removeAt(0); // Elimina el primer topping seleccionado
  128. });
  129. }
  130. // Ahora agrega el nuevo topping seleccionado
  131. setState(() {
  132. currentSelectedToppings.add(topping.id);
  133. _selectedToppings.add(topping.id);
  134. });
  135. }
  136. selectedToppingsByCategory[_currentTopingCategoriaSeleccionada!.id] =
  137. List.from(currentSelectedToppings);
  138. }
  139. }
  140. @override
  141. void dispose() {
  142. horizontalScrollController.dispose();
  143. super.dispose();
  144. }
  145. Future<void> cargarCategoriasIniciales() async {
  146. setState(() => _isLoading = true);
  147. var categoriasObtenidas = await cvm.getCategoriaProducto();
  148. setState(() {
  149. categorias = categoriasObtenidas;
  150. _isLoading = false;
  151. // Selecciona la primera categoría por defecto si hay categorías disponibles
  152. if (categorias.isNotEmpty) {
  153. categoriaSeleccionada = categorias.first;
  154. seleccionarCategoria(categoriaSeleccionada!);
  155. }
  156. });
  157. }
  158. Future<void> seleccionarCategoria(CategoriaProducto categoria) async {
  159. if (!mounted) return;
  160. setState(() {
  161. _isLoading = true;
  162. categoriaSeleccionada = categoria;
  163. });
  164. productos = await pvm.fetchRegistros(categoriaProducto: categoria);
  165. if (!mounted) return;
  166. setState(() => _isLoading = false);
  167. }
  168. void agregarAlCarrito(Producto producto) {
  169. setState(() {
  170. var indice =
  171. carrito.indexWhere((item) => item.producto.id == producto.id);
  172. if (indice != -1) {
  173. carrito[indice].cantidad++;
  174. } else {
  175. // Se agrega el nuevo producto al carrito con cantidad inicial de 1
  176. carrito.add(ItemCarrito(producto: producto));
  177. }
  178. });
  179. }
  180. void quitarDelCarrito(Producto producto) {
  181. setState(() {
  182. // Comienza con setState por la misma razón
  183. var indice =
  184. carrito.indexWhere((item) => item.producto.id == producto.id);
  185. if (indice != -1) {
  186. if (carrito[indice].cantidad > 1) {
  187. carrito[indice].cantidad--;
  188. } else {
  189. carrito.removeAt(indice);
  190. }
  191. }
  192. });
  193. }
  194. void addToCart(Producto producto, {Map<String, dynamic>? customizations}) {
  195. // Revisa si hay un producto en el carrito con las mismas customizaciones
  196. var existingIndex = carrito.indexWhere((item) =>
  197. item.producto.id == producto.id &&
  198. mapEquals(item.customizaciones, customizations));
  199. if (existingIndex != -1) {
  200. carrito[existingIndex].cantidad++;
  201. } else {
  202. carrito.add(ItemCarrito(
  203. producto: producto, cantidad: 1, customizaciones: customizations));
  204. }
  205. setState(() {});
  206. }
  207. void customizeProduct(Producto producto) {
  208. // Asumimos que este producto requiere customización
  209. setState(() {
  210. _productoActual = producto;
  211. isCustomizingProduct = true;
  212. if (categoriasDeTopings.isNotEmpty) {
  213. // Selecciona la primera categoría de toppings automáticamente
  214. _currentTopingCategoriaSeleccionada = categoriasDeTopings.first;
  215. // Filtra los toppings basado en la primera categoría
  216. _topingsFiltrados = _topingsDisponibles
  217. .where((toping) =>
  218. toping.idCategoria == _currentTopingCategoriaSeleccionada!.id)
  219. .toList();
  220. }
  221. });
  222. }
  223. void finalizeCustomization() {
  224. if (_productoActual != null) {
  225. // Prepara el diccionario de customizaciones con listas vacías para cada categoría
  226. Map<String, List<String>> customizations = {
  227. 'BASE': [],
  228. 'SALSA': [],
  229. 'ADEREZO': [],
  230. 'TOPPINGS': [],
  231. };
  232. // Itera sobre los toppings seleccionados, clasificándolos por categoría
  233. selectedToppingsByCategory.forEach((categoryId, toppingIds) {
  234. var categoryName = _categoriaTopingViewModel.getById(categoryId)?.clave;
  235. toppingIds.forEach((toppingId) {
  236. var topping = _topingViewModel.getById(toppingId);
  237. if (topping != null &&
  238. categoryName != null &&
  239. customizations.containsKey(categoryName.toUpperCase())) {
  240. customizations[categoryName.toUpperCase()]!.add(topping.nombre!);
  241. }
  242. });
  243. });
  244. // Asegúrate de que la customización final se almacene como deseas, por ejemplo, como un solo mapa si es necesario
  245. Map<String, dynamic> finalCustomizations = {};
  246. customizations.forEach((key, value) {
  247. finalCustomizations[key] = value.join(', ');
  248. });
  249. addToCart(_productoActual!, customizations: finalCustomizations);
  250. // Limpia el estado para la siguiente personalización
  251. setState(() {
  252. isCustomizingProduct = false;
  253. _productoActual = null;
  254. _currentTopingCategoriaSeleccionada = null; // Agrega esta línea
  255. _topingsFiltrados.clear(); // Agrega esta línea
  256. selectedToppingsByCategory.clear(); // Agrega esta línea
  257. _selectedToppings.clear(); // Ya existe, asegúrate de que se llama
  258. });
  259. }
  260. }
  261. @override
  262. Widget build(BuildContext context) {
  263. return Scaffold(
  264. appBar: AppBar(
  265. title: Text("Crear Pedido"),
  266. ),
  267. body: Row(
  268. children: [
  269. Flexible(
  270. flex: 3,
  271. child: _buildCartSection(),
  272. ),
  273. SizedBox(width: 35),
  274. Flexible(
  275. flex: 7,
  276. child: isCustomizingProduct
  277. ? buildCustomizationOptions()
  278. : _buildProductsSection(),
  279. ),
  280. ],
  281. ),
  282. );
  283. }
  284. Widget _buildTotalSection() {
  285. double total =
  286. calcularTotalPedido(); // Aquí llamarías a la función calcularTotalPedido
  287. return Padding(
  288. padding: const EdgeInsets.symmetric(horizontal: 8.0),
  289. child: Row(
  290. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  291. children: [
  292. const Text('Total',
  293. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  294. Text("\$${total.toStringAsFixed(2)}",
  295. style:
  296. const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  297. ],
  298. ),
  299. );
  300. }
  301. List<Widget> _buildToppingList(Map<String, dynamic>? customizations) {
  302. List<Widget> list = [];
  303. customizations?.forEach((category, toppingsAsString) {
  304. if (toppingsAsString is String && toppingsAsString.isNotEmpty) {
  305. // Divide la string por comas para obtener los nombres individuales de los toppings
  306. List<String> toppingNames = toppingsAsString.split(', ');
  307. for (var toppingName in toppingNames) {
  308. list.add(ListTile(
  309. title: Text(toppingName),
  310. subtitle: Text(category), // Muestra la categoría como subtítulo
  311. ));
  312. }
  313. }
  314. });
  315. return list;
  316. }
  317. Widget _buildCartSection() {
  318. return Card(
  319. margin: const EdgeInsets.all(8.0),
  320. child: Column(
  321. children: [
  322. const Padding(
  323. padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
  324. child: Row(
  325. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  326. children: [
  327. Text('Producto',
  328. style:
  329. TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  330. Text('Cantidad',
  331. style:
  332. TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
  333. ],
  334. ),
  335. ),
  336. Expanded(
  337. child: ListView.builder(
  338. itemCount: carrito.length,
  339. itemBuilder: (context, index) {
  340. final item = carrito[index];
  341. // Crear una lista de Widgets para las customizaciones.
  342. List<Widget> customizationWidgets = [];
  343. item.customizaciones?.forEach((category, toppingsAsString) {
  344. if (toppingsAsString is String &&
  345. toppingsAsString.isNotEmpty) {
  346. // Si hay más de un topping en la cadena, sepáralos y muéstralos en la misma línea
  347. customizationWidgets.add(
  348. Align(
  349. alignment: Alignment.centerLeft,
  350. child: Padding(
  351. padding: const EdgeInsets.only(left: 16.0, top: 4.0),
  352. child: Text(
  353. '- $category: $toppingsAsString', // Cambio clave aquí
  354. style: const TextStyle(
  355. fontWeight: FontWeight.w500,
  356. fontSize: 14.0,
  357. )),
  358. ),
  359. ),
  360. );
  361. }
  362. });
  363. return Column(
  364. children: [
  365. ListTile(
  366. title: Text(
  367. item.producto.nombre!,
  368. style: const TextStyle(fontWeight: FontWeight.w600),
  369. ),
  370. subtitle: Text(
  371. '\$${item.producto.precio}',
  372. style: const TextStyle(
  373. fontWeight: FontWeight.bold,
  374. color: Color(0xFF008000)),
  375. ),
  376. trailing: Row(
  377. mainAxisSize: MainAxisSize.min,
  378. children: [
  379. IconButton(
  380. icon: const Icon(Icons.delete, color: Colors.red),
  381. onPressed: () => eliminarProductoDelCarrito(index),
  382. ),
  383. IconButton(
  384. icon: const Icon(Icons.remove),
  385. onPressed: () => quitarDelCarrito(item.producto),
  386. ),
  387. const SizedBox(width: 5),
  388. Text(
  389. '${item.cantidad}',
  390. style: const TextStyle(
  391. fontWeight: FontWeight.bold, fontSize: 14),
  392. ),
  393. const SizedBox(width: 5),
  394. IconButton(
  395. icon: const Icon(Icons.add),
  396. onPressed: () => agregarAlCarrito(item.producto),
  397. ),
  398. ],
  399. ),
  400. ),
  401. // Coloca aquí las customizaciones directamente.
  402. ...customizationWidgets,
  403. Divider(), // Opcional: Un divisor visual entre los elementos.
  404. ],
  405. );
  406. },
  407. ),
  408. ),
  409. const Divider(
  410. thickness: 5,
  411. ),
  412. _buildTotalSection(),
  413. const SizedBox(height: 25),
  414. Padding(
  415. padding: const EdgeInsets.all(8.0),
  416. child: ElevatedButton(
  417. onPressed: () {
  418. // Aquí implementarías la lógica para finalizar el pedido
  419. },
  420. style: ElevatedButton.styleFrom(
  421. primary: AppTheme.primary,
  422. onPrimary: AppTheme.secondary,
  423. textStyle: const TextStyle(fontSize: 22),
  424. fixedSize: const Size(250, 50)),
  425. child: const Text('Finalizar Pedido'),
  426. ),
  427. ),
  428. ],
  429. ),
  430. );
  431. }
  432. void eliminarProductoDelCarrito(int index) {
  433. setState(() {
  434. carrito.removeAt(index);
  435. });
  436. }
  437. Widget _buildProductsSection() {
  438. // Asumiendo que tienes un método para obtener los productos según la categoría seleccionada
  439. return Column(
  440. children: [
  441. _buildCategoryButtons(),
  442. const SizedBox(height: 10),
  443. Expanded(
  444. child: GridView.builder(
  445. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  446. crossAxisCount: 3, // Número de columnas
  447. childAspectRatio: 3 / 2, // Proporción de cada tarjeta
  448. ),
  449. itemCount: productos.length,
  450. itemBuilder: (context, index) {
  451. final producto = productos[index];
  452. return Card(
  453. child: InkWell(
  454. onTap: () {
  455. // Modifica esta parte para verificar si el producto necesita customización
  456. if (producto.toping == 1) {
  457. // Asume que `toping` es 1 si necesita customización
  458. customizeProduct(producto);
  459. } else {
  460. // Si no requiere customización, agrega directamente al carrito
  461. agregarAlCarrito(producto);
  462. }
  463. },
  464. child: Column(
  465. mainAxisAlignment: MainAxisAlignment.center,
  466. children: [
  467. const Icon(Icons.fastfood, size: 80),
  468. const SizedBox(height: 8),
  469. Padding(
  470. padding: EdgeInsets.fromLTRB(30, 0, 30, 0),
  471. child: Text(
  472. producto.nombre ?? '',
  473. style: const TextStyle(
  474. fontSize: 16, fontWeight: FontWeight.bold),
  475. )),
  476. const SizedBox(height: 8),
  477. Text(
  478. '\$${producto.precio}',
  479. style: const TextStyle(
  480. fontSize: 16,
  481. fontWeight: FontWeight.bold,
  482. color: Color(0xFF008000)),
  483. ),
  484. ],
  485. ),
  486. ),
  487. );
  488. },
  489. ),
  490. ),
  491. ],
  492. );
  493. }
  494. Widget _buildCategoryButtons() {
  495. return Column(
  496. mainAxisSize: MainAxisSize.min,
  497. children: [
  498. SizedBox(
  499. height: 50, // Altura para los botones
  500. child: Scrollbar(
  501. controller: horizontalScrollController,
  502. thumbVisibility: true,
  503. thickness: 5.0,
  504. child: ListView.builder(
  505. scrollDirection: Axis.horizontal,
  506. itemCount: categorias.length,
  507. controller: horizontalScrollController,
  508. itemBuilder: (context, index) {
  509. final categoria = categorias[index];
  510. bool esSeleccionada = categoriaSeleccionada?.id == categoria.id;
  511. return Padding(
  512. padding: const EdgeInsets.symmetric(horizontal: 4.0),
  513. child: ElevatedButton(
  514. onPressed: () => seleccionarCategoria(categoria),
  515. style: ElevatedButton.styleFrom(
  516. primary: esSeleccionada
  517. ? const Color(0xFFFF848F)
  518. : Colors.white,
  519. onPrimary: Colors.black,
  520. textStyle: const TextStyle(
  521. fontWeight: FontWeight.bold,
  522. ),
  523. shape: RoundedRectangleBorder(
  524. borderRadius: BorderRadius.circular(18.0),
  525. side: BorderSide(
  526. color: esSeleccionada ? Colors.black : Colors.grey),
  527. ),
  528. ),
  529. child: Text(categoria.nombre ?? 'Sin nombre'),
  530. ),
  531. );
  532. },
  533. ),
  534. ),
  535. ),
  536. ],
  537. );
  538. }
  539. Widget buildCustomizationOptions() {
  540. return Container(
  541. margin: const EdgeInsets.all(8.0),
  542. child: Card(
  543. child: Column(
  544. children: [
  545. Expanded(
  546. child: Row(
  547. children: [
  548. Expanded(
  549. flex: 4,
  550. child: ListView.builder(
  551. itemCount: categoriasDeTopings.length,
  552. itemBuilder: (context, index) {
  553. final categoria = categoriasDeTopings[index];
  554. return ListTile(
  555. title: Text(
  556. categoria.nombre ?? 'N/A',
  557. style: const TextStyle(
  558. fontSize: 16, fontWeight: FontWeight.bold),
  559. ),
  560. selected:
  561. _currentTopingCategoriaSeleccionada == categoria,
  562. trailing: Image.network(
  563. categoria.mediaTopingCategoria[0].media!.ruta!,
  564. width: 80,
  565. loadingBuilder: (BuildContext context, Widget child,
  566. ImageChunkEvent? loadingProgress) {
  567. if (loadingProgress == null) return child;
  568. return Center(
  569. child: CircularProgressIndicator(
  570. value: loadingProgress.expectedTotalBytes !=
  571. null
  572. ? loadingProgress.cumulativeBytesLoaded /
  573. loadingProgress.expectedTotalBytes!
  574. : null,
  575. ),
  576. );
  577. },
  578. errorBuilder: (BuildContext context,
  579. Object exception, StackTrace? stackTrace) {
  580. // Aquí puedes devolver un widget como un ícono o imagen local para indicar el error
  581. return const Icon(
  582. Icons.menu_book,
  583. size: 30,
  584. );
  585. },
  586. ),
  587. onTap: () {
  588. setState(() {
  589. _currentTopingCategoriaSeleccionada = categoria;
  590. _topingsFiltrados = _topingsDisponibles
  591. .where((t) => t.idCategoria == categoria.id)
  592. .toList();
  593. });
  594. },
  595. );
  596. },
  597. ),
  598. ),
  599. const VerticalDivider(width: 0),
  600. Expanded(
  601. flex: 6,
  602. child: GridView.builder(
  603. gridDelegate:
  604. const SliverGridDelegateWithFixedCrossAxisCount(
  605. crossAxisCount: 3,
  606. ),
  607. itemCount: _topingsFiltrados
  608. .length, // Usando _topingsFiltrados directamente
  609. itemBuilder: (context, index) {
  610. final topping = _topingsFiltrados[index];
  611. bool isSelected =
  612. _selectedToppings.contains(topping.id);
  613. return Card(
  614. color: isSelected
  615. ? const Color(0xFFFC8178)
  616. : Colors.white,
  617. child: InkWell(
  618. onTap: () => _onToppingSelected(topping),
  619. child: Column(
  620. mainAxisAlignment: MainAxisAlignment.center,
  621. children: [
  622. Image.network(
  623. topping.mediaToping[0].media!.ruta!,
  624. width: 80,
  625. loadingBuilder: (BuildContext context,
  626. Widget child,
  627. ImageChunkEvent? loadingProgress) {
  628. if (loadingProgress == null) return child;
  629. return Center(
  630. child: CircularProgressIndicator(
  631. value: loadingProgress
  632. .expectedTotalBytes !=
  633. null
  634. ? loadingProgress
  635. .cumulativeBytesLoaded /
  636. loadingProgress
  637. .expectedTotalBytes!
  638. : null,
  639. ),
  640. );
  641. },
  642. errorBuilder: (BuildContext context,
  643. Object exception,
  644. StackTrace? stackTrace) {
  645. // Aquí puedes devolver un widget como un ícono o imagen local para indicar el error
  646. return const Icon(
  647. Icons.lunch_dining,
  648. size: 60,
  649. );
  650. },
  651. ),
  652. Text(topping.nombre!,
  653. style: TextStyle(
  654. color: isSelected
  655. ? Colors.white
  656. : Colors.black)),
  657. ],
  658. ),
  659. ),
  660. );
  661. },
  662. ),
  663. ),
  664. ],
  665. ),
  666. ),
  667. Container(
  668. padding: EdgeInsets.symmetric(vertical: 8.0),
  669. alignment: Alignment.centerRight,
  670. // El botón para finalizar la personalización
  671. child: _buildConfirmButton(),
  672. ),
  673. ],
  674. ),
  675. ),
  676. );
  677. }
  678. Widget _buildConfirmButton() {
  679. return ElevatedButton(
  680. style: ElevatedButton.styleFrom(
  681. primary: AppTheme.primary, // Color del botón
  682. onPrimary: Colors.black,
  683. textStyle: const TextStyle(fontSize: 22),
  684. padding: const EdgeInsets.fromLTRB(80, 20, 80, 20)),
  685. onPressed: () {
  686. finalizeCustomization(); // Este método creará el pedido personalizado
  687. },
  688. child: Text('Confirmar Pedido'),
  689. );
  690. }
  691. }