diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..b32c93a Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/maps/c1/c1PavimentoSuperior.svg b/assets/maps/c1/c1PavimentoSuperior.svg new file mode 100644 index 0000000..96c176a --- /dev/null +++ b/assets/maps/c1/c1PavimentoSuperior.svg @@ -0,0 +1,6544 @@ + + + + + + + + + + + + + +PLANTA BAIXA . BLOCO C.01 - PAVIMENTO SUPERIOR +ESC.: 1/125 +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +SECRETARIA GERAL +INSTITUTO +SECRETARIA +SALA DE REUNIÃO +DEPÓSITO +DIRETORIA +GABINETE +VAZIO +VAZIO +VAZIO +VAZIO +4,84m² +11,65m² +7,94m² + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +4,48m² + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +15,88m² +28,16m² +16,24m² + + + + + +PMAT +VISITANTES +LABORATÓRIO DE PESQUISA 2 +LABORATÓRIO DE PESQUISA 3 +LABORATÓRIO DE PESQUISA 4 + + + + + + + +LDC 1 +LDC 2 +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +CIRCULAÇÃO +LDC 3 +LP 5 +LP 6 +LP 7 +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +CIRCULAÇÃO +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE +SALA DOCENTE + + + + + + + + + + +ALMOX. +16,98m² + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +W.C. PNE +W.C. MASC. +W.C. FEM. + + + + + + + +20,07m² +26,70m² +13,50m² + + + + + +104,90m² + + + + + +77,44m² +14,00m² +15,48m² +12,72m² +8,22m² +8,22m² +12,72m² +14,00m² +14,62m² + + + + + + + + + + + + + + + +12,73m² +8,20m² +8,23m² +12,73m² +14,00m² +11,58m² +11,36m² +14,00m² +14,00m² + + + + + + + + + + + +67,05m² +12,78m² + + + + + + + +34,67m² +33,60m² +34,67m² + + + + + +20,07m² + + + + + + + +W.C. PNE +W.C. FEM. +W.C. MASC. +COZINHA + + + + + + + + +SALA DOCENTE + + + + + + + + + + + + + + + + + + + + + + + + + + +12,73m² +8,20m² +8,23m² +12,73m² +14,00m² +14 +11,58 +14 +11,36 + + + + + + + + + + + +12,73m² +8,20m² +8,23m² +12,73m² + + + + + + + +14 +9,42 +5,11 +14 +14,49 +8,22 +8,22 +12,72 +12,72 +11,69 +11,69 +11,69 +11,69 +8,22 +8,22 +12,72 +12,72 +14 +14,49 +14,49 +14 +78,23 +25,69 + + + + + +16,94m² + + + + + + +47,87m² +16,77m² +8,09m² +11,54m² + + + + + + + + + + +76,52m² +CIRCULAÇÃO + + + + + +162,69m² +CIRCULAÇÃO + + + + + +61,24m² + + + + + + +CIRCULAÇÃO +95,14m² +174,81m² +3,31m² +11,78m² +4,50m² +5,87m² + + + + + + + + + +5,85 + + + + + + + + + +2,85 + + + + + + + + + +2,84 + + + + + + + + + +5,90 + + + + + + + + + +2,85 + + + + + + + + + +3,85 + + + + + + + + + +4,84 + + + + + + + + + +3,50 + + + + + + + + + +8,19 + + diff --git a/assets/maps/c1/c1PavimentoTerreo.svg b/assets/maps/c1/c1PavimentoTerreo.svg new file mode 100644 index 0000000..fd538c3 --- /dev/null +++ b/assets/maps/c1/c1PavimentoTerreo.svg @@ -0,0 +1,12023 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +i=1% + + + + + + + + +LEM +PMAT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +SALA DE AULA +139,44m² +SALA DE AULA +139,24m² +SALA DE AULA +104,67m² +JARDIM +79,72m² +CIRCULAÇÃO +LABORATÓRIO +78,33m² +SALA DE ESTUDO +52,07m² + + + + + + + + + + + + + + + + + + + + +SALA DE AULA +103,84m² +SALA DE ESTUDO +52,07m² +DEPÓSITO +24,92m² +BLACKBEE +52,51m² +BANHº +7,84m² +HALL +81,00m² +HALL +57,68m² +SALA DE AULA +104,49m² +AUDITÓRIO +142,04m² +ÁREA EXTERNA +80,53m² +CIRCULAÇÃO + + + + + + + + + + + + + + + + + + + + + + + + + + + +ALMOXARIFADO IFQ +52,07m² +HIALOTECNIA +52,00m² +CIRCULAÇÃO + + + + + + + + + + + + + + + + + + + + +LDF 6 +52,07m² +LAB. ÓTICA +52,07m² + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +DML + + + + + +DEPOSITO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +109,20m² + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +LABORATÓRIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +BANHº +11,67m² +PNE +2,89m² + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +105,02m² +LABORATÓRIO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +105,02m² +LABORATÓRIO + + + + + + + + + + + + + +81,73m² + + + + + +72,80m² + + + + + +128,03m² + + + + + +CIRCULAÇÃO +149,40m² + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PLANTA BAIXA . BLOCO C.01 - PAVIMENTO TÉRREO +ESC.: 1/125 +1 + + + + + + + + + + + + + + + +CIRCULAÇÃO + + + + + + + + + + + + + + + +MASCULINO +BANHEIRO +FEMININO +BANHEIRO +PNE + + + + + + + + + +23,01m² +16,11m² +3.52m² +4,15m² +6,29m² + + diff --git a/lib/app/app.color.dart b/lib/app/app.color.dart new file mode 100644 index 0000000..b45a38a --- /dev/null +++ b/lib/app/app.color.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; + +class AppColors { + // static const Color primary = Color(0xff003E1F); + // static const Color secondary = Color(0xff01110A); + static const Color primary = Colors.deepOrange; + static const Color secondary = Colors.orange; +} diff --git a/lib/app/app.constant.dart b/lib/app/app.constant.dart index 5b8a0a2..9ebfcbf 100644 --- a/lib/app/app.constant.dart +++ b/lib/app/app.constant.dart @@ -1,4 +1,8 @@ +/// Valid types: +/// +/// common, initial, intermediary. enum TypePoint { - path, - goal, + common, + entrance, + passage, } diff --git a/lib/app/app.repository.dart b/lib/app/app.repository.dart new file mode 100644 index 0000000..d02903e --- /dev/null +++ b/lib/app/app.repository.dart @@ -0,0 +1,71 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; + +class AppRepository { + static const String path = 'https://api-proex.onrender.com'; + static const String queryLogin = '/login'; + static const String queryUser = '/user'; + static const String queryAllUsers = '/users'; + static const String queryMap = '/maps'; + static const String queryBuilder = '/api/accounts'; + static const String queryOrganization = '/api/accounts'; + static const String queryPoints = '/points'; + + Dio dio = Dio(); + + Future post( + {required dynamic model, required String query, Options? options}) async { + const String erroMessage = "Erro na consulta"; + //print(model.toJson()); + try { + return await dio + .post( + AppRepository.path + query, + data: model.toJson(), + options: options, + ) + .then( + (res) { + return res.data.toString(); + }, + ); + } catch (e) { + if (kDebugMode) { + print("Erro em post:\n"); + print(e); + } + return erroMessage; + } + } + + Future get( + {required String id, required String query, Options? options}) async { + const String erroMessage = "Erro na consulta"; + if (kDebugMode) { + print("GET...\n"); + print(AppRepository.path + query + '/' + id); + } + try { + return await dio + .get( + AppRepository.path + query + '/' + id, + options: options, + ) + .then( + (res) { + return res.data["id"] == null + ? res.data.toString() + : jsonEncode(res.data); + }, + ); + } catch (e) { + if (kDebugMode) { + print("Erro em post:\n"); + print(e); + } + return erroMessage; + } + } +} diff --git a/lib/app/app.widget.dart b/lib/app/app.widget.dart index e877074..0f17a4a 100644 --- a/lib/app/app.widget.dart +++ b/lib/app/app.widget.dart @@ -1,23 +1,32 @@ import 'package:flutter/material.dart'; +import 'package:mvp_proex/app/app.color.dart'; +import 'package:mvp_proex/features/login/login.view.dart'; import 'package:mvp_proex/features/map/map.view.dart'; +import 'package:mvp_proex/features/mapselection/mapselection.view.dart'; +import 'package:mvp_proex/features/user/user.model.dart'; +import 'package:provider/provider.dart'; class AppWidget extends StatelessWidget { const AppWidget({Key? key}) : super(key: key); - // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - title: 'MVP Demo', - theme: ThemeData( - primarySwatch: Colors.deepOrange, - scaffoldBackgroundColor: Colors.black26, + return Provider( + create: (_) => UserModel(), + child: MaterialApp( + debugShowCheckedModeBanner: false, + title: 'MVP Demo', + theme: ThemeData( + primarySwatch: AppColors.primary as MaterialColor, + scaffoldBackgroundColor: Colors.black, + ), + initialRoute: '/login', + routes: { + '/map': (context) => const MapView(), + '/login': (context) => const LoginView(), + '/mapselection':(context) => const MapselectionView(), + }, ), - initialRoute: '/map', - routes: { - '/map': (context) => const MapView(), - }, ); } } diff --git a/lib/features/login/login.controller.dart b/lib/features/login/login.controller.dart new file mode 100644 index 0000000..082294f --- /dev/null +++ b/lib/features/login/login.controller.dart @@ -0,0 +1,13 @@ +import 'package:flutter/cupertino.dart'; +import 'package:rx_notifier/rx_notifier.dart'; + +class LoginController { + TextEditingController emailEditingController = TextEditingController(); + TextEditingController passwordEditingController = TextEditingController(); + + RxNotifier isVisible = RxNotifier(false); + bool get getIsVisible => isVisible.value; + + RxNotifier isLoading = RxNotifier(false); + bool get getIsLoading => isLoading.value; +} diff --git a/lib/features/login/login.view.dart b/lib/features/login/login.view.dart new file mode 100644 index 0000000..0b4951b --- /dev/null +++ b/lib/features/login/login.view.dart @@ -0,0 +1,219 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:rx_notifier/rx_notifier.dart'; +import 'package:mvp_proex/app/app.color.dart'; +import 'package:mvp_proex/features/login/login.controller.dart'; +import 'package:mvp_proex/features/user/user.model.dart'; +import 'package:mvp_proex/features/user/user.repository.dart'; +import 'package:mvp_proex/features/widgets/shared/button_submit.widget.dart'; +import 'package:mvp_proex/features/widgets/shared/form_field.widget.dart'; +import 'package:mvp_proex/features/widgets/shared/snackbar.message.dart'; + +class LoginView extends StatefulWidget { + const LoginView({Key? key}) : super(key: key); + + @override + _LoginViewState createState() => _LoginViewState(); +} + +class _LoginViewState extends State { + late UserModel userModel; + LoginController controller = LoginController(); + final _formKey = GlobalKey(); + + @override + void initState() { + super.initState(); + userModel = Provider.of(context, listen: false); + if (userModel.token != "") { + Navigator.of(context).pushReplacementNamed('/mapselection'); + } + } + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return Scaffold( + body: Center( + child: SingleChildScrollView( + child: Container( + width: size.width < 320 ? size.width * 0.8 : 280, + height: size.height, + alignment: Alignment.center, + child: Form( + key: _formKey, + child: Column( + children: [ + const Spacer( + flex: 4, + ), + const Text( + "Módulo\n1", + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.w700, + color: AppColors.primary, + ), + textAlign: TextAlign.center, + ), + const Spacer( + flex: 2, + ), + FormFieldWidget( + title: "E-mail", + description: "Entre com seu email", + validator: (String value) { + if (value.isEmpty) return "Campo vazio"; + if (value.length < 10) return "Campo muito pequeno"; + if (!value.contains("@")) return "Falta @"; + if (!value.contains("@")) return "Falta ."; + return null; + }, + controller: controller.emailEditingController, + onChanged: (value) { + userModel.email = value; + }, + icon: const SizedBox(), + keyboardType: TextInputType.emailAddress, + ), + RxBuilder( + builder: (context) { + return FormFieldWidget( + title: "Senha", + description: "Senha do sistema", + validator: (value) { + if (value.isEmpty) return "Campo vazio"; + return null; + }, + controller: controller.passwordEditingController, + onChanged: (value) { + userModel.password = value; + }, + keyboardType: TextInputType.text, + obscure: !controller.getIsVisible, + icon: IconButton( + icon: !controller.getIsVisible == true + ? const Icon(Icons.visibility) + : const Icon(Icons.visibility_off), + onPressed: () { + controller.isVisible.value = + !controller.getIsVisible; + }, + ), + ); + }, + ), + const Spacer( + flex: 2, + ), + RxBuilder( + builder: (context) { + return controller.getIsLoading + ? const CircularProgressIndicator() + : ButtonSubmitWidget( + textButton: "Entrar", + onPressed: () async { + if (_formKey.currentState!.validate()) { + controller.isLoading.value = true; + await UserRepository() + .login( + userModel: userModel, + ) + .then( + (value) async { + if (value.contains("Erro")) { + showMessageError( + context: context, text: value); + } else { + userModel.token = value; + //TODO: tratar possíveis erros de requisição + await UserRepository() + .getUser(value) + .then((othervalue) { + userModel.permission = + othervalue["role"]; + }); + Navigator.of(context) + .pushReplacementNamed( + '/mapselection'); + } + }, + ).whenComplete(() => + controller.isLoading.value = false); + } + }, + ); + }, + ), + RxBuilder( + builder: ((context) { + return controller.getIsLoading + ? const CircularProgressIndicator() + : TextButton( + onPressed: () async { + userModel.email = "gabriel@gmail.com"; + userModel.password = "123456"; + await UserRepository() + .login( + userModel: userModel, + ) + .then( + (value) async { + if (value.contains("Erro")) { + showMessageError( + context: context, text: value); + } else { + userModel.token = value; + //TODO: tratar possíveis erros de requisição + await UserRepository() + .getUser(value) + .then((othervalue) { + userModel.permission = + othervalue["role"]; + }); + Navigator.of(context) + .pushReplacementNamed( + '/mapselection'); + } + }, + ).whenComplete(() => + controller.isLoading.value = false); + }, + child: const Text( + "Automático", + style: TextStyle(color: Colors.white), + )); + }), + ), + const Spacer( + flex: 4, + ), + TextButton( + onPressed: () { + Navigator.of(context).pushNamed("/recovery-password"); + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "Recuperar Senha", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + ), + ), + const Spacer( + flex: 1, + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/map/map.view.dart b/lib/features/map/map.view.dart index 3fa90bf..8b749a3 100644 --- a/lib/features/map/map.view.dart +++ b/lib/features/map/map.view.dart @@ -1,27 +1,33 @@ import 'package:flutter/material.dart'; -import 'package:mvp_proex/features/model/person.model.dart'; -import 'package:mvp_proex/features/widgets/svg_map.widget.dart'; +import 'package:mvp_proex/features/svg_map/svg_map.view.dart'; class MapView extends StatefulWidget { - const MapView({Key? key}) : super(key: key); + final String? mysvgPath; + final String? mapId; + final String? mapName; + const MapView({ + Key? key, + this.mysvgPath, + this.mapId, + this.mapName, + }) : super(key: key); @override State createState() => _MapViewState(); } class _MapViewState extends State { - PersonModel person = PersonModel(639, 274, 0, -22.2467586, -45.0171148, 0); - @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: SVGMap( - svgPath: "assets/maps/reitoria/mapaTeste.svg", + svgPath: widget.mysvgPath ?? "assets/maps/c1/c1PavimentoSuperior.svg", svgWidth: 800, svgHeight: 600, svgScale: 1.3, - person: person, + mapID: widget.mapId, + mapName: widget.mapName, ), ), ); diff --git a/lib/features/mapselection/mapselection.repository.dart b/lib/features/mapselection/mapselection.repository.dart new file mode 100644 index 0000000..3f8e38a --- /dev/null +++ b/lib/features/mapselection/mapselection.repository.dart @@ -0,0 +1,28 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:mvp_proex/app/app.repository.dart'; + +class MapselectionRepository extends AppRepository { + Future getMapList({required String token}) async { + if (kDebugMode) { + print("Get maps..."); + } + try { + return await dio + .get( + AppRepository.path + AppRepository.queryMap, + options: Options( + headers: { + "Authorization": "Bearer $token", + }, + responseType: ResponseType.json, + ), + ) + .then((res) { + return res.data; + }); + } on DioError { + rethrow; + } + } +} diff --git a/lib/features/mapselection/mapselection.view.dart b/lib/features/mapselection/mapselection.view.dart new file mode 100644 index 0000000..895bf91 --- /dev/null +++ b/lib/features/mapselection/mapselection.view.dart @@ -0,0 +1,150 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:mvp_proex/app/app.color.dart'; +import 'package:mvp_proex/features/map/map.view.dart'; +import 'package:mvp_proex/features/mapselection/mapselection.repository.dart'; +import 'package:mvp_proex/features/user/user.model.dart'; +import 'package:mvp_proex/features/user/user.repository.dart'; +import 'package:provider/provider.dart'; + +class MapselectionView extends StatefulWidget { + const MapselectionView({Key? key}) : super(key: key); + + @override + State createState() => _MapselectionViewState(); +} + +class _MapselectionViewState extends State { + late UserModel userModel; + UserRepository repository = UserRepository(); + final _formKey = GlobalKey(); + + StreamController _streamController = StreamController(); + List listOfMaps = []; + void getAllMaps() { + _streamController = StreamController(onListen: () async { + try { + listOfMaps = await MapselectionRepository().getMapList( + token: userModel.token, + ); + _streamController.sink.add("event"); + _streamController.close(); + } on DioError catch (e) { + switch (e.response!.statusCode) { + case 404: + if (kDebugMode) { + print("Not found"); + } + break; + case 401: + if (kDebugMode) { + print("Unauthorized"); + } + break; + default: + break; + } + _streamController.addError("error"); + } + }); + } + + @override + void initState() { + super.initState(); + userModel = Provider.of(context, listen: false); + if (userModel.token == "") { + Navigator.of(context).pushReplacementNamed('/login'); + } + getAllMaps(); + } + + @override + void dispose() { + _streamController.close(); + super.dispose(); + } + + Map mapa = {}; + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: _streamController.stream, + builder: (context, snapshot) { + if (snapshot.hasError) { + return const Scaffold( + body: Text("Ocorreu um erro"), + ); + } else if (snapshot.connectionState != ConnectionState.done) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } + return Scaffold( + body: Center( + child: SizedBox( + width: 300, + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + children: [ + const Text( + "Selecione o mapa a ser visualizado:", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + DropdownButtonFormField( + dropdownColor: Color.fromARGB( + AppColors.primary.alpha, + AppColors.primary.red ~/ 2, + AppColors.primary.green ~/ 2, + AppColors.primary.blue ~/ 2), + items: listOfMaps + .map((mapaIndividual) => DropdownMenuItem( + child: Text( + mapaIndividual["name"] ?? "ERR", + style: const TextStyle(color: Colors.white), + ), + value: mapaIndividual, + )) + .toList(), + value: listOfMaps[0], + onChanged: (value) => { + mapa = value as Map, + }, + ), + const SizedBox( + height: 30, + ), + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => MapView( + mysvgPath: mapa["source"], + mapId: mapa["id"], + mapName: mapa["name"], + ))); + }, + child: const Text("Acessar mapa")), + ], + ), + ), + ), + ), + )); + }, + ); + } +} diff --git a/lib/features/model/person.model.dart b/lib/features/model/person.model.dart deleted file mode 100644 index 1230685..0000000 --- a/lib/features/model/person.model.dart +++ /dev/null @@ -1,115 +0,0 @@ -// ignore_for_file: prefer_final_fields -import 'dart:async'; -import 'dart:io'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:geolocator/geolocator.dart'; - -class PersonModel { - double _x; - double _y; - double _z; - - double _lat; - double _lon; - double _alt; - - // getters - double get x => _x; - double get y => _y; - double get z => _z; - - double get lat => _lat; - double get lon => _lon; - double get alt => _alt; - - // setters - set setx(double value) { - _x = value; - //TODO: atualizar lat, lon, alt - } - - set sety(double value) { - _y = value; - //TODO: atualizar lat, lon, alt - } - - set setz(double value) { - _z = value; - //TODO: atualizar lat, lon, alt - } - - set setLat(double newLat) { - //calcular novo x com base na lat - double delta = lat - newLat; - _x += delta; - } - - set setLon(double newLon) { - double delta = lon - newLon; - _y += delta; - } - - // construtor - PersonModel(this._x, this._y, this._z, this._lat, this._lon, this._alt) { - if (Platform.isAndroid) { - while (true) { - Future.delayed(Duration(seconds: 2)).then((value) => - _determinePosition().then((value) => _lat += - _lat - double.parse(value.latitude.toStringAsFixed(3)))); - } - } - } - - Future _determinePosition() async { - bool serviceEnabled; - LocationPermission permission; - - // Test if location services are enabled. - serviceEnabled = await Geolocator.isLocationServiceEnabled(); - if (!serviceEnabled) { - // Location services are not enabled don't continue - // accessing the position and request users of the - // App to enable the location services. - return Future.error('Location services are disabled.'); - } - - permission = await Geolocator.checkPermission(); - if (permission == LocationPermission.denied) { - permission = await Geolocator.requestPermission(); - if (permission == LocationPermission.denied) { - // Permissions are denied, next time you could try - // requesting permissions again (this is also where - // Android's shouldShowRequestPermissionRationale - // returned true. According to Android guidelines - // your App should show an explanatory UI now. - return Future.error('Location permissions are denied'); - } - } - - if (permission == LocationPermission.deniedForever) { - // Permissions are denied forever, handle appropriately. - return Future.error( - 'Location permissions are permanently denied, we cannot request permissions.'); - } - - // When we reach here, permissions are granted and we can - // continue accessing the position of the device. - return await Geolocator.getCurrentPosition(); - } - - /*StreamSubscription serviceStatusStream = - Geolocator.getServiceStatusStream().listen((ServiceStatus status) { - print(status); - }); - - StreamSubscription positionStream = - Geolocator.getPositionStream().listen((Position position) {});*/ - - // toString - @override - String toString() { - return 'PersonModel{_x: $_x, _y: $_y, _z: $_z, _lat: $_lat, _lon: $_lon, _alt: $_alt}'; - } -} diff --git a/lib/features/organization/organization.model.dart b/lib/features/organization/organization.model.dart new file mode 100644 index 0000000..cfd2989 --- /dev/null +++ b/lib/features/organization/organization.model.dart @@ -0,0 +1,38 @@ +class OrganizationModel { + late String id; + late String name; + late String description; + late String cep; + late String state; + late String city; + late String neighborhood; + late String street; + late String number; + + OrganizationModel(); + + OrganizationModel.fromJson(Map json) { + id = json["id"] ?? ""; + name = json["name"]; + description = json["description"]; + cep = json["cep"]; + state = json["state"]; + city = json["city"]; + neighborhood = json["neighborhood"]; + street = json["street"]; + number = json["number"]; + } + + Map toJson() { + Map json = {}; + json["name"] = name; + json["description"] = description; + json["cep"] = cep; + json["state"] = state; + json["city"] = city; + json["neighborhood"] = neighborhood; + json["street"] = street; + json["number"] = number; + return json; + } +} diff --git a/lib/features/organization/organization.repository.dart b/lib/features/organization/organization.repository.dart new file mode 100644 index 0000000..8fc43d7 --- /dev/null +++ b/lib/features/organization/organization.repository.dart @@ -0,0 +1,61 @@ +import 'dart:developer'; + +import 'package:dio/dio.dart'; +import 'package:mvp_proex/app/app.repository.dart'; +import 'package:mvp_proex/features/organization/organization.model.dart'; + +class OrganizationRepository extends AppRepository { + + Future createorganization( + OrganizationModel organizationModel, String token) async { + try { + return await dio + .post(AppRepository.path + AppRepository.queryOrganization, + data: organizationModel.toJson(), + options: Options(headers: { + 'Authorization': 'Bearer $token', + })) + .then( + (value) { + return value.toString(); + }, + ); + } on DioError catch (e) { + if (e.response != null) { + if (e.response!.statusCode == 404) { + return "API Offline"; + } + return e.response!.statusCode.toString(); + } + return e.toString(); + } catch (e) { + return e.toString(); + } + } + + Future getall(String token) async { + try { + return await dio + .get(AppRepository.path + "/organizations", + options: Options(headers: { + 'Authorization': 'Bearer $token', + })) + .then( + (value) { + log(value.data.toString()); + return value.toString(); + }, + ); + } on DioError catch (e) { + if (e.response != null) { + if (e.response!.statusCode == 404) { + return "API Offline"; + } + return e.response!.statusCode.toString(); + } + return e.toString(); + } catch (e) { + return e.toString(); + } + } +} diff --git a/lib/features/point/point.model.dart b/lib/features/point/point.model.dart new file mode 100644 index 0000000..27f9b52 --- /dev/null +++ b/lib/features/point/point.model.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +PointModel pointModelFromJson(String str) => + PointModel.fromJson(json.decode(str)); + +String pointModelToJson(PointModel data) => json.encode(data.toJson()); + +class PointModel { + String uuid=""; + String name=""; + String description=""; + double x=0.0; + double y=0.0; + int floor=0; + String mapId=""; + + PointModel( + {this.uuid = "", + this.name = "", + this.description = "", + this.x = 0.0, + this.y = 0.0, + this.floor = 1, + this.mapId = ""}); + + PointModel.fromJson(Map json) { + uuid = json["id"] ?? ""; + name = json["name"]; + description = json["description"]; + x = json["x"].toDouble(); + y = json["y"].toDouble(); + floor = json["floor"]; + mapId = json["map_id"]; + } + + Map toJson() => { + // se o uuid for vazio é porque acabou de criar, então não passa para o post + if(uuid != "") + "id": uuid, + "name": name, + "description": description, + "floor": floor, + "x": x, + "y": y, + "map_id": mapId, + }; +} diff --git a/lib/features/point/point.repository.dart b/lib/features/point/point.repository.dart new file mode 100644 index 0000000..1214b31 --- /dev/null +++ b/lib/features/point/point.repository.dart @@ -0,0 +1,171 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:mvp_proex/app/app.repository.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:mvp_proex/features/point/point_parent.model.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Classe utilizada para a comunicação com o banco referente às ações de ponto. +/// +/// O token utilizado está sendo passado por SHARED PREFERENCES, caso seja necessário basta mudar aqui para PROVIDER. +class PointRepository extends AppRepository { + /// Pega todos os pontos do banco. + Future getAllPoints() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String token = prefs.getString("token") ?? ""; + const String erroMessage = "Erro na consulta"; + try { + if (kDebugMode) { + print("Get all points..."); + } + return await dio + .get( + AppRepository.path + AppRepository.queryPoints, + options: Options( + headers: {"Authorization": "Bearer $token"}, + responseType: ResponseType.plain), + ) + .then( + (res) { + return res.toString(); + }, + ); + } catch (e) { + return erroMessage; + } + } + + /// Salva um ponto no banco. + Future postPoint(String pointClass, PointModel point) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String token = prefs.getString("token") ?? ""; + try { + if (kDebugMode) { + print("Post point..."); + } + return await dio + .post( + AppRepository.path + AppRepository.queryPoints + "/" + pointClass, + options: Options( + headers: {"Authorization": "Bearer $token"}, + responseType: ResponseType.plain), + data: point.toJson(), //TODO: arrumar o .toJson + ) + .then((res) { + return res.toString(); + }); + } on DioError { + if (kDebugMode) { + print("ERRO no post"); + } + rethrow; + } + } + + /// Pega todos os pontos de um mapa. + /// + /// Retorna um vetor de pontos no tipo Map + Future getMapPoints(String mapID) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String token = prefs.getString("token") ?? ""; + try { + if (kDebugMode) { + print("Get map points..."); + } + return await dio + .get( + AppRepository.path + AppRepository.queryMap + "/" + mapID, + options: Options( + headers: {"Authorization": "Bearer $token"}, + responseType: ResponseType.json, + ), + ) + .then( + (res) { + return (json.decode(res.toString())['points']); + }, + ); + } on DioError { + // Não é necessário catch, porque o erro que ocorrer aqui deve ser tratado no svg_map.view, então apenas repassa o erro para lá + // Pode ser que seja melhor retornar uma string sobre o erro (ao invés do erro todo), e então apenas mostrar a mensagem, mas aí ocorreria um erro inesperado - não haveria nenhum throw aqui, o erro em si só aconteceria no svg_map ao tentar tratar a string da mensagem de erro de resposta como se fosse a lista de pontos esperada. + // É melhor então tratar um erro que nós mesmos jogamos ou tratar qualquer erro inesperado indefinidamente? + rethrow; + } + } + + /// Edita um ponto no banco. + Future editPoint(PointModel point) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String token = prefs.getString("token") ?? ""; + try { + Map dataSent = point.toJson(); + dataSent.remove("children"); // não é necessário mandar o children no PUT + if (kDebugMode) { + print("Edit point..."); + } + return await dio + .put( + AppRepository.path + + AppRepository.queryPoints + + "/" + + (point is PointParent ? "parent" : "child") + + "/" + + point.uuid, + options: Options( + headers: {"Authorization": "Bearer $token"}, + responseType: ResponseType.plain), + data: dataSent, + ) + .then( + (res) { + // print(res.toString()); + return res.toString(); + }, + ); + } on DioError { + if (kDebugMode) { + print("ERRO em editPoint"); + } + rethrow; + } + } + + /// Deleta um ponto no banco. + /// + /// [pointClass] é o tipo do ponto, necessário para a rota; **deve ser _parent_ ou _child_**. + /// + /// [pointId] é o id do ponto a ser deletado. + Future deletePoint(String pointClass, String pointId) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String token = prefs.getString("token") ?? ""; + try { + if (kDebugMode) { + print("Delete point..."); + } + return await dio + .delete( + AppRepository.path + + AppRepository.queryPoints + + "/" + + pointClass + + "/" + + pointId, + options: Options( + headers: {"Authorization": "Bearer $token"}, + responseType: ResponseType.plain), + ) + .then( + (res) { + return res.toString(); + }, + ); + } on DioError { + if (kDebugMode) { + print("ERRO em deletePoint"); + } + rethrow; + } + } +} diff --git a/lib/features/point/point.widget.dart b/lib/features/point/point.widget.dart new file mode 100644 index 0000000..546a3da --- /dev/null +++ b/lib/features/point/point.widget.dart @@ -0,0 +1,91 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:mvp_proex/app/app.constant.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:mvp_proex/features/point/point_child.model.dart'; +import 'package:mvp_proex/features/point/point_parent.model.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +// Um vetor de mapas é mapeado para um vetor de pointWidgets +// O ponto é dado pelo json, mas para ser mostrado no mapa ele precisa estar como PointWidget +// Precisamos que o ponto seja dado por um model +// Teremos então ao invés de um vetor de mapas, um vetor de PointModels +// Depois se mapeia então os pointModels para PointWidgets + +class PointWidget extends StatefulWidget { + // ignore: prefer_typing_uninitialized_variables + final point; + final double side; + final Function()? onPressed; + final String idPontoAnterior; + + const PointWidget({ + Key? key, + required this.point, + required this.side, + this.onPressed, + required this.idPontoAnterior, + }) : super(key: key); + + @override + State createState() => _PointWidgetState(); +} + +class _PointWidgetState extends State { + SharedPreferences? prefs; + PointModel? pontoAnterior; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + getColor() { + if (widget.point is PointChild) { + return Colors.green; + } else if (widget.point is PointParent) { + if (widget.point.uuid == widget.idPontoAnterior) { + return Colors.yellow; + } + switch (widget.point.type) { + case TypePoint.common: + return Colors.red; + case TypePoint.passage: + return Colors.orange; + case TypePoint.entrance: + return Colors.blue; + default: + return Colors.yellow; + } + } else { + if (kDebugMode) { + print( + "ERRO em point.widget.\nTipo inesperado, esperado PointChild ou PointParent, recebido \"" + + widget.point.runtimeType.toString() + + "\"."); + } + return Colors.pink; + } + } + + return Positioned( + top: widget.point.y - widget.side / 2, + left: widget.point.x - widget.side / 2, + child: InkWell( + onTap: + kIsWeb || Platform.isLinux || Platform.isMacOS || Platform.isWindows + ? widget.onPressed + : null, + child: Container( + color: getColor(), + width: widget.side, + height: widget.side, + ), + ), + ); + } +} diff --git a/lib/features/point/point_child.model.dart b/lib/features/point/point_child.model.dart new file mode 100644 index 0000000..eed25e1 --- /dev/null +++ b/lib/features/point/point_child.model.dart @@ -0,0 +1,43 @@ +import 'package:mvp_proex/features/point/point.model.dart'; + +/// Cria um objeto ponto filho, que herda do objeto ponto. +/// ```dart +/// var exemplo = PointChild(); +/// exemplo.x = 10.0; +/// exemplo.isObstacle = false; +/// +/// var novoexemplo = PointChild.fromJson(json); +/// // json (ou subjson) deve ser: +/// { +/// id: "string", +/// x: 0.0, +/// // ... y, name, description, floor, mapId, +/// parentId: "string" +/// isObstacle: false +/// } +/// ``` +class PointChild extends PointModel { + String parentId = ""; + bool isObstacle = false; + + PointChild({ + this.parentId = "", + this.isObstacle = false, + }); + + PointChild.fromJson(Map json) : super.fromJson(json) { + parentId = json["point_parent_id"]; + isObstacle = json["is_obstacle"]; + } + + @override + Map toJson() { + final Map childData = { + "point_parent_id": parentId, + "is_obstacle": isObstacle + }; + Map returnVal = super.toJson(); + returnVal.addEntries(childData.entries); + return returnVal; + } +} diff --git a/lib/features/point/point_parent.model.dart b/lib/features/point/point_parent.model.dart new file mode 100644 index 0000000..c83f438 --- /dev/null +++ b/lib/features/point/point_parent.model.dart @@ -0,0 +1,86 @@ +import 'package:mvp_proex/app/app.constant.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:mvp_proex/features/point/point_child.model.dart'; +import 'dart:convert'; + +/// Retorna um PointParent dado um json no tipo String +PointParent pointParentFromJson(String str) => + PointParent.fromJson(json.decode(str)); + +/// Cria um objeto ponto pai, que herda do objeto ponto. +/// ```dart +/// var exemplo = PointParent(); +/// exemplo.x = 10.0; +/// exemplo.type = TypePoint.entrance; +/// +/// var novoexemplo = PointParent.fromJson(json); +/// // json deve ser: +/// { +/// id: "string", +/// x: 0.0, +/// // ... y, name, description, floor, mapId, +/// neighbors: [{ +/// id: "string" +/// direction: "string" +/// }], +/// children: [{PointChild.toJson()}], // As childrens devem ser objetos completos em formato json +/// type: "initial" +/// } +/// ``` +class PointParent extends PointModel { + // fields + List> neighbor = []; + List children = []; + TypePoint type = TypePoint.common; + + // generative constructor (no formal parameters) + // calls the zero parameter super constructor + PointParent() { + type = TypePoint.common; + neighbor = []; + children = []; + } + + // named constructor + /// Constrói um PointParent dado um json no tipo Map + PointParent.fromJson(Map json) : super.fromJson(json) { + if (json["type"] != null) { + json["type"] == "entrance" + ? type = TypePoint.entrance + : json["type"] == "passage" + ? type = TypePoint.passage + : type = TypePoint.common; + } else { + type = TypePoint.common; + } + if (json["neighbor"] != null) { + for (var element in json["neighbor"]) { + neighbor.add(element as Map); + } + } + if (json["children"] != null) { + for (var element in json["children"]) { + children.add(PointChild.fromJson(element)); + } + } + } + + @override + Map toJson() { + List> pointChildList = []; + for (PointChild element in children) { + pointChildList.add(element.toJson()); + } + + final Map parentData = { + "neighbor": neighbor, + "type": type.name, + "children": pointChildList, + }; + + Map returnVal = super.toJson(); + returnVal.addEntries(parentData.entries); + + return returnVal; + } +} diff --git a/lib/features/controller/svg_map.controller.dart b/lib/features/svg_map/svg_map.controller.dart similarity index 100% rename from lib/features/controller/svg_map.controller.dart rename to lib/features/svg_map/svg_map.controller.dart diff --git a/lib/features/svg_map/svg_map.view.dart b/lib/features/svg_map/svg_map.view.dart new file mode 100644 index 0000000..32e5698 --- /dev/null +++ b/lib/features/svg_map/svg_map.view.dart @@ -0,0 +1,901 @@ +import 'dart:io'; +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'dart:math' as math; +import 'package:flutter_svg/svg.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:dio/dio.dart'; +import 'package:mvp_proex/features/point/point_child.model.dart'; +import 'package:mvp_proex/features/point/point_parent.model.dart'; +import 'package:mvp_proex/features/svg_map/svg_map_flags.dart'; +import 'package:mvp_proex/features/widgets/dialog_view_point.dart'; +import 'package:mvp_proex/features/widgets/shared/snackbar.message.dart'; +import 'package:mvp_proex/features/widgets/painters.widget.dart'; +import 'package:mvp_proex/features/user/user.model.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:mvp_proex/features/point/point.widget.dart'; +import 'package:mvp_proex/features/point/point.repository.dart'; +import 'package:mvp_proex/features/widgets/point_valid.widget.dart'; +import 'package:mvp_proex/features/widgets/custom_appbar.widget.dart'; +import 'package:mvp_proex/features/widgets/dialog_add_point.widget.dart'; +import 'package:mvp_proex/invoice_service.dart'; + +class SVGMap extends StatefulWidget { + /// Define o caminho do asset: + /// + /// ```dart + /// SVGMap( + /// svgPath: "assets/maps/c1/c1PavimentoTerreo.svg", + /// ... + /// ), + /// ``` + final String svgPath; + + /// Define a largura do SVG, em pixel + /// + /// ```dart + /// SVGMap( + /// ... + /// svgWidth: 800, + /// ... + /// ), + /// ``` + final double svgWidth; + + /// Define a altura do SVG, em pixel + /// + /// ```dart + /// SVGMap( + /// ... + /// svgHeight: 600, + /// ... + /// ), + /// ``` + final double svgHeight; + + /// Define a scala inicial do SVG, em pixel + /// Por padrão ele é 1.0 + /// + /// ```dart + /// SVGMap( + /// ... + /// svgScale: 1.1, + /// ... + /// ), + /// ``` + final double svgScale; + + /// É o id do mapa que contém os pontos + final String? mapID; + + /// O nome do mapa para ser passado para a CustomAppBar + final String? mapName; + + const SVGMap({ + Key? key, + required this.svgPath, + required this.svgWidth, + required this.svgHeight, + this.mapID, + this.mapName, + this.svgScale = 1, + }) : super(key: key); + + @override + State createState() => _SVGMapState(); +} + +class _SVGMapState extends State { + bool isAdmin = false; + + double? x, y; + double top = 0, left = 0; + + late double scaleFactor; + + bool flagScale = true; + bool flagDuration = false; + + late final Widget svg; + + late PointParent pontoAnterior; + List newPointList = []; + + final PdfInvoiceService service = PdfInvoiceService(); + + void centralizar(bool flagScale) { + setState(() { + flagDuration = flagScale; + top = ((pontoAnterior.y - MediaQuery.of(context).size.height / 2) + + 2 * AppBar().preferredSize.height) * + -1; + left = (pontoAnterior.x - MediaQuery.of(context).size.width / 2) * -1; + }); + } + + late UserModel userModel; + late PointParent pontoRecebido; + String erroMessage = "ERRO INESPERADO"; + String erroDetalhe = "ERRO INESPERADO"; + + StreamController _streamcontroller = StreamController(); + Future fetchMapPoints() async { + _streamcontroller = StreamController( + onPause: () => debugPrint('Paused'), + onResume: () => debugPrint('Resumed'), + onCancel: () async { + debugPrint('Cancelled'); + if (SvgMapFlags.modoAdicao == "inicial") { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text( + "Novo Mapa", + textAlign: TextAlign.center, + ), + content: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + Text("Esse mapa não possui pontos"), + Text( + "Como você é um administrador, insira um ponto inicial para este mapa."), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pushReplacementNamed(context, '/mapselection'); + }, + child: const Text("Voltar")), + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text( + "OK", + style: TextStyle(color: Colors.green), + )), + ], + actionsAlignment: MainAxisAlignment.center, + ), + ); + } + }, + onListen: () async { + SharedPreferences prefs; + prefs = await SharedPreferences.getInstance(); + prefs.setString("token", userModel.token); + try { + await PointRepository() + .getMapPoints(widget.mapID!) + .then( + (res) => { + if (res is! List) + { + debugPrint( + "ERRO na resposta de getMapPoint.\nO retorno deve ser uma lista de pontos\nTipo esperado: List, tipo recebido: " + + res.runtimeType.toString()), + erroMessage = "Erro no servidor.", + erroDetalhe = "Tipo inesperado.", + _streamcontroller.addError(erroMessage), + } + else + { + if (res.isEmpty) + { + if (userModel.permission != "super") + { + erroMessage = "Não há pontos no mapa.", + erroDetalhe = + "Peça à um administrador para adicionar o ponto inicial do mapa.", + _streamcontroller.addError(erroMessage), + } + else + { + SvgMapFlags.modoAdicao = "inicial", + _streamcontroller.sink.add("1"), + _streamcontroller.close(), + }, + } + else + { + for (var ponto in res) + { + pontoRecebido = PointParent.fromJson(ponto), + newPointList.add(pontoRecebido), + if (pontoRecebido.children.isNotEmpty) + { + for (PointChild filho + in pontoRecebido.children) + { + newPointList.add(filho), + } + } + }, + prefs.setString( + "pontoAnterior", + pointModelToJson(newPointList.first), + ), + SvgMapFlags.modoAdicao = "caminho", + // O primeiro elemento da lista não pode ser um ponto filho + pontoAnterior = newPointList.first as PointParent, + centralizar(flagScale), + _streamcontroller.sink.add("1"), + _streamcontroller.close(), + }, + }, + }, + ); + } on DioError catch (e) { + if (kDebugMode) { + print(e.response?.data); + } + if (e.response != null) { + switch (e.response!.statusCode) { + case 400: + erroMessage = "[400] Bad Request"; + erroDetalhe = "Verifique a requisição"; + break; + case 401: + erroMessage = "[401] Não autorizado"; + erroDetalhe = "O usuário não possui autorização"; + break; + case 404: + erroMessage = "[404] Não encontrado"; + erroDetalhe = + e.response!.data["message"] ?? e.response!.statusMessage; + break; + default: + erroMessage = "Erro desconhecido (" + + e.response!.statusCode.toString() + + ") - contate o suporte"; + if (e.response!.data is! String) { + erroDetalhe = e.response!.data["message"]; + } else { + erroDetalhe = e.response!.data!; + } + break; + } + } else { + erroMessage = "Erro desconhecido"; + erroDetalhe = "Não houve resposta do servidor"; + } + _streamcontroller.addError(erroMessage); + } + }, + ); + _streamcontroller.add("1"); + } + + @override + void dispose() { + _streamcontroller.close(); + super.dispose(); + } + + @override + void initState() { + userModel = Provider.of(context, listen: false); + if (userModel.token == "") { + Navigator.pushReplacementNamed(context, '/login'); + } + fetchMapPoints(); + + scaleFactor = widget.svgScale; + svg = SvgPicture.asset( + widget.svgPath, + color: Colors.white, + fit: BoxFit.none, + ); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: _streamcontroller.stream, + builder: (context, snapshot) { + Widget childVar; + if (snapshot.hasError) { + childVar = Scaffold( + body: AlertDialog( + title: const Text("Erro de conexão"), + content: SingleChildScrollView( + child: Column( + children: [ + const Text( + "Ocorreu um erro ao se conectar com o servidor:\n"), + Text( + erroMessage, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Text(erroDetalhe), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pushReplacementNamed(context, '/mapselection'); + }, + child: const Text("Voltar")), + TextButton( + onPressed: () { + if (kDebugMode) { + print("Tentar conexão novamente.."); + } + fetchMapPoints(); + setState(() {}); + }, + child: const Text("Tentar novamente")), + ], + ), + ); + } else if (snapshot.connectionState != ConnectionState.done) { + childVar = Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + SizedBox( + width: 60, + height: 60, + child: CircularProgressIndicator(), + ), + Padding( + padding: EdgeInsets.only(top: 16), + child: Text( + 'Aguardando servidor...', + style: TextStyle( + color: Colors.amber, + ), + ), + ), + ], + ), + ); + } else { + bool isValidX = SvgMapFlags.modoAdicao == "inicial" || + pontoAnterior.x > ((x ?? 1) - 1) && + pontoAnterior.x < ((x ?? 0) + 1); + + bool isValidY = SvgMapFlags.modoAdicao == "inicial" || + pontoAnterior.y > ((y ?? 1)) - 1 && + pontoAnterior.y < ((y ?? 0) + 1); + + bool isValid = isValidX || isValidY; + + childVar = Scaffold( + appBar: CustomAppBar( + height: 100, + child: Stack( + alignment: Alignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.home_work, + size: 30, + color: Colors.white, + ), + const SizedBox( + width: 20, + ), + Text( + widget.mapName ?? "Bloco C", + style: const TextStyle( + fontSize: 18, + color: Colors.white, + ), + ), + ], + ), + Positioned( + top: -10, + left: 0, + child: IconButton( + icon: const Icon( + Icons.qr_code_outlined, + size: 35, + color: Colors.white, + ), + onPressed: () async { + final data = await service.createPDF(newPointList); + service.savePdfFile("QR_Todos", data); + }, + ), + ), + Positioned( + top: -10, + right: 0, + child: IconButton( + icon: const Icon( + Icons.arrow_back_rounded, + size: 35, + color: Colors.white, + ), + onPressed: () { + Navigator.pushReplacementNamed( + context, '/mapselection'); + }, + ), + ) + ], + ), + ), + body: Transform.rotate( + angle: math.pi / 0.5, + child: Transform.scale( + scale: scaleFactor, + child: Stack( + children: [ + AnimatedPositioned( + duration: flagDuration + ? const Duration(milliseconds: 500) + : const Duration(milliseconds: 0), + top: top, + left: left, + child: MouseRegion( + onHover: (event) { + if (isAdmin) { + setState(() { + x = event.localPosition.dx; + y = event.localPosition.dy; + }); + } + }, + child: GestureDetector( + onDoubleTap: () { + setState( + () { + if (flagScale) { + scaleFactor *= 2; + flagScale = false; + } else { + scaleFactor *= 1 / 2; + flagScale = true; + } + }, + ); + }, + onPanUpdate: (details) { + setState( + () { + flagDuration = false; + top = top + details.delta.dy; + left = left + details.delta.dx; + }, + ); + }, + // Adiciona um ponto. + // Se não estiver no modo caminho não precisa validar se está na linha + // No final da adição, se houve adição de ponto ele é colocado na lista; se era adição de ponto filho ele automaticamente volta para adição de caminho + onTapDown: (details) { + if (isAdmin && + SvgMapFlags.modoAdicao != "vizinho" && + ((isValid && SvgMapFlags.isLine) || + (SvgMapFlags.modoAdicao != "caminho"))) { + dialogAddPoint(context, details, widget.mapID!).then((point) { + if (point != null) { + newPointList.add(point); + if (SvgMapFlags.modoAdicao == "inicial") { + pontoAnterior = point; + SvgMapFlags.modoAdicao = "caminho"; + } + for (var element in newPointList) { + if (element.uuid == pontoAnterior.uuid && + element is PointParent) { + if (point is PointChild) { + element.children.add(point); + } else { + // O pontoAnterior.neighbor é editado em dialog_add_point utilizando o que estava no prefs, mas ele só é atualizado na newPointList aqui + for (var vizinho in point.neighbor) { + if (vizinho["id"] == element.uuid) { + String direction = + vizinho["direction"]; + String directionAnterior = ""; + switch (direction) { + case "N": + directionAnterior = "S"; + break; + case "S": + directionAnterior = "N"; + break; + case "E": + directionAnterior = "W"; + break; + case "W": + directionAnterior = "E"; + break; + default: + break; + } + element.neighbor.add({ + "id": point.uuid, + "direction": directionAnterior, + }); + } + } + pontoAnterior = + newPointList.last as PointParent; + } + } + } + } + setState(() { + newPointList = newPointList; + }); + }).whenComplete(() { + if (SvgMapFlags.modoAdicao == "filho") { + SvgMapFlags.modoAdicao = "caminho"; + setState(() {}); + } + }); + } + }, + child: Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + child: Stack( + children: [ + svg, + if (isAdmin) + CustomPaint( + painter: + PathPainter(pointList: newPointList), + child: SizedBox( + width: x, + height: y, + ), + ), + if (SvgMapFlags.modoAdicao == "filho") + CustomPaint( + painter: LinePainter( + coordInicialX: pontoAnterior.x, + coordInicialY: pontoAnterior.y, + ), + child: SizedBox( + width: x, + height: y, + ), + ), + if (isAdmin) + ...newPointList + .map( + (pointInList) => PointWidget( + point: pointInList, + side: 5, + idPontoAnterior: pontoAnterior.uuid, + onPressed: () async { + SharedPreferences prefs = + await SharedPreferences + .getInstance(); + if (SvgMapFlags.modoAdicao == + "vizinho") { + if (pointInList is PointParent) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text( + "Adicionar Vizinho?"), + actions: [ + TextButton( + onPressed: () { + SvgMapFlags + .modoAdicao = + "caminho"; + Navigator.pop( + context); + }, + child: const Text( + "Cancelar", + style: TextStyle( + color: Colors + .redAccent), + ), + ), + TextButton( + onPressed: + () async { + String direction; + String + directionAnterior; + if (isValidX) { + if (pontoAnterior + .y < + pointInList + .y) { + direction = + "N"; + directionAnterior = + "S"; + } else { + direction = + "S"; + directionAnterior = + "N"; + } + } else { + if (pontoAnterior + .x < + pointInList + .x) { + direction = + "W"; + directionAnterior = + "E"; + } else { + direction = + "E"; + directionAnterior = + "W"; + } + } + if (isValidX || + isValidY) { + // Se ainda não for vizinho do ponto anterior (o amarelo) + if (pontoAnterior + .neighbor + .any((element) => + element["id"] == + pointInList.uuid) == + false) { + // Adiciona o vizinho no ponto anterior (o amarelo) + pontoAnterior + .neighbor + .add({ + "id": pointInList + .uuid, + "direction": + directionAnterior, + }); + } + // Se ainda não for vizinho no ponto clicado + if (pointInList.neighbor.any((element) => + element[ + "id"] == + pontoAnterior + .uuid) == + false) { + // adiciona o vizinho no ponto clicado + pointInList + .neighbor + .add({ + "id": pontoAnterior + .uuid, + "direction": + direction, + }); + } + try { + await PointRepository() + .editPoint( + pontoAnterior, + ); + await PointRepository() + .editPoint( + pointInList); + for (var element + in newPointList) { + if (element + .uuid == + pontoAnterior + .uuid) { + if (element + is PointParent) { + element + .neighbor + .add({ + "id": + pointInList.uuid, + "direction": + directionAnterior + }); + } + } + } + showMessageSucess( + context: + context, + text: + "Vizinho adicionado", + ); + } on DioError { + pontoAnterior + .neighbor + .removeWhere( + (e) => + e["id"] == + pointInList + .uuid, + ); + pointInList + .neighbor + .removeWhere( + (e) => + e["id"] == + pontoAnterior + .uuid, + ); + showMessageError( + context: + context, + text: + "Erro ao adicionar vizinho", + ); + } + Navigator.pop( + context); + SvgMapFlags + .modoAdicao = + "caminho"; + } + }, + child: const Text( + "Sim"), + ) + ], + ); + }); + } else { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + "O ponto não pode ser objetivo nem obstáculo!", + textAlign: + TextAlign.center, + ), + ), + ); + } + } else { + dialogViewPoint( + context, + pointInList, + newPointList, + ).whenComplete(() async { + setState(() { + pontoAnterior = + pointParentFromJson( + prefs.getString( + "pontoAnterior") ?? + ""); + }); + }); + } + }, + ), + ) + .toList(), + if (isAdmin && SvgMapFlags.isLine) + ...pointValidWidget( + x: x ?? 0, + y: y ?? 0, + width: widget.svgWidth, + height: widget.svgHeight, + isValidX: isValidX, + isValidY: isValidY, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + floatingActionButton: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + (isAdmin && + (SvgMapFlags.modoAdicao == "caminho" || + SvgMapFlags.modoAdicao == "vizinho")) + ? FloatingActionButton( + heroTag: "btnLine", + onPressed: () { + setState( + () { + SvgMapFlags.isLine = !SvgMapFlags.isLine; + }, + ); + }, + child: const Icon( + Icons.line_style, + size: 30, + ), + ) + : Container(), + isAdmin + ? const SizedBox( + height: 20, + ) + : Container(), + (kIsWeb || + Platform.isLinux || + Platform.isMacOS || + Platform.isWindows) + ? FloatingActionButton( + heroTag: "btnAdmin", + backgroundColor: Colors.red[900], + onPressed: () { + setState( + () { + isAdmin = !isAdmin; + // criar scaffold message + ScaffoldMessenger.of(context) + .removeCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + isAdmin + ? 'Modo Admin Ativado' + : 'Modo Admin Desativado', + textAlign: TextAlign.center, + ), + ), + ); + }, + ); + }, + child: const Icon( + Icons.admin_panel_settings, + size: 30, + ), + ) + : Container(), + const SizedBox( + height: 20, + ), + FloatingActionButton( + heroTag: "btnScale", + onPressed: () { + setState( + () { + if (flagScale) { + scaleFactor *= 2; + flagScale = false; + } else { + scaleFactor *= 1 / 2; + flagScale = true; + } + }, + ); + }, + child: Icon( + flagScale ? Icons.zoom_in_sharp : Icons.zoom_out_map, + size: 30, + ), + ), + const SizedBox( + height: 20, + ), + FloatingActionButton( + heroTag: "btnCentralizar", + onPressed: () { + centralizar(true); + }, + child: const Icon( + Icons.center_focus_strong, + size: 30, + ), + ), + ], + ), + ); + } + return childVar; + }, + ); + } +} diff --git a/lib/features/svg_map/svg_map_flags.dart b/lib/features/svg_map/svg_map_flags.dart new file mode 100644 index 0000000..5b3a446 --- /dev/null +++ b/lib/features/svg_map/svg_map_flags.dart @@ -0,0 +1,4 @@ +class SvgMapFlags { + static bool isLine = true; + static String modoAdicao = "caminho"; +} diff --git a/lib/features/user/user.model.dart b/lib/features/user/user.model.dart new file mode 100644 index 0000000..ab99fe0 --- /dev/null +++ b/lib/features/user/user.model.dart @@ -0,0 +1,34 @@ +class UserModel { + late String name; + late String email; + late String password; + late String permission; + late String id; + late String token; + + UserModel( + {this.email = "", + this.password = "", + this.name = "", + this.permission = "NORMAL", + this.id = "", + this.token = ""}); + + UserModel.fromJson(Map json) { + name = json["name"].toString(); + email = json["email"].toString(); + password = json["password"].toString(); + permission = json["permission"].toString(); + id = json["id"] ?? ''; + token = json["token"] ?? ''; + } + + Map toJson() { + Map json = {}; + json["name"] = name; + json["email"] = email; + json["password"] = password; + json["role"] = permission; + return json; + } +} diff --git a/lib/features/user/user.repository.dart b/lib/features/user/user.repository.dart new file mode 100644 index 0000000..31056f3 --- /dev/null +++ b/lib/features/user/user.repository.dart @@ -0,0 +1,151 @@ +import 'dart:convert'; +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:mvp_proex/app/app.repository.dart'; +import 'package:mvp_proex/features/user/user.model.dart'; + +class UserRepository extends AppRepository { + Future login({required UserModel userModel}) async { + try { + if (kDebugMode) { + print("Login..."); + } + return await dio + .post( + AppRepository.path + AppRepository.queryLogin, + data: userModel.toJson(), + ) + .then( + (res) { + return res.data["token"] ?? ""; + }, + ); + } on DioError catch (e) { + if (e.response != null) { + return "Erro " + + e.response!.statusCode.toString() + + ": " + + e.response!.statusMessage!; + } + return jsonEncode({ + "code": 5000, + "message": "Erro Interno", + "details": e.response.toString(), + }); + } + } + + Future getUser(String token) async { + try { + if (kDebugMode) { + print("Get user..."); + } + return await dio + .get( + AppRepository.path + AppRepository.queryUser, + options: Options( + headers: {"Authorization": "Bearer $token"}, + responseType: ResponseType.json, + ), + ) + .then( + (Response res) { + return res.data; + }, + ); + } on DioError catch (e) { + if (e.response != null) { + return { + "code": e.response!.statusCode, + "message": e.response!.statusMessage + }; + } + return {"code": 5000, "message": "Internal Error. No response."}; + } + } + + Future getAllUsers() async { + try { + return await dio + .post( + AppRepository.path + AppRepository.queryAllUsers, + ) + .then( + (value) { + if (value.statusCode == 200) { + return jsonEncode(value.data); + } + return jsonEncode(value.data); + }, + ); + } on DioError catch (e) { + if (e.response != null) { + if (e.response!.statusCode == 404) { + return jsonEncode({"code": 404, "message": "API Offline"}); + } + + return jsonEncode(e.response); + } + return jsonEncode(e.response); + } catch (e) { + return jsonEncode({"code": 5000, "message": "Error Interno"}); + } + } + + Future editAccount(UserModel userModel) async { + try { + return await dio + .post( + AppRepository.path + AppRepository.queryLogin, + data: userModel.toJson(), + ) + .then( + (value) { + if (value.statusCode == 200) { + return jsonEncode(value.data); + } + return jsonEncode(value.data); + }, + ); + } on DioError catch (e) { + if (e.response != null) { + if (e.response!.statusCode == 404) { + return jsonEncode({"code": 404, "message": "API Offline"}); + } + + return jsonEncode(e.response); + } + return jsonEncode(e.response); + } catch (e) { + return jsonEncode({"code": 5000, "message": "Error Interno"}); + } + } + + Future deleteAccount(String uid) async { + try { + return await dio + .delete( + AppRepository.path + AppRepository.queryUser + "/" + uid, + ) + .then( + (value) { + if (value.statusCode == 200) { + return jsonEncode(value.data); + } + return jsonEncode(value.data); + }, + ); + } on DioError catch (e) { + if (e.response != null) { + if (e.response!.statusCode == 404) { + return jsonEncode({"code": 404, "message": "API Offline"}); + } + + return jsonEncode(e.response); + } + return jsonEncode(e.response); + } catch (e) { + return jsonEncode({"code": 5000, "message": "Error Interno"}); + } + } +} diff --git a/lib/features/widgets/dialog_add_point.widget.dart b/lib/features/widgets/dialog_add_point.widget.dart new file mode 100644 index 0000000..6bbff49 --- /dev/null +++ b/lib/features/widgets/dialog_add_point.widget.dart @@ -0,0 +1,241 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:mvp_proex/app/app.constant.dart'; +import 'dart:convert'; +import 'package:mvp_proex/features/point/point.repository.dart'; +import 'package:mvp_proex/features/point/point_child.model.dart'; +import 'package:mvp_proex/features/point/point_parent.model.dart'; +import 'package:mvp_proex/features/svg_map/svg_map_flags.dart'; +import 'package:mvp_proex/features/widgets/shared/snackbar.message.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; + +Future dialogAddPoint(BuildContext context, var details, String mapId) { + return showDialog( + context: context, + builder: (context) { + String tipo = SvgMapFlags.modoAdicao; + String name = "Ponto"; + String descricao = "Descrição"; + return AlertDialog( + title: tipo == "filho" + ? const Text("Adicionar ponto filho") + : const Text("Adicionar ponto pai"), + content: Column( + children: [ + Text( + "X = ${details.localPosition.dx}\nY = ${details.localPosition.dy}"), + DropdownButtonFormField( + value: tipo, + items: SvgMapFlags.modoAdicao == "caminho" + ? const [ + DropdownMenuItem( + child: Text("Caminho"), + value: "caminho", + ), + DropdownMenuItem( + child: Text("Passagem"), + value: "passagem", + ), + ] + : SvgMapFlags.modoAdicao == "filho" + ? const [ + DropdownMenuItem( + child: Text("Destino"), + value: "filho", + ), + DropdownMenuItem( + child: Text("Obstáculo"), + value: "obstaculo", + ), + ] + : const [ + DropdownMenuItem( + child: Text("Inicial"), + value: "inicial", + ), + ], + onChanged: (value) { + tipo = value as String; + }, + ), + TextFormField( + maxLengthEnforcement: MaxLengthEnforcement.enforced, + maxLength: 80, + decoration: const InputDecoration( + labelText: "Nome do ponto", + ), + onChanged: (value) { + if (value.isEmpty) { + name = "Ponto"; + } else { + name = value; + } + }, + ), + TextFormField( + maxLengthEnforcement: MaxLengthEnforcement.enforced, + maxLength: 250, + decoration: const InputDecoration( + labelText: "Descrição do ponto", + ), + onChanged: (value) { + if (value.isEmpty) { + descricao = "Descrição"; + } else { + descricao = value; + } + }, + ) + ], + ), + scrollable: true, + actions: [ + // CANCELAR + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text( + "Cancelar", + style: TextStyle(color: Colors.redAccent), + ), + ), + // ADICIONAR + TextButton( + onPressed: () async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String directionAnterior = ""; + PointParent pontoAnterior = PointParent(); + + if (SvgMapFlags.modoAdicao != "inicial") { + //usando esse point model para que seja possível acessar os valores do ponto com o uuid desejado + pontoAnterior = pointParentFromJson( + prefs.getString("pontoAnterior") ?? ""); + } + + // inicialização é feita dentro do if else + // ignore: prefer_typing_uninitialized_variables + var point; + if (SvgMapFlags.modoAdicao == "filho") { + point = PointChild(); + } else { + point = PointParent(); + } + + point.x = details.localPosition.dx; + point.y = details.localPosition.dy; + point.name = name; + point.description = descricao; + point.mapId = mapId; + if (point is PointParent) { + switch (tipo) { + case "passagem": + point.type = TypePoint.passage; + break; + case "inicial": + point.type = TypePoint.entrance; + break; + default: + point.type = TypePoint.common; + break; + } + if (SvgMapFlags.modoAdicao != "inicial") { + // verificar a orientação + double difx = (point.x - pontoAnterior.x); + double dify = (point.y - pontoAnterior.y); + String direction; + if (difx < 0) difx *= -1; + if (dify < 0) dify *= -1; + if (difx < 2) point.x = pontoAnterior.x; + if (dify < 2) point.y = pontoAnterior.y; + + if (point.x == pontoAnterior.x) { + if (pontoAnterior.y < point.y) { + direction = "N"; + directionAnterior = "S"; + } else { + direction = "S"; + directionAnterior = "N"; + } + } else { + if (pontoAnterior.x < point.x) { + direction = "W"; + directionAnterior = "E"; + } else { + direction = "E"; + directionAnterior = "W"; + } + } + point.neighbor.add( + {"id": pontoAnterior.uuid, "direction": direction}, + ); + } + } else if (point is PointChild) { + switch (tipo) { + case "obstaculo": + point.isObstacle = true; + break; + default: + point.isObstacle = false; + break; + } + point.parentId = pontoAnterior.uuid; + } else { + // ignore: avoid_print + print("ERRO de tipo em dialog_point.widget."); + throw ("Erro de tipo"); + } + + PointRepository pRepository = PointRepository(); + + try { + await pRepository + .postPoint( + point is PointParent ? "parent" : "child", point) + .then((value) async { + point.uuid = json.decode(value)["id"]; + + if (SvgMapFlags.modoAdicao != "filho") { + prefs.setString( + "pontoAnterior", + pointModelToJson(point), + ); + if (SvgMapFlags.modoAdicao == "caminho") { + pontoAnterior.neighbor.add( + {"id": point.uuid, "direction": directionAnterior}); + await pRepository.editPoint(pontoAnterior); + } + } else if (SvgMapFlags.modoAdicao == "filho") { + pontoAnterior.children.add(point); + } + + Navigator.pop(context, point); + }); + } on DioError catch (e) { + //TODO: arrumar mensagem de erro + //quando vem uma message ela está vindo como string, e não como mapa, porém é possível que venha como html, e nesse caso não teria como dar json decode nela + try { + showMessageError( + context: context, + text: e.message + + " " + + e.response!.statusMessage! + + "\n" + + e.response!.data, + ); + } catch (f) { + showMessageError( + context: context, + text: e.message + " " + e.response!.statusMessage!, + ); + } + } + }, + child: const Text("Adicionar")), + ], + ); + }, + ); +} diff --git a/lib/features/widgets/dialog_edit_point.dart b/lib/features/widgets/dialog_edit_point.dart deleted file mode 100644 index 80a4c35..0000000 --- a/lib/features/widgets/dialog_edit_point.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:dijkstra/dijkstra.dart'; - -Future dialogEditPoint( - BuildContext context, - var e, - int id, - int prev, - int inicio, - Function centralizar, - var widget, - List> points, - var graph) async { - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text("Ponto ${e["id"]}"), - content: Text("X = ${e["x"]}\nY = ${e["y"]}\nPrev = ${e['vizinhos']}"), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text( - "Cancelar", - style: TextStyle(color: Colors.redAccent), - ), - ), - TextButton( - onPressed: e["id"] == id - ? () { - points.remove(e); - - Navigator.pop(context); - } - : null, - child: const Text( - "Remover", - style: TextStyle(color: Colors.redAccent), - ), - ), - TextButton( - onPressed: () { - prev = e["id"]; - Navigator.pop(context); - }, - child: const Text( - "Usar como anterior", - style: TextStyle(color: Colors.green), - ), - ), - TextButton( - child: const Text( - "Objetivo", - style: TextStyle(color: Colors.deepPurple), - ), - onPressed: () async { - // TODO: Caminho melhor - // fechar pop up - Navigator.pop(context); - - //onde estou - int here = 0; - - points.forEach((element) { - if (element['x'] == widget.person.x && - element['y'] == widget.person.y) { - here = element['id']; - } - }); - - // lista do caminho a ser seguido - List tracker = Dijkstra.findPathFromGraph(graph, here, e["id"]); - - //se removesse o primeiro ponto ele iria pensar que já está nele, - //então iria pular o primeiro - // tracker.removeAt(0); - - print(tracker); - - centralizar(true); - for (var i = 0; i < tracker.length; i++) { - widget.person.setx = points - .firstWhere((element) => element['id'] == tracker[i])['x']; - widget.person.sety = points - .firstWhere((element) => element['id'] == tracker[i])['y']; - inicio = tracker[i]; - centralizar(true); - await Future.delayed(const Duration(seconds: 3)); - } - - // pegando o ponto inicial - // Map pointInit = points - // .where((element) => - // element["x"] == widget.person.x && - // element["y"] == widget.person.y) - // .first; - - // // traçar o caminho, caso o caminho seja de volta - // while (pointInit["id"] != e["id"]) { - // tracker.add(e); - // e = points.where((element) => element["id"] == e["prev"]).first; - // } - - // tracker = tracker.reversed.toList(); - - // print(tracker); - - // for (var i = 0; i < tracker.length; i++) { - // widget.person.setx = tracker[i]["x"]; - // widget.person.sety = tracker[i]["y"]; - // inicio = tracker[i]["id"]; - // await Future.delayed(const Duration(seconds: 2)); - // centralizar(true); - // } - }, - ), - ], - ); - }, - ); -} diff --git a/lib/features/widgets/dialog_editar_point.dart b/lib/features/widgets/dialog_editar_point.dart new file mode 100644 index 0000000..f3861fe --- /dev/null +++ b/lib/features/widgets/dialog_editar_point.dart @@ -0,0 +1,111 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:mvp_proex/app/app.constant.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:mvp_proex/features/point/point.repository.dart'; +import 'package:mvp_proex/features/widgets/shared/snackbar.message.dart'; + +Future dialogEditar( + BuildContext context, + PointModel point, +) { + return showDialog( + context: context, + builder: (context) { + TypePoint type = TypePoint.common; + String name = point.name; + String descricao = point.description; + return AlertDialog( + title: const Text("Editar ponto"), + content: Column( + children: [ + Text("X = ${point.x}\nY = ${point.y}"), + DropdownButtonFormField( + value: type, + items: const [ + DropdownMenuItem( + child: Text("Intermediário"), + value: TypePoint.passage, + ), + DropdownMenuItem( + child: Text("Caminho"), + value: TypePoint.common, + ), + ], + onChanged: (value) { + if (value != TypePoint.passage) { + name = "Caminho"; + type = TypePoint.common; + } else { + name = "Passagem"; + type = TypePoint.passage; + } + }, + ), + TextFormField( + maxLengthEnforcement: MaxLengthEnforcement.enforced, + maxLength: 80, + initialValue: name, + decoration: const InputDecoration( + labelText: "Nome do ponto", + ), + onChanged: (value) { + if (value.isEmpty) { + name = "Ponto"; + } else { + name = value; + } + }, + ), + TextFormField( + initialValue: descricao, + maxLengthEnforcement: MaxLengthEnforcement.enforced, + maxLength: 250, + decoration: const InputDecoration( + labelText: "Descrição do ponto", + ), + onChanged: (value) { + if (value.isEmpty) { + descricao = "Descrição"; + } else { + descricao = value; + } + }, + ) + ], + ), + scrollable: true, + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text( + "Cancelar", + style: TextStyle(color: Colors.redAccent), + ), + ), + TextButton( + onPressed: () async { + point.name = name; + point.description = descricao; + if (kDebugMode) { + print(point.toJson()); + } + try { + await PointRepository().editPoint(point); + Navigator.pop(context); + } on DioError catch (e) { + showMessageError(context: context, text: e.message); + } + }, + child: const Text( + "Salvar", + style: TextStyle(color: Colors.green), + ), + ) + ]); + }); +} diff --git a/lib/features/widgets/dialog_point.widget.dart b/lib/features/widgets/dialog_point.widget.dart deleted file mode 100644 index 38f291c..0000000 --- a/lib/features/widgets/dialog_point.widget.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mvp_proex/app/app.constant.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -Future dialogPointWidget( - BuildContext context, var details, int id, var points, var graph) { - return showDialog( - context: context, - builder: (context) { - Object? type = TypePoint.path.toString(); - String name = "Caminho"; - return AlertDialog( - title: Text("Adicionar ponto $id"), - content: Column( - children: [ - Text( - "X = ${details.localPosition.dx}\nY = ${details.localPosition.dy}"), - DropdownButtonFormField( - value: type, - items: [ - DropdownMenuItem( - child: const Text("Objetivo"), - value: TypePoint.goal.toString(), - ), - DropdownMenuItem( - child: const Text("Caminho"), - value: TypePoint.path.toString(), - ), - ], - onChanged: (value) { - if (value != TypePoint.goal.toString()) { - name = "Caminho"; - } else { - name = "Objetivo"; - } - type = value; - }, - ), - TextFormField( - initialValue: name, - decoration: const InputDecoration( - labelText: "Nome do objetivo", - ), - onChanged: (value) { - if (value.isEmpty) { - name = "Objetivo $id"; - } else { - name = value; - } - }, - ) - ], - ), - scrollable: true, - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text( - "Cancelar", - style: TextStyle(color: Colors.redAccent), - ), - ), - TextButton( - onPressed: () async { - /* Calcular o peso das dinstâncias com base na diferença das coordenadas */ - SharedPreferences prefs = await SharedPreferences.getInstance(); - - int prev = (prefs.getInt('prev') ?? 0); - print(prev); - int peso = ((details.localPosition.dx - points[prev]["x"]) - .abs() + - (details.localPosition.dy - points[prev]["y"]).abs()) - .round(); - Map json = { - "id": id, - "x": details.localPosition.dx, - "y": details.localPosition.dy, - /*Sempre existirá ao menos um vizinho, que é o ponto anterior*/ - "vizinhos": {prev++: peso}, - "type": type, - "name": name - }; - print(prev); - - /*O ponto anterior a este deve conter o novo ponto */ - points[prev - 1]["vizinhos"].putIfAbsent(id, () => peso); - graph[prev - 1] = points[prev - 1]["vizinhos"]; - - graph.putIfAbsent(id, () => json["vizinhos"]); - points.add(json); - - await prefs.setInt('prev', id); - print(points); - print(graph); - - List myList = (prefs.getStringList('tracker') ?? []); - List myOriginaList = - myList.map((i) => int.parse(i)).toList(); - print('Your list ${myOriginaList}'); - - int usuarioPos = (prefs.getInt('pos') ?? 0); - - print('Usuario pos ${usuarioPos}'); - - Navigator.pop(context); - }, - child: const Text("Adicionar")), - ], - ); - }, - ); -} diff --git a/lib/features/widgets/dialog_qrcode.widget.dart b/lib/features/widgets/dialog_qrcode.widget.dart new file mode 100644 index 0000000..b879c38 --- /dev/null +++ b/lib/features/widgets/dialog_qrcode.widget.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:mvp_proex/invoice_service.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; + +Future qrDialog( + BuildContext context, + PointModel point, +) async { + final PdfInvoiceService service = PdfInvoiceService(); + return showDialog( + context: context, + builder: (context) { + Size size = MediaQuery.of(context).size; //Mais responsivo + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + alignment: Alignment.center, + insetPadding: + EdgeInsets.symmetric(horizontal: size.width / 4, vertical: 24), + clipBehavior: Clip.hardEdge, + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + point.name, + style: const TextStyle(fontSize: 20), + ), + QrImage( + data: point.uuid.toString(), + size: 150, + errorCorrectionLevel: QrErrorCorrectLevel.H, + backgroundColor: Colors.white, + gapless: true, + // tentativa de adicionar um logo ao qrcode + //embeddedImage: + // const AssetImage('modulomapas/assets/images/logo.png'), + //embeddedImageStyle: QrEmbeddedImageStyle( + // size: const Size(80, 80), + //), + ), + Text( + point.description, + textAlign: TextAlign.center, + ), + const SizedBox(height: 3), + OverflowBar( + overflowAlignment: OverflowBarAlignment.end, + overflowDirection: VerticalDirection.down, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text("Sair"), + ), + TextButton( + onPressed: () async { + final data = await service.createPDF([point]); + service.savePdfFile("QR_${point.name}", data); + }, + child: const Text("Imprimir"), + ), + ], + ), + ], + ), + ), + ); + }, + ); +} diff --git a/lib/features/widgets/dialog_view_point.dart b/lib/features/widgets/dialog_view_point.dart new file mode 100644 index 0000000..ca34434 --- /dev/null +++ b/lib/features/widgets/dialog_view_point.dart @@ -0,0 +1,196 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:mvp_proex/features/point/point.repository.dart'; +import 'package:mvp_proex/features/point/point_child.model.dart'; +import 'package:mvp_proex/features/point/point_parent.model.dart'; +import 'package:mvp_proex/features/svg_map/svg_map_flags.dart'; +import 'package:mvp_proex/features/widgets/dialog_qrcode.widget.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:mvp_proex/features/widgets/shared/snackbar.message.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dialog_editar_point.dart'; + +// aqui seria interessante usar o state management, para não pedir para apagar o ponto várias vezes - o mesmo para o diálogo de editar + +Future dialogViewPoint( + BuildContext context, + var point, + List points, +) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: SingleChildScrollView( + child: Column(children: [ + Text("Nome do Ponto: ${point.name}", + style: const TextStyle(fontSize: 20)), + SelectableText( + "\nID do Ponto: ${point.uuid}\nX = ${point.x.toStringAsPrecision(6)}\nY = ${point.y.toStringAsPrecision(6)}\nDescrição: ${point.description}"), + ]), + ), + actions: [ + // CANCELAR: + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text( + "Cancelar", + style: TextStyle(color: Colors.redAccent), + ), + ), + // REMOVER PONTO: + TextButton( + onPressed: () async { + // ao remover um ponto, tirar a referência à ele em qualquer ponto que tenha ele como vizinho, e remover os filhos, atualizar esses pontos no banco, e então remover esse ponto do banco + PointRepository pRepository = PointRepository(); + try { + if (point is PointParent) { + // se o ponto a ser removido é Parent, então ele tem vizinhos + + for (Map vizinho in point.neighbor) { + // pra cada vizinho do ponto que desejo remover + + for (PointModel individualPoint in points) { + // busca na lista até achar o vizinho atual + if (individualPoint is PointParent && + individualPoint.uuid == vizinho["id"]) { + individualPoint.neighbor.removeWhere((item) => + item["id"] == + point + .uuid); // então remova a referência do meu ponto nele e atualiza o banco + await pRepository.editPoint(individualPoint); + break; + } + } + } + + for (PointChild individualPoint in point.children) { // pra cada filho que esse ponto tiver, apaga ele no banco + await pRepository.deletePoint( + "child", individualPoint.uuid); + } + await pRepository.deletePoint("parent", point.uuid); + } else if (point is PointChild) { + // se não é Parent então é Child, só precisa remover a referência do pai + for (PointModel individualPoint in points) { + if (individualPoint is PointParent) { + if (individualPoint.uuid == point.parentId) { + individualPoint.children.remove(point); + await pRepository.editPoint(individualPoint); + break; + } + } + } + await pRepository.deletePoint("child", point.uuid); + } else { + if (kDebugMode) { + print("ERRO em dialog_edit_point."); + print( + "Tipo inesperado, esperado PointParent ou PointChild, recebido " + + point.runtimeType.toString()); + } + throw ("Erro de tipo"); + } + + points.remove(point); + Navigator.pop(context); + } on DioError catch (e) { + String textoErro = ""; + if (e.response != null) { + textoErro = e.response!.statusCode.toString(); + textoErro += " - " + e.response!.statusMessage!; + } + try { + showMessageError( + context: context, + text: textoErro + + "\n" + + json.decode(e.response!.data)["message"]); + } catch (f) { + showMessageError(context: context, text: textoErro); + } + } + }, + child: const Text( + "Remover", + style: TextStyle(color: Colors.redAccent), + ), + ), + // EDITAR PONTO: + TextButton( + onPressed: () { + dialogEditar(context, point); + }, + child: const Text( + "Editar Ponto", + style: TextStyle(color: Colors.redAccent), + ), + ), + TextButton( + onPressed: () { + if (point is PointModel) { + // ignore: avoid_print + print(point.toJson()); + } + }, + child: const Text( + "Debug", + style: TextStyle(color: Colors.blue), + ), + ), + // GERAR QR CODE: + TextButton( + onPressed: () { + qrDialog(context, point); + }, + child: const Text( + "Gerar QRCode", + style: TextStyle(color: Colors.green), + ), + ), + // USAR COMO ANTERIOR: + TextButton( + onPressed: () { + prefs.setString( + "pontoAnterior", + pointModelToJson(point), + ); + + Navigator.pop(context); + }, + child: const Text( + "Usar como anterior", + style: TextStyle(color: Colors.green), + ), + ), + if (point.uuid == + pointParentFromJson(prefs.getString("pontoAnterior") ?? "") + .uuid) ...[ + // ADICIONAR VIZINHO + TextButton( + onPressed: () { + SvgMapFlags.modoAdicao = "vizinho"; + Navigator.pop(context); + }, + child: const Text("Adicionar Vizinho"), + ), + // ADICIONAR FILHO + TextButton( + onPressed: () { + SvgMapFlags.modoAdicao = "filho"; + SvgMapFlags.isLine = false; + Navigator.pop(context); + }, + child: const Text("Adicionar filho"), + ), + ] + ], + ); + }, + ); +} diff --git a/lib/features/widgets/painters.widget.dart b/lib/features/widgets/painters.widget.dart new file mode 100644 index 0000000..d5ed9c4 --- /dev/null +++ b/lib/features/widgets/painters.widget.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:mvp_proex/features/point/point_parent.model.dart'; + +class LinePainter extends CustomPainter { + double coordInicialX = 0; + double coordInicialY = 0; + LinePainter({ + required this.coordInicialX, + required this.coordInicialY, + }); + + @override + void paint(Canvas canvas, size) { + Paint paint = Paint(); + paint.color = Colors.green; + paint.strokeWidth = 2; + + canvas.drawLine( + Offset(coordInicialX, coordInicialY), + Offset(size.width, size.height), + paint, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} + +class PathPainter extends CustomPainter { + List pointList; + PathPainter({ + required this.pointList, + }); + + double xVizinho = 0, yVizinho = 0; + + @override + void paint(Canvas canvas, size) { + Paint paintParent = Paint(); + paintParent.color = Colors.lightBlue; + paintParent.strokeWidth = 2; + + Paint paintChild = Paint(); + paintChild.color = Colors.green; + paintChild.strokeWidth = 2; + + // Desenha os pontos do caminho - exceto do ponto anterior + for (var ponto in pointList) { + if (ponto is PointParent) { + for (var vizinho in ponto.neighbor) { + if (vizinho["direction"] == "E" || vizinho["direction"] == "N") { + for (PointModel vizinhoPonto in pointList) { + if (vizinhoPonto.uuid == vizinho["id"]) { + xVizinho = vizinhoPonto.x; + yVizinho = vizinhoPonto.y; + break; + } + } + canvas.drawLine( + Offset(ponto.x, ponto.y), + Offset(xVizinho, yVizinho), + paintParent, + ); + } + } + if (ponto.children.isNotEmpty) { + for (var filho in ponto.children) { + canvas.drawLine( + Offset(ponto.x, ponto.y), + Offset(filho.x, filho.y), + paintChild, + ); + } + } + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/features/widgets/person.widget.dart b/lib/features/widgets/person.widget.dart deleted file mode 100644 index 309b4e5..0000000 --- a/lib/features/widgets/person.widget.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mvp_proex/features/model/person.model.dart'; - -class PersonWidget extends StatelessWidget { - final PersonModel person; - final double side; - const PersonWidget({Key? key, this.side = 10, required this.person}) - : super(key: key); - - @override - Widget build(BuildContext context) { - // offset to center, side is 10 - - return AnimatedPositioned( - duration: const Duration(seconds: 2), - top: person.y - side, - left: person.x - side, - child: CircleAvatar( - backgroundColor: Colors.blue[900], - radius: side, - child: CircleAvatar( - radius: side * .8, - backgroundImage: const AssetImage("assets/images/profile.jpeg"), - ), - ), - ); - } -} diff --git a/lib/features/widgets/point.widget.dart b/lib/features/widgets/point.widget.dart deleted file mode 100644 index af69347..0000000 --- a/lib/features/widgets/point.widget.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:mvp_proex/app/app.constant.dart'; - -class PointWidget extends StatelessWidget { - final Map json; - final double side; - final Function()? onPressed; - const PointWidget( - {Key? key, required this.json, required this.side, this.onPressed}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return Positioned( - top: json["y"] - side / 2, - left: json["x"] - side / 2, - child: InkWell( - onTap: Platform.isLinux || Platform.isMacOS || Platform.isWindows - ? onPressed - : null, - child: Container( - color: json["type"] == TypePoint.path.toString() - ? Colors.red - : Colors.green, - width: side, - height: side, - ), - ), - ); - } -} diff --git a/lib/features/widgets/point_valid.widget.dart b/lib/features/widgets/point_valid.widget.dart index 033760f..35af47c 100644 --- a/lib/features/widgets/point_valid.widget.dart +++ b/lib/features/widgets/point_valid.widget.dart @@ -7,7 +7,6 @@ List pointValidWidget({ required double height, required isValidX, required isValidY, - var lastPoint, }) { return [ Positioned( @@ -17,9 +16,7 @@ List pointValidWidget({ width: width, height: 1, decoration: BoxDecoration( - color: lastPoint == null - ? Colors.green - : isValidY + color: isValidY ? Colors.green : Colors.red, borderRadius: BorderRadius.circular(5), @@ -33,9 +30,7 @@ List pointValidWidget({ width: 1, height: height, decoration: BoxDecoration( - color: lastPoint == null - ? Colors.green - : isValidX + color: isValidX ? Colors.green : Colors.red, borderRadius: BorderRadius.circular(5), diff --git a/lib/features/widgets/shared/bottom_navigation_bar.widget.dart b/lib/features/widgets/shared/bottom_navigation_bar.widget.dart new file mode 100644 index 0000000..7f4783d --- /dev/null +++ b/lib/features/widgets/shared/bottom_navigation_bar.widget.dart @@ -0,0 +1,64 @@ +// A bottom navigation bar que tinha antes mas que não será visível para o administrador mas talvez possa ser usada no módulo do usuário. Foi movida para cá para liberar espaço no svg_map.view + +import 'package:flutter/material.dart'; + +class BottomNavigationBarShared extends StatelessWidget { + const BottomNavigationBarShared({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + margin: const EdgeInsets.all(16), + height: 80, + width: 100, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(25)), + color: Colors.deepOrange, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Icon( + Icons.arrow_upward_outlined, + size: 40, + color: Colors.white, + ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: const [ + Icon( + Icons.social_distance, + color: Colors.white, + ), + Text("2 Km"), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: const [ + Icon( + Icons.timelapse, + color: Colors.white, + ), + Text("1 min"), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: const [ + Icon( + Icons.timer, + color: Colors.white, + ), + Text("10:56"), + ], + ), + ], + ), + ); + } +} diff --git a/lib/features/widgets/shared/button_submit.widget.dart b/lib/features/widgets/shared/button_submit.widget.dart new file mode 100644 index 0000000..f35e302 --- /dev/null +++ b/lib/features/widgets/shared/button_submit.widget.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:mvp_proex/app/app.color.dart'; + +class ButtonSubmitWidget extends StatefulWidget { + final String textButton; + final Function onPressed; + final double d; + final bool inversed; + + const ButtonSubmitWidget({ + Key? key, + required this.textButton, + required this.onPressed, + this.d = 0.65, + this.inversed = false, + }) : super(key: key); + @override + _ButtonSubmitWidgetState createState() => _ButtonSubmitWidgetState(); +} + +class _ButtonSubmitWidgetState extends State { + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + return Container( + width: size.width * widget.d, + height: 60, + margin: const EdgeInsets.symmetric(vertical: 10), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: widget.inversed ? Colors.white : AppColors.primary, + shadowColor: AppColors.primary, + elevation: 10, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + side: const BorderSide( + color: AppColors.primary, + ), + ), + ), + child: Text( + widget.textButton, + style: TextStyle( + fontSize: 20.0, + color: widget.inversed ? AppColors.primary : Colors.white, + ), + ), + onPressed: () { + widget.onPressed(); + }, + ), + ); + } +} diff --git a/lib/features/widgets/shared/form_field.widget.dart b/lib/features/widgets/shared/form_field.widget.dart new file mode 100644 index 0000000..c19907b --- /dev/null +++ b/lib/features/widgets/shared/form_field.widget.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:mvp_proex/app/app.color.dart'; + +class FormFieldWidget extends StatefulWidget { + final String title; + final String description; + final Function validator; + final Function onChanged; + final TextInputType keyboardType; + final TextEditingController controller; + final bool obscure; + final int? maxLines; + final AutovalidateMode auto; + final Widget icon; + + const FormFieldWidget({ + Key? key, + required this.title, + required this.description, + required this.validator, + required this.onChanged, + required this.keyboardType, + this.obscure = false, + this.maxLines = 1, + required this.icon, + required this.controller, + this.auto = AutovalidateMode.onUserInteraction, + }) : super(key: key); + @override + _FormFieldWidgetState createState() => _FormFieldWidgetState(); +} + +class _FormFieldWidgetState extends State { + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.title, + style: const TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.bold, + fontFamily: 'Manrope', + ), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 5, + ), + TextFormField( + style: const TextStyle(color: Colors.white), + obscureText: widget.obscure, + controller: widget.controller, + onChanged: (String value) { + widget.onChanged(value); + }, + maxLines: widget.maxLines, + keyboardType: widget.keyboardType, + validator: (value) { + return widget.validator(value); + }, + autovalidateMode: widget.auto, + decoration: InputDecoration( + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.primary, + ), + borderRadius: BorderRadius.all(Radius.circular(20.0))), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.primary, + ), + borderRadius: BorderRadius.all(Radius.circular(20.0))), + border: const OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.primary, + ), + borderRadius: BorderRadius.all(Radius.circular(20.0))), + hintText: widget.description, + hintStyle: const TextStyle( + color: AppColors.primary, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + suffixIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: widget.icon, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/widgets/shared/snackbar.message.dart b/lib/features/widgets/shared/snackbar.message.dart new file mode 100644 index 0000000..50451aa --- /dev/null +++ b/lib/features/widgets/shared/snackbar.message.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +void showMessageError({required BuildContext context, required String text}) { + final snackBar = SnackBar( + content: Text( + text, + textAlign: TextAlign.center, + ), + backgroundColor: Colors.red, + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); +} + +void showMessageSucess({required BuildContext context, required String text}) { + final snackBar = SnackBar( + content: Text( + text, + textAlign: TextAlign.center, + ), + backgroundColor: Colors.green, + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); +} diff --git a/lib/features/widgets/svg_map.widget.dart b/lib/features/widgets/svg_map.widget.dart deleted file mode 100644 index 0685696..0000000 --- a/lib/features/widgets/svg_map.widget.dart +++ /dev/null @@ -1,446 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'dart:math' as math; -import 'package:flutter_svg/svg.dart'; -import 'package:mvp_proex/app/app.constant.dart'; -import 'package:mvp_proex/features/model/person.model.dart'; -import 'package:mvp_proex/features/widgets/custom_appbar.widget.dart'; -import 'package:mvp_proex/features/widgets/dialog_edit_point.dart'; -import 'package:mvp_proex/features/widgets/dialog_point.widget.dart'; -import 'package:mvp_proex/features/widgets/person.widget.dart'; -import 'package:mvp_proex/features/widgets/point.widget.dart'; -import 'package:mvp_proex/features/widgets/point_valid.widget.dart'; - -class SVGMap extends StatefulWidget { - /// Define o caminho do asset: - /// - /// ```dart - /// SVGMap( - /// svgPath: "assets/maps/reitoria/mapaTeste.svg", - /// ... - /// ), - /// ``` - final String svgPath; - - /// Define a largura do SVG, em pixel - /// - /// ```dart - /// SVGMap( - /// ... - /// svgWidth: 800, - /// ... - /// ), - /// ``` - final double svgWidth; - - /// Define a altura do SVG, em pixel - /// - /// ```dart - /// SVGMap( - /// ... - /// svgHeight: 600, - /// ... - /// ), - /// ``` - final double svgHeight; - - /// Define a scala inicial do SVG, em pixel - /// Por padrão ele é 1.0 - /// - /// ```dart - /// SVGMap( - /// ... - /// svgScale: 1.1, - /// ... - /// ), - /// ``` - final double svgScale; - - /// Define a origem o personagem no SVG - /// Por padrão ele é 0 - /// - /// ```dart - /// SVGMap( - /// ... - /// person: person, - /// ... - /// ), - /// ``` - final PersonModel person; - - const SVGMap({ - Key? key, - required this.svgPath, - required this.svgWidth, - required this.svgHeight, - this.svgScale = 1, - required this.person, - }) : super(key: key); - - @override - State createState() => _SVGMapState(); -} - -class _SVGMapState extends State { - bool isAdmin = false; - bool isLine = true; - - double? top, x; - double? left, y; - - late double scaleFactor; - - bool flag = true; - bool flagDuration = false; - - late final Widget svg; - - late double objetivoX; - late double objetivoY; - - int prev = 0; - int id = 0; - int inicio = 0; - List> points = []; - Map graph = {}; - - Future centralizar(bool flag) async { - setState(() { - flagDuration = flag; - top = ((widget.person.y - MediaQuery.of(context).size.height / 2) + - 2 * AppBar().preferredSize.height) * - -1; - left = (widget.person.x - MediaQuery.of(context).size.width / 2) * -1; - }); - } - - @override - void initState() { - scaleFactor = widget.svgScale; - svg = SvgPicture.asset( - widget.svgPath, - color: Colors.white, - fit: BoxFit.none, - ); - // ignore: unused_local_variable - Map json = { - "id": id++, - "x": widget.person.x, - "y": widget.person.y, - "vizinhos": {}, - "type": TypePoint.goal.toString(), - "name": "Entrada Reitoria" - }; - - graph[0] = {}; - points.add(json); - super.initState(); - } - - @override - Widget build(BuildContext context) { - bool isValidX = (points.last["x"] > ((x ?? 1) - 1) && - points.last["x"] < ((x ?? 0) + 1)); - - bool isValidY = (points.last["y"] > ((y ?? 1)) - 1 && - points.last["y"] < ((y ?? 0) + 1)); - - bool isValid = isValidX || isValidY; - - if (left == null && top == null) { - top = ((widget.person.y - MediaQuery.of(context).size.height / 2) + - AppBar().preferredSize.height) * - -1; - left = (widget.person.x - MediaQuery.of(context).size.width / 2) * -1; - } - return Scaffold( - appBar: CustomAppBar( - height: 100, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Icon( - Icons.home_work, - size: 30, - color: Colors.white, - ), - SizedBox( - width: 20, - ), - Text( - "Entrada Reitoria", - style: TextStyle( - fontSize: 18, - color: Colors.white, - ), - ), - ], - ), - ), - body: Transform.rotate( - angle: math.pi / 0.5, - child: Transform.scale( - scale: scaleFactor, - child: Stack( - children: [ - AnimatedPositioned( - duration: flagDuration - ? const Duration(milliseconds: 500) - : const Duration(milliseconds: 0), - top: top, - left: left, - child: MouseRegion( - onHover: (event) { - if (isAdmin) { - setState(() { - x = event.localPosition.dx; - y = event.localPosition.dy; - }); - } - }, - child: GestureDetector( - onDoubleTap: () { - setState( - () { - if (flag) { - scaleFactor *= 2; - flag = false; - } else { - scaleFactor *= 1 / 2; - flag = true; - } - }, - ); - }, - onPanUpdate: (details) { - setState( - () { - flagDuration = false; - top = top! + details.delta.dy; - left = left! + details.delta.dx; - }, - ); - }, - onLongPressEnd: (details) { - setState( - () { - objetivoX = details.localPosition.dx; - objetivoY = details.localPosition.dy; - - widget.person.setx = objetivoX; - widget.person.sety = objetivoY; - }, - ); - }, - onSecondaryTapDown: (details) { - if (isAdmin && isValid) { - dialogPointWidget(context, details, id, points, graph) - .whenComplete( - () => setState( - () { - id++; - prev++; - }, - ), - ); - } - }, - child: Container( - margin: EdgeInsets.zero, - padding: EdgeInsets.zero, - child: Stack( - children: [ - svg, - if (isAdmin) - ...points - .map( - (e) => PointWidget( - json: e, - side: 5, - onPressed: () { - if (isAdmin) { - //somente desktop - - dialogEditPoint( - context, - e, - id, - prev, - inicio, - centralizar, - widget, - points, - graph); - } - }, - ), - ) - .toList(), - PersonWidget( - person: widget.person, - ), - if (isAdmin && isLine) - ...pointValidWidget( - x: x ?? 0, - y: y ?? 0, - width: widget.svgWidth, - height: widget.svgHeight, - lastPoint: points.last, - isValidX: isValidX, - isValidY: isValidY, - ), - ], - ), - ), - ), - ), - ), - ], - ), - ), - ), - floatingActionButton: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - isAdmin - ? FloatingActionButton( - onPressed: () { - setState( - () { - isLine = !isLine; - }, - ); - }, - child: const Icon( - Icons.line_style, - size: 30, - ), - ) - : Container(), - isAdmin - ? const SizedBox( - height: 20, - ) - : Container(), - (Platform.isLinux || Platform.isMacOS || Platform.isWindows) - ? FloatingActionButton( - backgroundColor: Colors.red[900], - onPressed: () { - setState( - () { - isAdmin = !isAdmin; - // criar scaffold message - ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - isAdmin - ? 'Modo Admin Ativado' - : 'Modo Admin Desativado', - textAlign: TextAlign.center, - ), - ), - ); - }, - ); - }, - child: const Icon( - Icons.admin_panel_settings, - size: 30, - ), - ) - : Container(), - const SizedBox( - height: 20, - ), - FloatingActionButton( - onPressed: () { - setState( - () { - if (flag) { - scaleFactor *= 2; - flag = false; - } else { - scaleFactor *= 1 / 2; - flag = true; - } - }, - ); - }, - child: Icon( - flag ? Icons.zoom_in_sharp : Icons.zoom_out_map, - size: 30, - ), - ), - const SizedBox( - height: 20, - ), - FloatingActionButton( - onPressed: () { - centralizar(true); - }, - child: const Icon( - Icons.center_focus_strong, - size: 30, - ), - ), - ], - ), - bottomNavigationBar: Container( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 20, - ), - margin: const EdgeInsets.all(16), - height: 80, - width: 100, - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(25)), - color: Colors.deepOrange, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Icon( - Icons.arrow_upward_outlined, - size: 40, - color: Colors.white, - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: const [ - Icon( - Icons.social_distance, - color: Colors.white, - ), - Text("2 Km"), - ], - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: const [ - Icon( - Icons.timelapse, - color: Colors.white, - ), - Text("1 min"), - ], - ), - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: const [ - Icon( - Icons.timer, - color: Colors.white, - ), - Text("10:56"), - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/features/widgets/top_navigation.widget.dart b/lib/features/widgets/top_navigation.widget.dart deleted file mode 100644 index fadc55a..0000000 --- a/lib/features/widgets/top_navigation.widget.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; - -class TopNavigationWidget extends StatelessWidget { - const TopNavigationWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 20, - ), - margin: const EdgeInsets.all(16), - height: 80, - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(25)), - color: Colors.deepOrange, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [Text("1"), Text("2")], - ), - ); - } -} diff --git a/lib/invoice_service.dart b/lib/invoice_service.dart new file mode 100644 index 0000000..64b77d9 --- /dev/null +++ b/lib/invoice_service.dart @@ -0,0 +1,103 @@ +import 'dart:typed_data'; +import 'dart:io'; +import 'package:mvp_proex/features/point/point.model.dart'; +import 'package:open_document/open_document.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:pdf/pdf.dart'; +import 'package:pdf/widgets.dart' as pw; + +class CustomRow { + final String name; + final String descricao; + final String id; + CustomRow(this.name, this.descricao, this.id); +} + +class PdfInvoiceService { + Future createPDF(List points) { + final pdf = pw.Document(); + final image = pw.MemoryImage( + File('assets/images/logo.png').readAsBytesSync(), + ); + for (PointModel point in points) { + pdf.addPage(pw.Page( + margin: const pw.EdgeInsets.all(30), + //mainAxisAlignment: pw.MainAxisAlignment.center, + //crossAxisAlignment: pw.CrossAxisAlignment.center, + pageFormat: PdfPageFormat.a4, + orientation: pw.PageOrientation.portrait, + build: (pw.Context context) { + return pw.Container( + height: 370 * 2, + width: 300 * 2, + decoration: pw.BoxDecoration( + border: pw.Border.all(style: pw.BorderStyle.solid)), + padding: const pw.EdgeInsets.all(40), + child: pw.Column( + mainAxisSize: pw.MainAxisSize.min, + mainAxisAlignment: pw.MainAxisAlignment.start, + children: [ + pw.Text("MarleyApp", + style: pw.TextStyle( + fontWeight: pw.FontWeight.bold, fontSize: 18)), + pw.Image( + image, + height: 70, + width: 70, + ), + pw.Text( + point.name, + style: pw.TextStyle( + decoration: pw.TextDecoration.underline, + decorationStyle: pw.TextDecorationStyle.solid, + fontWeight: pw.FontWeight.bold, + fontSize: 18), + ), + pw.SizedBox(height: 10), + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.center, + children: [ + for (int cont = 0; cont < 4; cont++) + pw.Padding( + padding: const pw.EdgeInsets.all(8.0), + child: pw.Column(children: [ + for (int cont = 0; cont < 4; cont++) + pw.Padding( + padding: const pw.EdgeInsets.all(8.0), + child: pw.BarcodeWidget( + padding: const pw.EdgeInsets.only(), + data: point.uuid.toString(), + barcode: pw.Barcode.qrCode( + typeNumber: 5, + errorCorrectLevel: pw + .BarcodeQRCorrectionLevel.high), + width: 90, + height: 90, + ), + ), + pw.SizedBox(height: 10), + ])) + ]), + pw.Text( + point.description, + style: const pw.TextStyle( + fontSize: 15, + ), + textAlign: pw.TextAlign.center, + ), + ]), + ); + })); + } + //} + return pdf.save(); + } + + Future savePdfFile(String fileName, Uint8List byteList) async { + final output = await getTemporaryDirectory(); + var filePath = "${output.path}/$fileName.pdf"; + final file = File(filePath); + await file.writeAsBytes(byteList); + await OpenDocument.openDocument(filePath: filePath); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..f6f23bf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..f16b4c3 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/pubspec.lock b/pubspec.lock index 2b40d74..d5f13a8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,13 +1,27 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.1" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" + barcode: + dependency: transitive + description: + name: barcode + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" boolean_selector: dependency: transitive description: @@ -21,7 +35,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -35,7 +49,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -43,6 +57,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -50,20 +71,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" - dijkstra: + dio: dependency: "direct main" description: - name: dijkstra + name: dio url: "https://pub.dartlang.org" source: hosted - version: "0.0.2" + version: "4.0.6" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -97,6 +118,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.14.0" + flutter_slidable: + dependency: transitive + description: + name: flutter_slidable + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2" flutter_svg: dependency: "direct main" description: @@ -114,41 +142,6 @@ packages: description: flutter source: sdk version: "0.0.0" - geolocator: - dependency: "direct main" - description: - name: geolocator - url: "https://pub.dartlang.org" - source: hosted - version: "7.7.1" - geolocator_android: - dependency: transitive - description: - name: geolocator_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - geolocator_apple: - dependency: transitive - description: - name: geolocator_apple - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.2" - geolocator_platform_interface: - dependency: transitive - description: - name: geolocator_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.6" - geolocator_web: - dependency: transitive - description: - name: geolocator_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" http: dependency: transitive description: @@ -163,6 +156,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" intl: dependency: transitive description: @@ -177,15 +177,8 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" - latlng: - dependency: "direct main" - description: - name: latlng - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.0" latlong2: - dependency: "direct main" + dependency: transitive description: name: latlong2 url: "https://pub.dartlang.org" @@ -211,21 +204,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mgrs_dart: dependency: transitive description: @@ -233,13 +226,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + open_document: + dependency: "direct main" + description: + name: open_document + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -254,6 +268,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.19" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" path_provider_linux: dependency: transitive description: @@ -261,6 +296,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.5" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: @@ -275,6 +317,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.5" + pdf: + dependency: "direct main" + description: + name: pdf + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.4" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "10.0.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "10.0.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.4" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" petitparser: dependency: transitive description: @@ -317,6 +401,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.4" + qr: + dependency: transitive + description: + name: qr + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" quiver: dependency: transitive description: @@ -324,6 +429,55 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1+1" + rx_notifier: + dependency: "direct main" + description: + name: rx_notifier + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + share_plus: + dependency: transitive + description: + name: share_plus + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + share_plus_linux: + dependency: transitive + description: + name: share_plus_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + share_plus_macos: + dependency: transitive + description: + name: share_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + share_plus_web: + dependency: transitive + description: + name: share_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + share_plus_windows: + dependency: transitive + description: + name: share_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" shared_preferences: dependency: "direct main" description: @@ -391,7 +545,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -412,21 +566,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" transparent_image: dependency: transitive description: @@ -455,6 +609,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.1" + url_launcher: + dependency: transitive + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.5" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" vector_math: dependency: transitive description: @@ -492,4 +702,4 @@ packages: version: "5.3.1" sdks: dart: ">=2.17.0-0 <3.0.0" - flutter: ">=2.5.0" + flutter: ">=2.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 92ffec9..27f7b9d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,16 +30,28 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + # Cupertino Icons cupertino_icons: ^1.0.2 + # Map and SVG flutter_map: ^0.14.0 - latlng: ^0.1.0 - latlong2: ^0.8.1 flutter_svg: ^0.23.0+1 - geolocator: ^7.7.1 - dijkstra: ^0.0.2 + # QR viewer and saver + qr_flutter: ^4.0.0 + permission_handler: ^10.0.0 + path_provider: ^2.0.11 + # PDF generation + pdf: ^3.0.6 + open_document: ^1.0.3 + # Backend integration + dio: ^4.0.0 + # Data and State management shared_preferences: ^2.0.7 + provider: ^6.0.0 + rx_notifier: ^1.1.0 + # firebase_core: + # cloud_firestore: ^2.2.1 + # firebase_storage: ^8.1.3 + # firebase_auth: ^1.0.0 dev_dependencies: flutter_test: @@ -64,10 +76,8 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/ - - assets/maps/reitoria/mapaTeste.svg + - assets/maps/c1/ - assets/images/profile.jpeg - # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. @@ -93,4 +103,4 @@ flutter: # weight: 700 # # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + # see https://flutter.dev/custom-fonts/#from-packages \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..591f85a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,15 @@ #include "generated_plugin_registrant.h" +#include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + OpenDocumentPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("OpenDocumentPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..3cc7795 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + open_document + permission_handler_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST