Aplicativo Android ONDEVOU

Aplicação nativa para coleta de dados e rastreamento GPS com integração total ao WebView da plataforma. Fonte única da verdade: globais.json sincronizado entre WebView e componentes nativos.

🚀 Produção 📱 Android 🌐 WebView 📍 GPS 🏭 Cilindros 📦 Produtos 🖨️ Bluetooth 📄 Offline First
5
Activities
3
WebViews
2
Services
7
Selectors
30+
Arquivos
99%
Offline

🏗️ Arquitetura do Sistema

Visão Geral da Arquitetura
graph TB subgraph WebViewCamada["🌐 WebView (Site ONDEVOU)"] JS[JavaScript] LS[localStorage] end subgraph NativeCamada["📱 Native Android"] MA[MainActivity] CA[ColetaActivity] LSvc[LocationService] CPA[ColetasPendentesActivity] PM[PrinterManager] end subgraph Armazenamento["💾 Armazenamento"] GJ[globais.json
Fonte Única] DB[SQLite
Coordenadas GPS] FILES[Arquivos .json
Coletas Pendentes] end JS -->|Android.gravarGlobaisJson| MA MA -->|salva| GJ MA -->|carrega| GJ CA -->|lê via getters| MA LSvc -->|carregaDadosFixos
lê via getters| MA LSvc -->|salva| DB CA -->|salva/restaura| LS CA -->|fallback| FILES JS -->|acesso direto| LS PM -->|usa dados empresa| MA

📁 Principais Componentes

ClasseResponsabilidade
MainActivityWebView principal, interface, gerenciamento de estado
ColetaActivityColeta de cilindros/produtos/etiquetas
LocationServiceRastreamento GPS com filtro inteligente
ColetasPendentesActivityGerenciamento de coletas offline
PrinterManagerImpressão Bluetooth térmica (Singleton)
GlobalLoadingDialogLoading global para operações assíncronas

🎯 Filosofia do App

  • 📦 Fonte Única da Verdadeglobais.json centraliza dados do WebView
  • 📍 Rastreamento Inteligente — Filtro por distância/direção/velocidade/tempo
  • 📴 Offline First — Coletas salvam em localStorage + fallback para arquivos
  • 🔄 Keep Alive — START_STICKY + verificação periódica
  • 🔋 Economia de Bateria — Diálogo para desativar otimização
  • 🖨️ Impressão Bluetooth — Suporte a impressoras térmicas 58mm

🔄 Fluxo de Dados — globais.json

📝 Estrutura do globais.json (Fonte Única)

{ "dados_fixo": { "participante": "662691", "razao_social": "EMPRESA EXEMPLO LTDA", "cnpj": "12.345.678/0001-90", "nome_cidade": "São Paulo", "sigla_uf": "SP", "id_membro_logado": "12345", "nome_membro_logado": "João Silva", "email_membro_logado": "joao@email.com", "id_romaneio": "ROM-001", "descricao_romaneio": "Entrega Zona Sul", "monitoramento_ativo": "S" } }
Fluxo de Escrita e Leitura
sequenceDiagram participant WV as WebView (JavaScript) participant MA as MainActivity participant FILE as globais.json participant CA as ColetaActivity participant LS as LocationService participant PM as PrinterManager Note over WV,FILE: ESCRITA (WebView → Native) WV->>MA: Android.gravarGlobaisJson(json) MA->>FILE: FileWriter.write() MA->>MA: carregarGlobaisJson() Note right of MA: globais estática atualizada Note over MA,PM: LEITURA (Native → Componentes) CA->>MA: MainActivity.getIdParticipante() CA->>MA: MainActivity.getIdRomaneio() CA->>MA: MainActivity.getIdMembroLogado() LS->>LS: carregarDadosFixos() LS->>MA: MainActivity.getIdParticipante() LS->>MA: MainActivity.getIdRomaneio() LS->>MA: MainActivity.getIdMembroLogado() LS->>MA: MainActivity.globais (monitoramento_ativo) PM->>MA: MainActivity.getRazaoSocial() PM->>MA: MainActivity.getCidadeUf()

🔑 Getters Estáticos (MainActivity)

// Acessíveis de qualquer lugar do app public static String getIdParticipante() { if (globais != null) { JSONObject dadosFixo = globais.optJSONObject("dados_fixo"); if (dadosFixo != null) { return dadosFixo.optString("participante", "0"); } } return "0"; } public static String getIdRomaneio() { ... } public static String getIdMembroLogado() { ... } public static String getRazaoSocial() { ... } public static String getCidadeUf() { ... } public static String getCnpj() { ... }

📦 Sistema de Coleta de Dados

Fluxo de Coleta (ColetaActivity)
flowchart TD A1["Teclado Zebra SCAN"] -->|dispatchKeyEvent| F["Fila de Leitura"] A2["Botao Buscar Cilindro"] --> C1["CilindroSelector"] A3["Botao Buscar Produto"] --> P1["ProdutoSelector"] A4["Botao QR Code"] --> Q1["QRCodeScannerActivity"] C1 -->|onCilindroSelecionado| F P1 -->|onProdutoSelecionado| F Q1 -->|ArrayListString| F F -->|processarProximoDaFila| P["processarCodigo"] P --> T["getTipoItem"] T -->|cilindro ou produto ou etiqueta| V{"isItemCompativelComFinalidade"} V -->|sim| ADD["Adiciona ItemColeta"] V -->|nao| FEEDBACK["Feedback visual vermelho"] ADD --> ADAPTER["ColetaAdapter.notifyDataSetChanged"] ADAPTER --> FIM1["Aguardando proximo item"] BOT["Botao SALVAR"] --> FORM["mostrarFormularioFinalizacao"] FORM --> SAVE["salvarColeta"] SAVE --> JS["salvarNoLocalStorage"] JS -->|ONLINE| WV["localStorage WebView"] JS -->|OFFLINE| FILE["Arquivo json"] SAVE --> LIMPA["Limpa lista atual"] LIMPA --> PEND["carregarColetasPendentes"]

🏷️ Tipos de Item

TipoFormatoExemplo
Cilindroondevou_cl_{participante}_{id}ondevou_cl_662691_12345
Produtoondevou_pr_{participante}_{id}ondevou_pr_662691_67890
Etiquetaondevou_ea_{participante}_{id}ondevou_ea_662691_ETQ001

⚙️ Metodo carregarDadosFixos()

// ColetaActivity.java private void carregarDadosFixos() { if (MainActivity.globais != null) { JSONObject dadosFixo = MainActivity.globais.optJSONObject("dados_fixo"); if (dadosFixo != null) { participante = dadosFixo.optString("participante", "0"); idMembro = dadosFixo.optString("id_membro_logado", "0"); nomeMembro = Uri.decode(dadosFixo.optString("nome_membro_logado", "Visitante")); idRomaneio = dadosFixo.optString("id_romaneio", ""); } } }

📋 Estrutura da Coleta Pendente (JSON)

{ "header": { "titulo": "Entregar cilindro", "descricao": "Observacao...", "leituras": 5, "data": "2026-05-11 14:30:00", "participante": "662691", "id_membro": "12345", "id_romaneio": "ROM-001", "latitude": "-23.5505", "longitude": "-46.6333", "status": "pendente", "id_finalidade": 3, "nome_finalidade": "Entregar cilindro" }, "data": { "coletar_dados_leituras": { "0": {"leitura": "ondevou_cl_662691_12345", "quantidade": "1"}, "1": {"leitura": "ondevou_pr_662691_67890", "quantidade": "3"} } } }

📍 Sistema de Rastreamento GPS (LocationService)

