{"id":641,"date":"2023-10-16T23:40:15","date_gmt":"2023-10-17T04:40:15","guid":{"rendered":"https:\/\/80bits.blog\/?p=641"},"modified":"2023-10-17T10:54:32","modified_gmt":"2023-10-17T15:54:32","slug":"flutter-biblioteca-y-guia-paso-a-paso-para-crear-asientos-botones-de-un-widget-de-sala-de-cine","status":"publish","type":"post","link":"https:\/\/80bits.blog\/index.php\/2023\/10\/16\/flutter-biblioteca-y-guia-paso-a-paso-para-crear-asientos-botones-de-un-widget-de-sala-de-cine\/","title":{"rendered":"Flutter &#8211; Biblioteca y gu\u00eda paso a paso para crear asientos\/botones de un widget de sala de cine"},"content":{"rendered":"\n<p>El otro d\u00eda, en uno de los muchos grupos en los que participo, espec\u00edficamente en uno sobre Flutter, vi a una persona realizar la siguiente pregunta:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" width=\"1170\" height=\"2532\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/facebook_screenshot_cine.jpg\" alt=\"Flutter grupo de cine\" class=\"wp-image-644\" style=\"width:350px;height:undefinedpx\" srcset=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/facebook_screenshot_cine.jpg 1170w, https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/facebook_screenshot_cine-710x1536.jpg 710w, https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/facebook_screenshot_cine-946x2048.jpg 946w\" sizes=\"(max-width: 1170px) 100vw, 1170px\" \/><\/figure>\n\n\n\n<p>Mi respuesta fue exactamente la que se muestra en la imagen. Aunque es una soluci\u00f3n v\u00e1lida, no necesariamente es la mejor. Sin embargo, tiene la ventaja de ser una soluci\u00f3n que se puede implementar de manera f\u00e1cil y r\u00e1pida.<\/p>\n\n\n\n<p>As\u00ed que decid\u00ed crear una versi\u00f3n mejorada (o al menos la mejor versi\u00f3n que pude) para dise\u00f1ar unas sillas de cine. Para ello, me inspir\u00e9 en la aplicaci\u00f3n de Cin\u00e9polis.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Dise\u00f1o Cin\u00e9polis<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" width=\"1170\" height=\"2532\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/IMG_8271.png\" alt=\"\" class=\"wp-image-646\" style=\"width:350px;height:757px\" srcset=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/IMG_8271.png 1170w, https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/IMG_8271-710x1536.png 710w, https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/IMG_8271-946x2048.png 946w\" sizes=\"(max-width: 1170px) 100vw, 1170px\" \/><\/figure>\n\n\n\n<p>Como pueden ver b\u00e1sicamente es una matriz de asientos, aunque no es cuadrada porque no hay la misma cantidad de asientos en algunas filas.<\/p>\n\n\n\n<p>Para desarrollar la l\u00f3gica, me gui\u00e9 parcialmente, aunque no al pie de la letra, por la metodolog\u00eda Atomic Design a fin de separar los distintos componentes, que esencialmente son tres:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Asiento (Bot\u00f3n)<\/h3>\n\n\n\n<p>Este es el \u00fanico widget que tiene estado ya que reacciona a los toques y\/o puede construirse en un estado especifico, as\u00ed como su tama\u00f1o color y dos propiedades para identificarse as\u00ed mismo, que es una letra (String) y numero de bot\u00f3n, A,10 por ejemplo.<\/p>\n\n\n\n<p>Tras explorar un poco la aplicaci\u00f3n y comprar algunos boletos, observ\u00e9 que b\u00e1sicamente un asiento puede encontrarse en los siguientes estados.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Libre<\/li>\n\n\n\n<li>Ocupado (no disponible)<\/li>\n\n\n\n<li>Asiento especial (tiene un icono de silla de ruedas)<\/li>\n\n\n\n<li>Libre\/Ocupado (este \u00faltimo cuando se selecciona y\/o deselecciona)<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1104\" height=\"532\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/mockup_buttons_cinema.jpg\" alt=\"flutter storyboard\" class=\"wp-image-671\"\/><\/figure>\n\n\n\n<p>La clase para este prop\u00f3sito es <code>BtnSeat<\/code> y representa los lugares o asientos que tiene una sala.<\/p>\n\n\n\n<p>Tiene un callback para capturar la respuesta al toque del bot\u00f3n y su estado. La utilizo para propagar la informaci\u00f3n retrospectivamente a trav\u00e9s de todo el ciclo de vida del bot\u00f3n. As\u00ed, puedo identificar qu\u00e9 bot\u00f3n se est\u00e1 tocando, bas\u00e1ndome en sus dos valores clave: letra (String) y n\u00famero.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fila de Asientos<\/h3>\n\n\n\n<p>Este widget construye una fila de asientos. Lo que hace es implementar la clase anterior y crear una fila compuesta por varios botones. La cantidad de botones depende del n\u00famero que se le pase al constructor, as\u00ed como de la letra. De este modo, cada bot\u00f3n ya tendr\u00e1 un identificador que ir\u00e1 de 1 a N, acompa\u00f1ado de una letra, como, por ejemplo, &#8216;A&#8217;.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"923\" height=\"445\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/mockup_row_buttons_cinema.jpg\" alt=\"flutter storyboard\" class=\"wp-image-673\"\/><\/figure>\n\n\n\n<p>La clase se denomina <code>RowSeats<\/code> y cuenta con dos propiedades clave: <code>specialSeats<\/code> y <code>blockSeats<\/code>. Ambas propiedades son listas de n\u00fameros enteros. En <code>specialSeats<\/code> se especifican cu\u00e1les botones del rango 1-N ser\u00e1n especiales, mientras que en <code>blockSeats<\/code> se se\u00f1alan cu\u00e1les estar\u00e1n bloqueados. Los botones especiales presentan un icono de silla de ruedas, y los bloqueados, que no tienen reacci\u00f3n al ser seleccionados (lugares no disponibles en la sala), muestran un icono de una persona.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fila de Letras y Lugares<\/h3>\n\n\n\n<p>Por \u00faltimo, existe una clase denominada <code>TheaterRows<\/code>. Esta clase representa la implementaci\u00f3n de cada una de las filas mencionadas anteriormente, junto con una letra que sirve como coordenada de la fila.<\/p>\n\n\n\n<p>En esta parte, se construye literalmente la vista, realizando c\u00e1lculos adicionales para generar un dise\u00f1o responsivo. La vista consta de dos columnas: una para mostrar las letras y otra para la fila de asientos. Estos \u00faltimos se crean enviando el n\u00famero de asientos y su correspondiente letra. Esta informaci\u00f3n se propaga hasta llegar al bot\u00f3n individual, que contendr\u00e1 un par clave-valor: letra y n\u00famero.<\/p>\n\n\n\n<p>Esta clase tambi\u00e9n recibe informaci\u00f3n sobre qu\u00e9 asientos est\u00e1n bloqueados y cu\u00e1les son especiales, y esta informaci\u00f3n se propaga durante la implementaci\u00f3n.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"909\" height=\"438\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/mockup_rows_letters_cinema.jpg\" alt=\"flutter storyboard\" class=\"wp-image-678\"\/><\/figure>\n\n\n\n<p>Como pueden observar en el dibujo, la idea es que no todas las filas tengan la misma cantidad de lugares.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Diseno responsivo<\/h3>\n\n\n\n<p>Dado que Flutter puede ejecutarse en m\u00faltiples sistemas y adaptarse a diferentes tama\u00f1os de pantalla, decid\u00ed crear un dise\u00f1o &#8216;Responsive&#8217;. El principal era conseguir un ajuste predominante en la anchura m\u00e1s que en la altura. Dado que Flutter permite una gran manipulaci\u00f3n a nivel de p\u00edxeles, con unos simples c\u00e1lculos es posible realizar los ajustes necesarios para adaptarse a distintos tama\u00f1os de pantalla.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1016\" height=\"718\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/mockup_responsive_guide_cinema.jpg\" alt=\"flutter storyboard\" class=\"wp-image-692\"\/><\/figure>\n\n\n\n<p>Como se puede ver en la imagen los tama\u00f1os y m\u00e1rgenes los base en el tama\u00f1o en horizontal del contenido <strong>S<\/strong>, esto puede ser el tama\u00f1o de la pantalla o el tama\u00f1o del contenedor de este widget.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>S: Tamano en ancho del contenedor.<\/li>\n\n\n\n<li>MX: n\u00famero mayor de lugares, la fila con m\u00e1s lugares.<\/li>\n\n\n\n<li>MRB: El espacio entre cada lugar (margen).<\/li>\n\n\n\n<li>MRF: Espacio entre cada fila (margen).<\/li>\n\n\n\n<li>BT: Lado del cuadrado del bot\u00f3n de letras.<\/li>\n\n\n\n<li>BN: Lado del cuadrado del bot\u00f3n de asientos.<\/li>\n\n\n\n<li>BR: Borde del bot\u00f3n<\/li>\n\n\n\n<li>T: Tamano del texto.<\/li>\n\n\n\n<li>N: Tamano del texto numerico.<\/li>\n<\/ul>\n\n\n\n<p>Todos estos c\u00e1lculos est\u00e1n expresados en porcentaje de ahi que mis multiplicadores sean 0.1, 0.7 etc.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">C\u00f3digo Flutter <\/h2>\n\n\n\n<p>Para implementar <code>TheaterRows<\/code> que es el widget que va a generar toda la vista se necesita enviar los siguientes par\u00e1metros:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Propiedad<\/th><th>Descripci\u00f3n<\/th><\/tr><\/thead><tbody><tr><td>rowsByLetter<\/td><td><code>Map&lt;String,int&gt;<\/code> donde se indica la letra y el n\u00famero de lugares por letra.<\/td><\/tr><tr><td>specialSeats<\/td><td><code>Map&lt;String,List&lt;int&gt;&gt;<\/code> donde se indica que asientos de que fila ser\u00e1n especiales (icono silla de ruedas)<\/td><\/tr><tr><td>blockSeats<\/td><td><code>Map&lt;String,List&lt;int&gt;&gt;<\/code> donde se indica que asientos de que fila estar\u00e1n bloqueados u ocupados (no reaccionar\u00e1n)<\/td><\/tr><tr><td>width<\/td><td><code>int<\/code>, tama\u00f1o en ancho del contenedor del widget<\/td><\/tr><tr><td>onTap<\/td><td><code>Function(String, int)<\/code>, es un m\u00e9todo que se propaga desde el <code>BtnRow<\/code> y obtiene la letra y numero de bot\u00f3n, fila y asiento b\u00e1sicamente.<\/td><\/tr><tr><td>maxZoom<\/td><td><code>double<\/code>, Este valor se utiliza para especificar el m\u00e1ximo nivel de zoom que podr\u00e1s aplicar a la vista. Muchas veces, debido a la gran cantidad de lugares, los cuadros resultan muy peque\u00f1os y se necesita aplicar zoom. Adem\u00e1s, observ\u00e9 que la aplicaci\u00f3n de Cin\u00e9polis incorpora esta misma funci\u00f3n, y quise replicarla<\/td><\/tr><tr><td>color<\/td><td><code>Color<\/code>, es el color del boton, por defecto es azul.<\/td><\/tr><tr><td>iconColor<\/td><td><code>Color<\/code>, es el color del icono sin seleccionar o bloqueado.<\/td><\/tr><tr><td>toggleIcon<\/td><td><code>IconData<\/code>, es el icono del asiento bloqueado o seleccionado.<\/td><\/tr><tr><td>toggleColor<\/td><td><code>Color<\/code>, es el color para el icono bloqueado o seleccionado.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Ejemplo Flutter <\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>TheaterRows(\n            rowsByLetter: Map.from({\n              'A': 15,\n              'B': 10,\n              'C': 9,\n            }),\n            specialSeats: Map.from({\n              'A': &#91;3],\n              'B': &#91;1, 2, 5],\n            }),\n            blockSeats: Map.from({\n              'A': &#91;4],\n              'B': &#91;3, 4],\n            }),\n            width: MediaQuery.of(context).size.width,\n            onTap: btnOntap,\n            maxZoom: 3\n          )<\/code><\/pre>\n\n\n\n<p>El codigo anterior generara la siguiente vista:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" width=\"1080\" height=\"2400\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/g_cine_vinew_screenshoot1.png\" alt=\"\" class=\"wp-image-709\" style=\"width:300px\" srcset=\"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/g_cine_vinew_screenshoot1.png 1080w, https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/g_cine_vinew_screenshoot1-691x1536.png 691w, https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/g_cine_vinew_screenshoot1-922x2048.png 922w\" sizes=\"(max-width: 1080px) 100vw, 1080px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Solo se generan tres filas de asientos, pero se podr\u00edan crear virtualmente ilimitadas; el c\u00f3digo no impone un l\u00edmite sobre cu\u00e1ntas filas y asientos por fila puede haber.<\/p>\n\n\n\n<p>El c\u00f3digo es una biblioteca para Flutter y no se integra dentro de un proyecto espec\u00edfico. M\u00e1s adelante, una vez que elabore un README adecuado y organice mejor el c\u00f3digo, tengo planes de subirlo a <a href=\"https:\/\/pub.dev\/\" target=\"_blank\" rel=\"noopener\" title=\"\">pub.dev<\/a>.<\/p>\n\n\n\n<p>Para utilizar el c\u00f3digo, basta con clonarlo directamente desde el repositorio y emplearlo como dependencia. Simplemente se debe agregar la ruta del proyecto local en la configuraci\u00f3n correspondiente, y estar\u00e1 listo para usarse.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>dependencies\n g_cinema_layout:\n     path: \/rutadelproyecto\/g_cinema_layout\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Consideraciones<\/h2>\n\n\n\n<p>El widget que crea toda la interfaz no tiene estado; por ende, no registra ni considera qu\u00e9 filas y qu\u00e9 asientos est\u00e1n seleccionados, los botones solo reaccionan y cambian pero no hay manera de obtener en un momento dado esa informacion. Dicha informaci\u00f3n deber\u00e1 almacenarse en otro lugar y se obtiene mediante la propagaci\u00f3n de cada bot\u00f3n individual a trav\u00e9s del m\u00e9todo <code>onTap<\/code>.<\/p>\n\n\n\n<p>Actualmente, no existe ninguna restricci\u00f3n respecto a la cantidad de lugares que se pueden seleccionar, y no hay ning\u00fan par\u00e1metro para imponer un l\u00edmite. Por lo tanto, de momento, es posible seleccionar todos los asientos disponibles. En el futuro, planeo incorporar una funcionalidad para restringir cuantos lugares se pueden seleccionar.<\/p>\n\n\n\n<p>La propiedad <code>rowsByLetter<\/code> es, en esencia, un <code>Map&lt;String, int><\/code>. Esto significa que, t\u00e9cnicamente, no se limita a usar letras como identificadores de las filas; tambi\u00e9n se puede emplear cualquier texto o n\u00famero, es decir, cualquier cadena de caracteres en realidad. No pienso meter una validacion aqui, tendria que extender el Map o hacer otro tipo de dato que le agregaria complejidad.<\/p>\n\n\n\n<p>Actualmente, el c\u00f3digo no cuenta con pruebas automatizadas. Planeo desarrollar e implementar tests adecuados antes de hacerlo disponible en pub.dev.<\/p>\n\n\n\n<p>Un aspecto distintivo entre este dise\u00f1o y el de Cin\u00e9polis radica en la disposici\u00f3n de los asientos. En esta implementaci\u00f3n, todos los lugares se generan desde el centro hacia los extremos, mientras que, seg\u00fan mi observaci\u00f3n, Cin\u00e9polis parece organizarlos de derecha a izquierda.<\/p>\n\n\n\n<p>Happy Coding!!<\/p>\n\n\n\n<p><strong><a href=\"https:\/\/github.com\/genitalico\/g_cinema_layout\" target=\"_blank\" rel=\"noopener\" title=\"\">Repositorio<\/a>.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Desarrollando un Widget Personalizado para Visualizar Lugares en un Cine con Flutter<\/p>\n","protected":false},"author":1,"featured_media":713,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[10,7,21,13,23,11,66,49,67],"tags":[8,27,25,24,9,73,85,84],"class_list":["post-641","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","category-apps","category-dart","category-dev","category-flutter","category-ios","category-linux","category-macos","category-windows","tag-android","tag-atomic-design","tag-dart","tag-flutter","tag-ios","tag-linux","tag-mac","tag-windows"],"jetpack_featured_media_url":"https:\/\/80bits.blog\/wp-content\/uploads\/2023\/10\/dogs_into_cinema.jpeg","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/641","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/comments?post=641"}],"version-history":[{"count":105,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/641\/revisions"}],"predecessor-version":[{"id":763,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/641\/revisions\/763"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media\/713"}],"wp:attachment":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media?parent=641"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/categories?post=641"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/tags?post=641"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}