Imported from AGROGEOBRASIL/agrogeo_brasil_app (
AGENTS.md). Install upstream withnpx skills add AGROGEOBRASIL/agrogeo_brasil_app. Copyright stays with the author.
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Visão geral
agrogeo_brasil_app_main é o app oficial da AGROGEO BRASIL, um app Flutter
já publicado em produção (Play Store + App Store) com clientes ativos.
Qualquer mudança deve ser conservadora e aditiva — não quebrar fluxos existentes.
- Idioma do código/UI/commits: português (pt-BR).
- Versão atual: ver
version:nopubspec.yaml(formatox.y.z+build). applicationId/namespaceAndroid:br.com.agrogeo.agrogeo_brasil_app.
Comandos
flutter pub get # instalar dependências
flutter run # rodar em device/emulador conectado
flutter analyze # lint (usa flutter_lints via analysis_options.yaml)
flutter test # rodar todos os testes
flutter test test/<arquivo>_test.dart # rodar um arquivo de teste
flutter test --name "<substring do nome>" # rodar um teste específico
flutter build apk --release # build Android
flutter build appbundle --release # bundle p/ Play Store (assinado por agrogeo_keystore.jks)
flutter build ios --release # build iOS (rodar em macOS)
O diretório de trabalho normalmente é lib/. A raiz do projeto é o pai (..).
Arquitetura
App Flutter "clássico" com backend 100% Firebase. Não há camada de arquitetura formal nem lib de state management.
- State management: apenas
StatefulWidget+setStateeStreamBuilderligado direto nos streams do Firestore/FirebaseAuth. Não usar Provider/ Riverpod/Bloc/GetX — manter o padrão existente ao adicionar telas. - Navegação: imperativa,
Navigator.push(MaterialPageRoute(...)). Sem rotas nomeadas e sem go_router. Existe umnavigatorKeyglobal emmain.dartusado só para deep-link de notificações FCM (_navigateToOS). - Entrada/auth:
main.dart→AuthWrapper(StreamBuilder emauthStateChanges) →PainelPage(logado) ouLoginPage. - Fluxo de telas:
PainelPage→FazendaDepartamentosPage→ módulos (OrdensServicoDepartamentoPage/SituacaoFazendaPage/AcordosPagamentosPage). - Dados do usuário:
SharedPreferencesguardaclienteNomeeclienteDocId; as fazendas são consultadas no Firestore porwhere('cliente', isEqualTo: nome). - Organização de pastas:
lib/screens/(telas),lib/services/(Firebase, FCM, notificações, OS, PDF),lib/models/(cliente.dart,imovel.dart). - Padrão para novo módulo: criar a tela em
lib/screens/(oulib/modules/para subsistemas maiores) e ligar viaNavigator.pusha partir de uma tela existente. Foi assim que "Acordos e Pagamentos" foi adicionado.
Firebase
firebase_core/auth/messaging, cloud_firestore, firebase_storage,
cloud_functions. Coleções principais: fazendas, ordensServico, mensagens.
FCM com background handler em main.dart e NotificationService para notificações
locais. A licença do Syncfusion é registrada em main() antes de qualquer uso.
Dois caminhos coexistem:
flutter_pdfview(view nativa por path de arquivo) emscreens/pdf_viewer_screen.dart.syncfusion_flutter_pdf+syncfusion_flutter_pdfviewer(gera/exibe por bytes) emservices/pdf_service.dart.
Camada nativa (Android)
MethodChannel('agrogeo_brasil_app/downloads') para salvar PDFs em Downloads (MediaStore).
⚠️ BUG CONHECIDO (canal de download morto em produção): existem dois
MainActivity.kt. O que o Manifest carrega —br.com.agrogeo.agrogeo_brasil_app.MainActivity(casa comnamespace/applicationIde o.MainActivitydo AndroidManifest) — está vazio (class MainActivity : FlutterActivity()). Toda a implementação do MethodChannel está no arquivocom.example.agrogeo_brasil_app_main/MainActivity.kt, que não é carregado. Ou seja, o canalagrogeo_brasil_app/downloadsestá efetivamente desligado em produção. Fix planejado como item separado (mover a implementação para o MainActivity ativo). Não fazer junto do módulo GeoPDF.
Módulo GeoPDF (em desenvolvimento — leitor offline estilo Avenza)
Leitor de GeoPDF offline: usuário importa um GeoPDF gerado no QGIS, o app lê o
georreferenciamento embutido, renderiza o mapa e mostra a posição GPS em tempo real
sobre o PDF. Aditivo e isolado em lib/modules/geopdf/ — não altera telas/
serviços/estado existentes.
Decisões aprovadas:
- Renderização:
pdfx(rasteriza a página) +flutter_map(CRS.simple, overlay de imagem +MarkerLayer). - Georreferenciamento: parser ISO32000 (
/VP → /Measure(/GEO) → /GCS, /GPTS, /LPTS, /Bounds) com fallback para sidecar JSON. - Conversão de coordenadas:
proj4dart. GPS WGS84 ↔ georreferência do PDF; display em UTM SIRGAS 2000 (E/N + zona detectada automaticamente) num painel fixo na tela do mapa, além do marcador. - GPS:
geolocator(stream). PermissõesACCESS_FINE/COARSE_LOCATIONjá estão no AndroidManifest; iOS precisa deNSLocationWhenInUseUsageDescriptionno Info.plist. - Ponto de entrada: botão em
FazendaDepartamentosPage.
Formato real do GeoPDF do cliente (validado em test/modelo_geopdf.pdf,
QGIS 3.36.1, projeto UTM 21S SIRGAS2000):
- Georreferência embutida em EPSG:4326 (WGS84 lat/lon) — não UTM.
GPTSem ordem (lat, lon);LPTSnormalizado[0..1]na BBox do viewport.- Dicionários
/VP /Measure /GEOficam em texto plano (não comprimidos) no PDF, então um parser por varredura/regex sobre o conteúdo é confiável para GeoPDFs do QGIS. - UTM 21S SIRGAS2000 = EPSG:31981 (
+proj=utm +zone=21 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs). Zona detectada porfloor((lon+180)/6)+1.
Dependências do módulo: proj4dart, pdfx (nativo), flutter_map, latlong2,
geolocator, xml + share_plus (importar/exportar KML), file_picker. iOS:
NSLocationWhenInUseUsageDescription no Info.plist. Android: permissões de
localização já no Manifest.
Estrutura: lib/modules/geopdf/{models,services,screens,widgets}/. Convenção do
mapa (CrsSimple): LatLng(lat=y, lng=x) em pixels do raster, origem inf-esquerda; GPS
plotado por lat/lon -> GeoAffine.toUV -> (u*w, v*h). Display UTM usa o pipeline de
ponto (CoordinateService.toUtmSirgas2000), independente do mapa.
Etapas (todas implementadas — commits no branch feature/geopdf-modulo):
- ✅ Parser ISO32000 +
coordinate_service, validados contramodelo_geopdf.pdf(test/geopdf_corners_test.dartimprime os cantos em UTM). - ✅ Import de PDF + lista de mapas (
test/geopdf_import_test.dart). - ✅ Tela do mapa (
flutter_map+ raster viapdfx, com cache em disco). - ✅ GPS + marcador em tempo real + painel fixo de coordenada UTM SIRGAS 2000.
- ✅ Medição de área estilo Avenza (mira fixa, Shoelace em UTM, salvar/listar/
renomear/excluir;
area_calculator,measurement_store,test/geopdf_area_test.dart). - ✅ Importar/exportar KML (
kml_service+measurement_importer,test/geopdf_kml_test.dart). Vértices já em WGS84 = datum do KML (sem reprojeção). - ✅ App como handler de PDF/KML do sistema ("Abrir com"). Canal nativo
agrogeo_brasil_app/incoming_files(MainActivity no Android, AppDelegate no iOS) copia ocontent://para o cache e entrega o path;incoming_file_routerroteia: PDF -> importador GeoPDF + abre o mapa; KML -> medições no único mapa, ou dialog de escolha, ou aviso se não houver mapa. Parte pura emtest/geopdf_incoming_test.dart.
Testes do módulo rodam em VM (sem device). Render (pdfx), GPS (geolocator),
share/file-picker e os intents de "Abrir com" exigem teste no aparelho.
Testar "Abrir com" sem WhatsApp (adb / simulação de intent):
O lado nativo lê ACTION_VIEW/ACTION_SEND, copia o content:///file:// para
cache/incoming/ e chama o canal. Para simular abrindo um arquivo já no device:
# 1) empurra um arquivo de teste para o device
adb push test/modelo_geopdf.pdf /sdcard/Download/modelo_geopdf.pdf
# 2) ACTION_VIEW de um PDF (file://) -> deve importar e abrir o mapa
adb shell am start -a android.intent.action.VIEW \
-d "file:///sdcard/Download/modelo_geopdf.pdf" \
-t "application/pdf" \
-n br.com.agrogeo.agrogeo_brasil_app/.MainActivity
# 3) ACTION_VIEW de um KML (precisa ter ao menos 1 GeoPDF importado antes)
adb push medicao.kml /sdcard/Download/medicao.kml
adb shell am start -a android.intent.action.VIEW \
-d "file:///sdcard/Download/medicao.kml" \
-t "application/vnd.google-earth.kml+xml" \
-n br.com.agrogeo.agrogeo_brasil_app/.MainActivity
Para o caminho real do WhatsApp (KML como application/octet-stream), basta trocar
-t por application/octet-stream — o intent-filter restringe pela extensão .kml.
Cold start: feche o app antes do am start; warm: deixe aberto (cai no onNewIntent).