Fluxo do LocationService
flowchart TD JS["JavaScript: startGpsMonitoringService"] --> DIA["sugerirDesativarEconomiaBateria"] DIA --> START["startForegroundService"] START --> ONCREATE["LocationService.onCreate"] ONCREATE --> LOAD["carregarDadosFixos"] LOAD --> MA["MainActivity.globais"] ONCREATE --> STARTGPS["iniciarMonitoramento"] ONCREATE --> SYNC["iniciarSincronizador"] ONCREATE --> KEEP["iniciarKeepAlive"] LOC["LocationCallback a cada 8s"] --> PROC["processarCoordenada"] PROC --> MA2["MainActivity.globais atualizado"] PROC --> FILTRO{"deveGravarCoordenadaMelhorado"} FILTRO -->|distancia maior igual 15m| GRAVA FILTRO -->|direcao maior igual 25 graus| GRAVA FILTRO -->|velocidade maior igual 8 km/h| GRAVA FILTRO -->|tempo maior igual 2 minutos| GRAVA FILTRO -->|nenhuma condicao| DESCARTAR["DESCARTAR"] GRAVA --> SQL["salvarNoSQLite"] SQL --> ENVIO["tentarEnviarCoordenada"] ENVIO -->|sucesso| SUCESSO["enviado = 1"] ENVIO -->|erro| TENTATIVAS["tentativas++"] SYNC --> PENDENTES["tentarEnviarPendentes a cada 30s"] KEEP --> VERIF["verifica servico a cada 10 min"] VERIF --> REINICIA["reinicia monitoramento se necessario"]

🔍 Filtro Inteligente deveGravarCoordenadaMelhorado

CondicaoLimiarMotivo
Distancia percorridamaior ou igual 15 metrosMovimento significativo
Mudanca de direcaomaior ou igual 25 graus com maior ou igual 5mCurva ou desvio na rota
Mudanca de velocidademaior ou igual 8 km/hAceleracao ou freio brusco
Tempo maximo sem gravarmaior ou igual 2 minutosFallback de seguranca

⚙️ Metodo carregarDadosFixos() no LocationService

private void carregarDadosFixos() { // SEMPRE le do MainActivity fonte unica participante = MainActivity.getIdParticipante(); idMembro = MainActivity.getIdMembroLogado(); idRomaneio = MainActivity.getIdRomaneio(); // Le monitoramento_ativo diretamente do JSON if (MainActivity.globais != null) { JSONObject dadosFixo = MainActivity.globais.optJSONObject("dados_fixo"); if (dadosFixo != null) { String monitoramentoStr = dadosFixo.optString("monitoramento_ativo", "N"); monitoramentoAtivo = "S".equals(monitoramentoStr); } } }

🔋 Dialogo de Economia de Bateria

private void sugerirDesativarEconomiaBateria() { if (jaSugeriuBateria) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) { jaSugeriuBateria = true; } } }

📌 Chamado por: WebAppInterface.startGpsMonitoringService()

🔌 Integrações e Interfaces

🌐 WebAppInterface (JavaScript ↔ Native)

Método JSMétodo NativeDescrição
Android.gravarGlobaisJson(json)gravarGlobaisJson()Salva dados no globais.json
Android.startGpsMonitoringService()startGpsMonitoringService()Inicia rastreamento GPS
Android.stopGpsMonitoringService()stopGpsMonitoringService()Para rastreamento GPS
Android.abrirQrCodeScanner()abrirQrCodeScanner()Abre scanner QR Code
Android.showLoading(msg)showLoading()Mostra loading global
Android.hideLoading()hideLoading()Esconde loading global
Android.viewUrl(url)viewUrl()Visualiza PDF ou baixa APK

🖨️ PrinterManager (Singleton)

// Obter instância PrinterManager pm = PrinterManager.getInstance(context); // Conectar à última impressora usada pm.connectToLastPrinter(callback); // Imprimir recibo térmico pm.printThermalReceipt(receiptText, callback); // Teste de impressão pm.printTest(callback); // Verificar conexão boolean connected = pm.isConnected();

📌 Impressoras suportadas: 58mm térmicas Bluetooth (ESC/POS)

📡 DataWedge (Zebra TC25)

// Detecção automática do dispositivo isZebraDevice = DeviceDetector.isZebraTC25(); // Inicialização dataWedgeScanner = new DataWedgeScanner(this); dataWedgeScanner.setCallback((barcode, barcodeType) -> { ColetaActivity coleta = ColetaActivity.getInstance(); if (coleta != null) { coleta.adicionarBarcodeExterno(barcode); } }); dataWedgeScanner.register();

⚙️ Métodos Principais por Classe

📱 MainActivity

  • carregarGlobaisJson() — Carrega do arquivo para memória
  • getIdParticipante() — Getter estático
  • getIdRomaneio() — Getter estático
  • getIdMembroLogado() — Getter estático
  • getRazaoSocial() — Getter estático
  • getCidadeUf() — Getter estático
  • getInstance() — Retorna instância singleton
  • getWebViewAtivo() — Retorna WebView atual
  • sugerirDesativarEconomiaBateria() — Diálogo bateria

📍 LocationService

  • carregarDadosFixos() — Lê do MainActivity via getters
  • processarCoordenada() — Processa cada localização
  • deveGravarCoordenadaMelhorado() — Filtro inteligente
  • salvarNoSQLite() — Persiste coordenada
  • tentarEnviarCoordenada() — Envia via WebView
  • tentarEnviarPendentes() — Sincroniza pendentes
  • iniciarKeepAlive() — Mantém serviço vivo

📦 ColetaActivity

  • carregarDadosFixos() — Lê dados do MainActivity
  • processarCodigo() — Adiciona item à lista
  • salvarColeta() — Salva em localStorage/arquivo
  • getTipoItem() — Identifica cilindro/produto/etiqueta
  • isItemCompativelComFinalidade() — Valida compatibilidade
  • adicionarBarcodeExterno() — Para Zebra TC25
  • getInstance() — Retorna instância singleton

🖨️ PrinterManager

  • getInstance() — Singleton
  • connectToLastPrinter() — Conecta à última usada
  • connectToPrinter() — Conecta a dispositivo específico
  • printThermalReceipt() — Imprime texto (ESC/POS)
  • printTest() — Teste de impressão
  • isConnected() — Verifica conexão ativa
  • hasSavedPrinter() — Verifica impressora salva

📊 Diagramas Completos

Diagrama de Classes (Principais)
classDiagram class MainActivity { +static JSONObject globais +static String getIdParticipante() +static String getIdRomaneio() +static String getIdMembroLogado() +static String getRazaoSocial() +static MainActivity getInstance() +WebView getWebViewAtivo() +carregarGlobaisJson() +sugerirDesativarEconomiaBateria() -WebView webViewOndevouCom -WebView webViewOndevouComBr } class ColetaActivity { +static ColetaActivity getInstance() +adicionarBarcodeExterno(String) -carregarDadosFixos() -processarCodigo(String) -salvarColeta(String) -getTipoItem(String) -isItemCompativelComFinalidade(String, int) -List~ItemColeta~ itensColetados } class LocationService { -carregarDadosFixos() -processarCoordenada(Location) -deveGravarCoordenadaMelhorado(Location) -salvarNoSQLite(String, Location) -tentarEnviarCoordenada(long, String, Location) -tentarEnviarPendentes() -iniciarKeepAlive() -String participante -String idRomaneio -boolean monitoramentoAtivo } class ItemColeta { -String leitura -int quantidade +String getTipo() +String getLeitura() } class PrinterManager { -static PrinterManager instance +static PrinterManager getInstance() +connectToLastPrinter(PrinterCallback) +printThermalReceipt(String, PrinterCallback) +printTest(PrinterCallback) +boolean isConnected() +boolean hasSavedPrinter() } class WebAppInterface { +gravarGlobaisJson(String) +startGpsMonitoringService() +stopGpsMonitoringService() +viewUrl(String) +abrirQrCodeScanner() +showLoading(String) +hideLoading() } MainActivity --> WebAppInterface : contains MainActivity --> LocationService : starts/uses ColetaActivity --> MainActivity : reads globais via getters LocationService --> MainActivity : reads globais via getters PrinterManager --> MainActivity : reads company data
Diagrama de Sequência — Ativação do GPS
sequenceDiagram participant JS as JavaScript (WebView) participant WAI as WebAppInterface participant MA as MainActivity participant LS as LocationService participant PM as PowerManager JS->>WAI: Android.startGpsMonitoringService() WAI->>MA: sugerirDesativarEconomiaBateria() MA->>PM: isIgnoringBatteryOptimizations() PM-->>MA: false (ainda otimizando) MA->>JS: AlertDialog "Configurar" WAI->>LS: new Intent(LocationService.class) WAI->>LS: startForegroundService() LS->>LS: onCreate() LS->>MA: MainActivity.getIdParticipante() MA-->>LS: "662691" LS->>MA: MainActivity.getIdRomaneio() MA-->>LS: "ROM-001" LS->>MA: MainActivity.globais (monitoramento_ativo) MA-->>LS: "S" LS->>LS: iniciarMonitoramento() LS->>LS: requestLocationUpdates(8000ms) loop A cada 8 segundos LS->>LS: onLocationResult() LS->>MA: carregarDadosFixos() (sempre atualizado) LS->>LS: deveGravarCoordenadaMelhorado() alt Distância ≥ 15m LS->>LS: salvarNoSQLite() LS->>LS: tentarEnviarCoordenada() else Descartar LS->>LS: Log descarte end end
Diagrama de Estado — Coleta Pendente
stateDiagram-v2 [*] --> Coletando: Iniciar ColetaActivity Coletando --> AdicionandoItem: SCAN / Buscar AdicionandoItem --> Coletando: Item adicionado Coletando --> ValidandoFinalizacao: Botão SALVAR ValidandoFinalizacao --> ValidandoFinalizacao: Verifica compatibilidade ValidandoFinalizacao --> Salvando: Itens compatíveis ValidandoFinalizacao --> RemovendoIncompativeis: Itens incompatíveis RemovendoIncompativeis --> Salvando: Usuário confirma Salvando --> VerificandoOnline: Salvar JSON VerificandoOnline --> SalvandoOnline: Conexão OK VerificandoOnline --> SalvandoOffline: Modo offline SalvandoOnline --> [*]: Salvo em localStorage SalvandoOffline --> [*]: Salvo em arquivo .json

📝 Changelog

// ====================================================================== // CHANGELOG - ONDEVOU Android App // ====================================================================== 2026-05-11: 📚 v2026.04.12 - Documentação Completa • 📄 Criação da documentação técnica - Arquitetura, fluxos e diagramas • 🎯 Refatoração LocationService - Agora lê direto do MainActivity.globais • 🔄 Fonte única da verdade - globais.json como único armazenamento • 📊 Diagramas Mermaid - Classes, sequência e estado 2026-04-12: 📍 Melhorias no Rastreamento GPS • 📏 Filtro inteligente - 15m distância, 25° direção, 8 km/h velocidade • ⏰ KeepAlive - Verificação a cada 10 minutos • 🔋 Diálogo bateria - Sugere desativar economia de bateria 2026-04-08: 📦 Coleta Offline • 💾 Fallback para arquivos - Quando localStorage indisponível • 🔄 Sincronização automática - Arquivos → localStorage ao voltar online • 🖨️ Impressão Bluetooth - Suporte a ESC/POS // ====================================================================== // 📌 PRÓXIMOS PASSOS PLANEJADOS // ====================================================================== ✅ CONCLUÍDO: Documentação completa do App Android ✅ CONCLUÍDO: Refatoração LocationService com fonte única ⏳ EM ANDAMENTO: Melhorias na sincronização de coletas pendentes 📋 PLANEJADO: Dashboard de acompanhamento de entregas em tempo real