diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e11f3..860defc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.0] - 2025-01-24 + +### Added +- **🎥 Microsoft Clarity Integration**: Complete integration with official Clarity Flutter SDK for behavioral analytics +- **EngineClarityConfig**: Configuration class for Microsoft Clarity with Project ID, User ID, and LogLevel support +- **Masking Widgets**: `EngineMaskWidget` and `EngineUnmaskWidget` for protecting sensitive content +- **Example App**: Complete example demonstrating Clarity integration with masking examples + +### Enhanced +#### Microsoft Clarity Features +- **Session Recordings**: Automatic capture of user sessions for replay +- **Heatmaps**: Visual representation of user interactions +- **User Insights**: Automatic detection of rage taps, dead taps, excessive scrolling +- **Auto-tracking**: Automatic capture of navigation and user interactions +- **Zero Configuration Events**: No manual event logging needed - Clarity captures automatically + +#### Architecture Updates +- **EngineAnalyticsModel**: Added `clarityConfig` property for Clarity configuration +- **EngineAnalytics**: Added `isClarityInitialized` +- **Widget Exports**: Added Clarity masking widgets to widget exports +- **Adapter Pattern**: Adapted Clarity's unique widget-based initialization to Engine Tracking architecture + +### Dependencies +- **clarity_flutter: ^1.0.0**: Official Microsoft Clarity Flutter SDK + +### Technical Details +- **Unique Implementation**: Clarity requires wrapping the app with ClarityWidget instead of static methods +- **LogLevel Support**: Automatic production optimization (LogLevel.None in release builds) +- **User ID Validation**: Base-36 format validation for Clarity user IDs +- **Session Recording**: ~30 minutes for real-time viewing, ~2 hours for complete processing + ## [1.3.0] - 2025-01-23 ### Added diff --git a/README.md b/README.md index 467a63b..1347a6f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,28 @@ # Engine Tracking +
+ Engine Tracking Logo +
+ +## 📋 Sobre o Projeto + [![pub.dev](https://img.shields.io/pub/v/engine_tracking.svg)](https://pub.dev/packages/engine_tracking) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Flutter](https://img.shields.io/badge/Flutter-3.32.0+-blue.svg)](https://flutter.dev/) [![Dart](https://img.shields.io/badge/Dart-3.8.0+-blue.svg)](https://dart.dev/) -Uma biblioteca Flutter completa para **tracking de analytics** e **bug reporting**, oferecendo integração com Firebase Analytics, Firebase Crashlytics, Grafana Faro e Google Cloud Logging. +Uma biblioteca Flutter completa para **tracking de analytics** e **bug reporting**, oferecendo integração com Firebase Analytics, Firebase Crashlytics, Microsoft Clarity, Grafana Faro, Splunk e Google Cloud Logging. + +### 📱 Plataformas Suportadas +- ✅ iOS +- ✅ Android + +--- -## 🚀 Características Principais +## 🚀 Principais Características -- 📊 **Analytics Múltiplo**: Suporte simultâneo para Firebase Analytics, Grafana Faro e Google Cloud Logging -- 🐛 **Bug Tracking Avançado**: Integração com Firebase Crashlytics, Grafana Faro e Google Cloud Logging para monitoramento completo +- 📊 **Analytics Múltiplo**: Firebase Analytics, Microsoft Clarity, Grafana Faro, Splunk e Google Cloud Logging +- 🐛 **Bug Tracking Avançado**: Firebase Crashlytics, Grafana Faro e Google Cloud Logging para monitoramento completo - 🌐 **HTTP Tracking**: Monitoramento automático de requisições HTTPS com métricas detalhadas - 👁️ **View Tracking**: Sistema automático de tracking de telas com `EngineStatelessWidget` e `EngineStatefulWidget` - ⚙️ **Configuração Flexível**: Ative/desative serviços individualmente através de configurações @@ -20,25 +32,12 @@ Uma biblioteca Flutter completa para **tracking de analytics** e **bug reporting - 🧪 **Testável**: Cobertura de testes superior a 95% para componentes testáveis - 🏗️ **Arquitetura Consistente**: Padrão unificado entre Analytics e Bug Tracking - 🎯 **Inicialização Condicional**: Serviços são inicializados apenas se habilitados na configuração -- 📦 **Export Unificado**: Todos os imports podem ser feitos através de `package:engine_tracking/engine_tracking.dart` +- 📦 **Export Unificado**: Todos os imports através de `package:engine_tracking/engine_tracking.dart` - 🚀 **Exemplos Completos**: Apps de demonstração com casos de uso reais (HTTP + View Tracking) -## 📦 Instalação - -Adicione ao seu `pubspec.yaml`: - -```yaml -dependencies: - engine_tracking: ^1.3.0 -``` - -Execute: +--- -```bash -flutter pub get -``` - -## 🏗️ Arquitetura da Solução +## 🗺️ Arquitetura da Solução ### 🆔 Sistema de Session ID (Correlação Automática) @@ -67,8 +66,8 @@ graph TD N --> R["Splunk"] N --> S["Crashlytics"] - T["Correlação de Logs"] --> U["Mesmo session_id"] - U --> V["Jornada Completa do Usuário"] + U["Correlação de Logs"] --> V["Mesmo session_id"] + V --> W["Jornada Completa do Usuário"] ``` ### 📱 Widgets Stateless e Stateful com Tracking Automático @@ -152,16 +151,16 @@ graph TD L --> Q["Grafana Faro"] L --> R["Splunk"] - N --> S["Firebase Crashlytics"] - N --> T["Grafana Faro Bug Tracking"] + N --> T["Firebase Crashlytics"] + N --> U["Grafana Faro Bug Tracking"] - K -->|Sim| U{{"level == error || fatal?"}} - U -->|Sim| V["EngineBugTracking.recordError()"] - U -->|Não| W["Apenas log normal"] + K -->|Sim| V{{"level == error || fatal?"}} + V -->|Sim| W["EngineBugTracking.recordError()"] + V -->|Não| X["Apenas log normal"] - V --> X["Crash Reporting"] - X --> S - X --> T + W --> Y["Crash Reporting"] + Y --> T + Y --> U ``` ### 📊 Sistema de Analytics (EngineAnalytics) @@ -181,24 +180,28 @@ graph TD I --> J["Firebase Analytics Config"] I --> K["Faro Config"] I --> L["Splunk Config"] + I --> M["Google Logging Config"] - D --> M["Adapters"] - E --> M - F --> M - G --> M - H --> M + D --> O["Adapters"] + E --> O + F --> O + G --> O + H --> O - M --> N["EngineFirebaseAnalyticsAdapter"] - M --> O["EngineFaroAnalyticsAdapter"] - M --> P["EngineSplunkAnalyticsAdapter"] + O --> P["EngineFirebaseAnalyticsAdapter"] + O --> Q["EngineFaroAnalyticsAdapter"] + O --> R["EngineSplunkAnalyticsAdapter"] + O --> S["EngineGoogleLoggingAnalyticsAdapter"] - N --> Q["Firebase Analytics SDK"] - O --> R["Grafana Faro SDK"] - P --> S["Splunk SDK"] + P --> T["Firebase Analytics SDK"] + Q --> U["Grafana Faro SDK"] + R --> V["Splunk SDK"] + S --> W["Google Cloud Logging API"] - Q --> T["Google Analytics Dashboard"] - R --> U["Grafana Dashboard"] - S --> V["Splunk Dashboard"] + T --> X["Google Analytics Dashboard"] + U --> Y["Grafana Dashboard"] + V --> Z["Splunk Dashboard"] + W --> AA["Google Cloud Console"] ``` ### 🐛 Sistema de Bug Tracking (EngineBugTracking) @@ -218,68 +221,79 @@ graph TD C --> J["EngineBugTrackingModel"] J --> K["Crashlytics Config"] J --> L["Faro Config"] + J --> M["Google Logging Config"] - D --> M["Adapters"] - E --> M - F --> M - G --> M - H --> M - I --> M + D --> N["Adapters"] + E --> N + F --> N + G --> N + H --> N + I --> N - M --> N["EngineCrashlyticsAdapter"] - M --> O["EngineFaroBugTrackingAdapter"] + N --> O["EngineCrashlyticsAdapter"] + N --> P["EngineFaroBugTrackingAdapter"] + N --> Q["EngineGoogleLoggingBugTrackingAdapter"] - N --> P["Firebase Crashlytics SDK"] - O --> Q["Grafana Faro SDK"] + O --> R["Firebase Crashlytics SDK"] + P --> S["Grafana Faro SDK"] + Q --> T["Google Cloud Logging API"] - P --> R["Firebase Console"] - Q --> S["Grafana Dashboard"] + R --> U["Firebase Console"] + S --> V["Grafana Dashboard"] + T --> W["Google Cloud Console"] - T["Flutter Error Handler"] --> F - U["Platform Error Handler"] --> E + X["Flutter Error Handler"] --> F + Y["Platform Error Handler"] --> E - V["Custom Errors"] --> E - W["Logging Events"] --> D + Z["Custom Errors"] --> E + AA["Logging Events"] --> D ``` -## 🚀 Exemplos de Uso +--- -O pacote inclui exemplos completos demonstrando todas as funcionalidades: +## 📦 Instalação + +Adicione ao seu `pubspec.yaml`: + +```yaml +dependencies: + engine_tracking: ^1.4.0 +``` + +Execute: -### 📱 Exemplo Principal -Demonstra inicialização, tracking de eventos, propriedades de usuário e navegação: ```bash -cd example && flutter run +flutter pub get ``` -### 🌐 Exemplo HTTP Tracking -Novo exemplo demonstrando tracking de requisições HTTPS com APIs públicas: -- **PokéAPI**: Requisições GET para dados de pokémons -- **JSONPlaceholder**: GET de posts/usuários e POST para criação -- **Tracking completo**: Tempo de resposta, códigos de status, tratamento de erros +--- -Para acessar: Execute o app e toque em **"HTTP Tracking"** +## 🚀 Exemplos de Uso -### 👁️ Exemplo View Tracking -Sistema completo de tracking automático de telas com `EngineStatelessWidget` e `EngineStatefulWidget`: -- Tracking automático de visualizações -- Logging de ações do usuário -- Monitoramento de ciclo de vida +### 📱 Exemplos Inclusos -Para acessar: Execute o app e toque em **"View Tracking"** +O pacote inclui exemplos completos demonstrando todas as funcionalidades: -## 📊 Analytics +```bash +cd example && flutter run +``` -O `EngineAnalytics` oferece integração com Firebase Analytics e Grafana Faro para tracking completo de eventos e comportamento do usuário. +- **📱 Exemplo Principal**: Inicialização, tracking de eventos, propriedades de usuário e navegação +- **🌐 Exemplo HTTP Tracking**: Requisições com PokéAPI e JSONPlaceholder +- **👁️ Exemplo View Tracking**: Sistema automático de tracking de telas ### 🎯 Configuração Básica ```dart import 'package:engine_tracking/engine_tracking.dart'; -Future setupAnalytics() async { +Future setupTracking() async { final analyticsModel = EngineAnalyticsModel( firebaseAnalyticsConfig: const EngineFirebaseAnalyticsConfig(enabled: true), + clarityConfig: const EngineClarityConfig( + enabled: true, + projectId: 'seu-projeto-clarity', + ), faroConfig: const EngineFaroConfig( enabled: true, endpoint: 'https://faro-collector.grafana.net/collect', @@ -288,44 +302,49 @@ Future setupAnalytics() async { environment: 'production', apiKey: 'sua-chave-api-faro', ), - googleLoggingConfig: const EngineGoogleLoggingConfig( - enabled: true, - projectId: 'seu-projeto-gcp', - logName: 'engine-tracking', - credentials: { - // Conteúdo completo do arquivo JSON da Service Account - "type": "service_account", - "project_id": "seu-projeto-gcp", - "private_key_id": "...", - "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", - "client_email": "sua-service-account@seu-projeto-gcp.iam.gserviceaccount.com", - "client_id": "...", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "...", - }, - resource: { - 'type': 'global', - 'labels': {'project_id': 'seu-projeto-gcp'}, + googleLoggingConfig: const EngineGoogleLoggingConfig( + enabled: true, + projectId: 'seu-projeto-gcp', + logName: 'engine-tracking', + credentials: { + "type": "service_account", + "project_id": "seu-projeto-gcp", + "private_key_id": "...", + "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", + "client_email": "sua-service-account@seu-projeto-gcp.iam.gserviceaccount.com", + // ... resto das credenciais + }, + resource: { + 'type': 'global', + 'labels': {'project_id': 'seu-projeto-gcp'}, }, ), - splunkConfig: const EngineSplunkConfig(enabled: false, /* outros campos */), - ); + splunkConfig: const EngineSplunkConfig(enabled: false), + ); + + final bugTrackingModel = EngineBugTrackingModel( + crashlyticsConfig: const EngineCrashlyticsConfig(enabled: true), + faroConfig: const EngineFaroConfig( + enabled: true, + endpoint: 'https://faro-collector.grafana.net/collect', + appName: 'MeuApp', + appVersion: '1.0.0', + environment: 'production', + apiKey: 'sua-chave-api-faro', + ), + googleLoggingConfig: const EngineGoogleLoggingConfig(enabled: true, /* configs */), + ); await EngineAnalytics.init(analyticsModel); + await EngineBugTracking.init(bugTrackingModel); } ``` -### 📈 Logging de Eventos +### 📈 Tracking de Eventos ```dart // Evento simples (Session ID incluído automaticamente) await EngineAnalytics.logEvent('button_clicked'); -// Output: { -// "event_name": "button_clicked", -// "session_id": "818c22c7-bcab-4e37-a12e-cd42a49547c6" -// } // Evento com parâmetros await EngineAnalytics.logEvent('purchase_completed', { @@ -334,14 +353,6 @@ await EngineAnalytics.logEvent('purchase_completed', { 'currency': 'BRL', 'category': 'subscription', }); -// Output: { -// "event_name": "purchase_completed", -// "session_id": "818c22c7-bcab-4e37-a12e-cd42a49547c6", -// "item_id": "premium_plan", -// "value": 29.99, -// "currency": "BRL", -// "category": "subscription" -// } // Evento de abertura do app await EngineAnalytics.logAppOpen(); @@ -353,7 +364,7 @@ await EngineAnalytics.logAppOpen(); // Definir ID do usuário await EngineAnalytics.setUserId('user_12345'); -// Com informações completas (para Faro) +// Com informações completas (para Faro/Clarity) await EngineAnalytics.setUserId( 'user_12345', 'usuario@exemplo.com', @@ -379,1027 +390,86 @@ await EngineAnalytics.setPage( ); ``` -### ✅ Verificação de Status +### 🐛 Bug Tracking ```dart -// Verificar se analytics está habilitado -if (EngineAnalytics.isEnabled) { - print('✅ Analytics está ativo'); -} - -// Verificar serviços específicos -if (EngineAnalytics.isFirebaseAnalyticsEnabled) { - print('🔥 Firebase Analytics ativo'); -} - -if (EngineAnalytics.isFaroEnabled) { - print('📊 Faro Analytics ativo'); -} - -if (EngineAnalytics.isGoogleLoggingInitialized) { - print('☁️ Google Cloud Logging ativo'); -} -``` - -## 🐛 Bug Tracking - -O `EngineBugTracking` oferece captura e logging de erros usando Firebase Crashlytics e Grafana Faro. - -### ⚙️ Configuração Básica - -```dart -import 'package:engine_tracking/engine_tracking.dart'; - -Future setupBugTracking() async { - final bugTrackingModel = EngineBugTrackingModel( - crashlyticsConfig: const EngineCrashlyticsConfig(enabled: true), - faroConfig: const EngineFaroConfig( - enabled: true, - endpoint: 'https://faro-collector.grafana.net/collect', - appName: 'MeuApp', - appVersion: '1.0.0', - environment: 'production', - apiKey: 'sua-chave-api-faro', - ), - googleLoggingConfig: const EngineGoogleLoggingConfig( - enabled: true, - projectId: 'seu-projeto-gcp', - logName: 'engine-tracking', - credentials: { - // Conteúdo completo do arquivo JSON da Service Account - "type": "service_account", - "project_id": "seu-projeto-gcp", - "private_key_id": "...", - "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", - "client_email": "sua-service-account@seu-projeto-gcp.iam.gserviceaccount.com", - "client_id": "...", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "...", - }, - resource: { - 'type': 'global', - 'labels': {'project_id': 'seu-projeto-gcp'}, - }, - ); - - await EngineBugTracking.init(bugTrackingModel); -} -``` - -### 📝 Logging Estruturado - -```dart -// Log simples -await EngineBugTracking.log('Usuário fez login'); - -// Log com contexto detalhado -await EngineBugTracking.log( - 'Erro no processamento de pagamento', - level: 'error', - attributes: { - 'user_id': 'user_12345', - 'payment_method': 'credit_card', - 'amount': 29.99, - 'transaction_id': 'txn_abc123', - }, - stackTrace: StackTrace.current, -); -``` - -### 👤 Gerenciamento de Usuário - -```dart -// Definir informações do usuário -await EngineBugTracking.setUserIdentifier( - 'user_12345', - 'usuario@exemplo.com', - 'João Silva', -); - -// Chaves customizadas -await EngineBugTracking.setCustomKey('plan_type', 'premium'); -await EngineBugTracking.setCustomKey('last_login', DateTime.now().toString()); -await EngineBugTracking.setCustomKey('device_type', 'mobile'); -``` - -### 🚨 Tratamento de Erros +// Log estruturado +await EngineBugTracking.log('Usuário realizou compra', { + 'user_id': '12345', + 'product_id': 'abc-123', + 'amount': 29.99, +}); -```dart -// Captura manual de erro +// Capturar erros try { - await riskyOperation(); + // código que pode falhar } catch (error, stackTrace) { await EngineBugTracking.recordError( error, stackTrace, - reason: 'Falha na operação crítica', - information: ['Contexto adicional', 'Dados do usuário'], - isFatal: false, - data: { - 'operation_id': '12345', - 'user_id': 'user_123', - 'timestamp': DateTime.now().toIso8601String(), - }, + reason: 'Falha no processamento de pagamento', ); } -// Tratamento global de erros Flutter -FlutterError.onError = EngineBugTracking.recordFlutterError; -``` - -### 🧪 Teste de Crash (Debug) - -```dart -// Apenas em modo debug para testar integração -#if DEBUG -await EngineBugTracking.testCrash(); -#endif -``` - -## 🆔 Session ID (Correlação Automática) - -O `EngineSession` oferece sistema de correlação de logs e analytics através de UUID v4 único por sessão do app. - -### 🎯 Características Principais - -- ✨ **Zero Configuração**: Session ID gerado automaticamente na primeira chamada -- 🔗 **Correlação Automática**: UUID v4 incluído automaticamente em todos os eventos -- 🆔 **Padrão RFC 4122**: Compatible com qualquer sistema que use UUID v4 -- 🔄 **Singleton Pattern**: Mesma instância de sessão durante toda a vida do app -- 🧪 **Testável**: Método `resetForTesting()` para cenários de teste - -### 🚀 Uso Automático - -O Session ID é incluído automaticamente em todos os eventos sem configuração adicional: - -```dart -// Zero configuração necessária! -await EngineAnalytics.logEvent('button_clicked', {'action': 'submit'}); -// Resultado: -// { -// "event_name": "button_clicked", -// "session_id": "818c22c7-bcab-4e37-a12e-cd42a49547c6", -// "action": "submit" -// } - -await EngineLog.info('User action completed'); -// Resultado no Google Cloud Logging: -// { -// "message": "User action completed", -// "session_id": "818c22c7-bcab-4e37-a12e-cd42a49547c6", -// "level": "info" -// } -``` - -### 🔍 Acesso Direto (Opcional) - -Se precisar acessar o Session ID diretamente: - -```dart -import 'package:engine_tracking/engine_tracking.dart'; - -// Obter Session ID atual -String sessionId = EngineSession.instance.sessionId; -print('Current Session: $sessionId'); - -// Verificar formato UUID v4 -bool isValidUUID = EngineSession.instance.isValidUUIDv4(sessionId); -print('Valid UUID v4: $isValidUUID'); // true - -// Para testes unitários (reseta session ID) -EngineSession.instance.resetForTesting(); -``` - -### 🎯 Formato UUID v4 - -O Session ID gerado segue o padrão UUID v4 (RFC 4122): - -``` -Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx -Exemplo: 818c22c7-bcab-4e37-a12e-cd42a49547c6 - -Características: -- 32 caracteres hexadecimais (0-9a-f) -- 5 grupos separados por hífen -- 13º caractere sempre "4" (versão) -- 17º caractere sempre "8", "9", "a" ou "b" (variant) +// Definir contexto do usuário +await EngineBugTracking.setUserIdentifier('user_12345'); +await EngineBugTracking.setCustomKey('subscription_plan', 'premium'); ``` -### 📊 Correlação nos Painéis - -Com o Session ID, você pode: - -- **Firebase Analytics**: Filtrar eventos por `session_id` para ver jornada completa -- **Google Cloud Logging**: Usar `session_id` para correlacionar logs da mesma sessão -- **Grafana Faro**: Agrupar eventos por sessão para análise de performance -- **Splunk**: Criar dashboards de jornada do usuário baseados no `session_id` - -```bash -# Exemplo de query no Google Cloud Logging -jsonPayload.session_id="818c22c7-bcab-4e37-a12e-cd42a49547c6" - -# Exemplo de filtro no Firebase Analytics -session_id == "818c22c7-bcab-4e37-a12e-cd42a49547c6" -``` - -## 📋 Logging do Sistema - -O `EngineLog` oferece sistema de logging estruturado com diferentes níveis. - -### 📊 Níveis de Log +### 📝 Sistema de Logging ```dart -import 'package:engine_tracking/engine_tracking.dart'; - -// Debug (Session ID incluído automaticamente) -await EngineLog.debug('Debug message', data: {'key': 'value'}); -// Output: { -// "message": "Debug message", -// "session_id": "818c22c7-bcab-4e37-a12e-cd42a49547c6", -// "key": "value" -// } - -// Info -await EngineLog.info('Info message', data: {'status': 'success'}); - -// Warning -await EngineLog.warning('Warning message', error: exception); - -// Error -await EngineLog.error('Error message', error: exception, stackTrace: stackTrace); - -// Fatal -await EngineLog.fatal('Fatal error', error: exception, stackTrace: stackTrace); -``` - -### 🏷️ Níveis Disponíveis - -| Nível | Valor | Uso Recomendado | -|-------|-------|-----------------| -| `debug` | 100 | Informações de desenvolvimento | -| `info` | 800 | Informações gerais | -| `warning` | 900 | Avisos e situações inesperadas | -| `error` | 1000 | Erros recuperáveis | -| `fatal` | 1200 | Erros críticos do sistema | - -## 🔧 Configuração Avançada - -### 🏗️ Configuração por Ambiente - -```dart -class TrackingConfig { - static EngineAnalyticsModel getAnalyticsConfig(String environment) { - final isProduction = environment == 'production'; - - return EngineAnalyticsModel( - firebaseAnalyticsConfig: EngineFirebaseAnalyticsConfig(enabled: isProduction), - faroConfig: EngineFaroConfig( - enabled: isProduction, - endpoint: isProduction - ? 'https://faro-prod.grafana.net/collect' - : 'https://faro-dev.grafana.net/collect', - appName: 'MeuApp', - appVersion: '1.0.0', - environment: environment, - apiKey: isProduction ? 'prod-key' : 'dev-key', - ), - ); - } - - static EngineBugTrackingModel getBugTrackingConfig(String environment) { - final isProduction = environment == 'production'; - - return EngineBugTrackingModel( - crashlyticsConfig: EngineCrashlyticsConfig(enabled: isProduction), - faroConfig: EngineFaroConfig( - enabled: true, // Faro sempre ativo para debugging - endpoint: isProduction - ? 'https://faro-prod.grafana.net/collect' - : 'https://faro-dev.grafana.net/collect', - appName: 'MeuApp', - appVersion: '1.0.0', - environment: environment, - apiKey: isProduction ? 'prod-key' : 'dev-key', - ), - ); - } -} -``` - -### 🎛️ Configuração Padrão - -```dart -// Analytics com configuração padrão (tudo desabilitado) -final defaultAnalytics = EngineAnalyticsModelDefault(); -await EngineAnalytics.init(defaultAnalytics); - -// Bug tracking com configuração padrão (tudo desabilitado) -final defaultBugTracking = EngineBugTrackingModelDefault(); -await EngineBugTracking.init(defaultBugTracking); -``` - -## 📊 Modelos de Dados - -### EngineAnalyticsModel - -```dart -class EngineAnalyticsModel { - final EngineFirebaseAnalyticsConfig firebaseAnalyticsConfig; - final EngineFaroConfig faroConfig; - - EngineAnalyticsModel({ - required this.firebaseAnalyticsConfig, - required this.faroConfig, - }); -} - -class EngineAnalyticsModelDefault implements EngineAnalyticsModel { - // Implementação com valores padrão desabilitados -} -``` - -### EngineBugTrackingModel - -```dart -class EngineBugTrackingModel { - final EngineCrashlyticsConfig crashlyticsConfig; - final EngineFaroConfig faroConfig; - - EngineBugTrackingModel({ - required this.crashlyticsConfig, - required this.faroConfig, - }); -} - -class EngineBugTrackingModelDefault implements EngineBugTrackingModel { - // Implementação com valores padrão desabilitados -} -``` - -### Configurações de Serviços - -```dart -// Firebase Analytics -class EngineFirebaseAnalyticsConfig { - final bool enabled; - - const EngineFirebaseAnalyticsConfig({required this.enabled}); -} - -// Firebase Crashlytics -class EngineCrashlyticsConfig { - final bool enabled; - - const EngineCrashlyticsConfig({required this.enabled}); -} - -// Grafana Faro (Compartilhado) -class EngineFaroConfig { - final bool enabled; - final String endpoint; - final String appName; - final String appVersion; - final String environment; - final String apiKey; - - const EngineFaroConfig({ - required this.enabled, - required this.endpoint, - required this.appName, - required this.appVersion, - required this.environment, - required this.apiKey, - }); -} -``` - -### EngineLogLevelType - -```dart -enum EngineLogLevelType { - debug('DEBUG', 100), - info('INFO', 800), - warning('WARNING', 900), - error('ERROR', 1000), - fatal('FATAL', 1200); - - final String name; - final int value; - - const EngineLogLevelType(this.name, this.value); -} -``` - -## 📱 Exemplo Completo - -Execute o exemplo interativo: - -```bash -cd example -flutter run -``` - -### 🏗️ Implementação Completa - -```dart -import 'package:flutter/material.dart'; -import 'package:engine_tracking/engine_tracking.dart'; - -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - - // Configurar Analytics - final analyticsModel = EngineAnalyticsModel( - firebaseAnalyticsConfig: const EngineFirebaseAnalyticsConfig(enabled: true), - faroConfig: const EngineFaroConfig( - enabled: true, - endpoint: 'https://faro-collector.grafana.net/collect', - appName: 'MeuApp', - appVersion: '1.2.3', - environment: 'production', - apiKey: 'faro-api-key', - ), - ); - - // Configurar Bug Tracking - final bugTrackingModel = EngineBugTrackingModel( - crashlyticsConfig: const EngineCrashlyticsConfig(enabled: true), - faroConfig: const EngineFaroConfig( - enabled: true, - endpoint: 'https://faro-collector.grafana.net/collect', - appName: 'MeuApp', - appVersion: '1.2.3', - environment: 'production', - apiKey: 'faro-api-key', - ), - ); - - // Inicializar serviços - await Future.wait([ - EngineAnalytics.init(analyticsModel), - EngineBugTracking.init(bugTrackingModel), - ]); - - // Configurar usuário - await Future.wait([ - EngineAnalytics.setUserId('user_12345', 'user@exemplo.com', 'João Silva'), - EngineBugTracking.setUserIdentifier('user_12345', 'user@exemplo.com', 'João Silva'), - ]); - - // Configurar tratamento global de erros - FlutterError.onError = EngineBugTracking.recordFlutterError; - - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Engine Tracking Demo', - home: MyHomePage(), - ); - } -} - -class MyHomePage extends StatefulWidget { - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - @override - void initState() { - super.initState(); - // Registrar visualização da tela - EngineAnalytics.setPage('HomeScreen'); - } - - void _onButtonPressed() async { - // Registrar evento de analytics - await EngineAnalytics.logEvent('button_pressed', { - 'button_name': 'home_action', - 'timestamp': DateTime.now().millisecondsSinceEpoch, - 'user_journey_step': 'main_interaction', - }); - - // Log para debugging - await EngineBugTracking.log( - 'Botão pressionado na tela inicial', - level: 'info', - attributes: {'screen': 'home', 'action': 'button_press'}, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Engine Tracking Demo')), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: _onButtonPressed, - child: Text('Testar Tracking'), - ), - SizedBox(height: 20), - Card( - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - children: [ - Text('Status dos Serviços:', style: TextStyle(fontWeight: FontWeight.bold)), - SizedBox(height: 8), - _buildStatusRow('Analytics', EngineAnalytics.isEnabled), - _buildStatusRow('Firebase Analytics', EngineAnalytics.isFirebaseAnalyticsEnabled), - _buildStatusRow('Faro Analytics', EngineAnalytics.isFaroEnabled), - _buildStatusRow('Google Cloud Logging', EngineAnalytics.isGoogleLoggingInitialized), - _buildStatusRow('Bug Tracking', EngineBugTracking.isEnabled), - _buildStatusRow('Crashlytics', EngineBugTracking.isCrashlyticsEnabled), - _buildStatusRow('Faro Logging', EngineBugTracking.isFaroEnabled), - _buildStatusRow('GCP Bug Tracking', EngineBugTracking.isGoogleLoggingInitialized), - ], - ), - ), - ), - ], - ), - ), - ); - } - - Widget _buildStatusRow(String service, bool isEnabled) { - return Row( - children: [ - Icon( - isEnabled ? Icons.check_circle : Icons.cancel, - size: 16, - color: isEnabled ? Colors.green : Colors.red, - ), - SizedBox(width: 8), - Text('$service: ${isEnabled ? 'Enabled' : 'Disabled'}'), - ], - ); - } -} -``` - -## 🏗️ Estrutura do Projeto - -### 📁 Organização de Diretórios e Arquivos - -``` -engine-tracking/ -├── .github/ # 🔧 Automação e Templates GitHub -│ ├── workflows/ # 🔄 GitHub Actions Pipelines -│ │ ├── ci.yml # Pipeline principal CI/CD -│ │ ├── publish.yml # Publicação automática pub.dev -│ │ └── quality.yml # Auditorias semanais de qualidade -│ ├── ISSUE_TEMPLATE/ # 📝 Templates de Issues -│ │ ├── bug_report.md # Template para reportar bugs -│ │ └── feature_request.md # Template para solicitar funcionalidades -│ ├── pull_request_template.md # Template para Pull Requests -│ └── README.md # Documentação da infraestrutura CI/CD -├── lib/ # 📚 Código fonte principal -│ ├── engine_tracking.dart # 🚪 Ponto de entrada principal -│ └── src/ # 📦 Implementações internas -│ ├── src.dart # Export barrel centralizado -│ ├── analytics/ # 📊 Sistema de Analytics -│ │ ├── analytics.dart # Export barrel do módulo -│ │ └── engine_analytics.dart # Implementação principal -│ ├── bug_tracking/ # 🐛 Sistema de Bug Tracking -│ │ ├── bug_tracking.dart # Export barrel do módulo -│ │ └── engine_bug_tracking.dart # Implementação principal -│ ├── config/ # ⚙️ Configurações dos serviços -│ │ ├── config.dart # Export barrel das configurações -│ │ ├── engine_firebase_analytics_config.dart -│ │ ├── engine_crashlytics_config.dart -│ │ └── engine_faro_config.dart -│ ├── models/ # 🏗️ Modelos de dados -│ │ ├── models.dart # Export barrel dos modelos -│ │ ├── engine_analytics_model.dart -│ │ └── engine_bug_tracking_model.dart -│ ├── enums/ # 🏷️ Enumerações -│ │ ├── enums.dart # Export barrel das enumerações -│ │ └── engine_log_level_type.dart -│ ├── logging/ # 📝 Sistema de logging -│ │ ├── logging.dart # Export barrel do logging -│ │ └── engine_log.dart # Implementação de logs -│ └── observers/ # 👁️ Observadores Flutter -│ ├── observers.dart # Export barrel dos observadores -│ └── engine_navigator_observer.dart -├── test/ # 🧪 Testes unitários e de integração -│ ├── analytics/ # Testes do sistema de analytics -│ ├── bug_tracking/ # Testes do sistema de bug tracking -│ ├── config/ # Testes das configurações -│ ├── models/ # Testes dos modelos de dados -│ ├── logging/ # Testes do sistema de logging -│ └── test_coverage.dart # Suite completa de testes -├── example/ # 📱 Aplicação de exemplo -│ ├── lib/main.dart # App Flutter demonstrativo -│ ├── pubspec.yaml # Dependências do exemplo -│ └── README.md # Documentação do exemplo -├── scripts/ # 🛠️ Scripts de desenvolvimento -│ ├── test_coverage.sh # Script de cobertura de testes -│ └── pana_analysis.sh # Script de análise de qualidade -├── codecov.yml # 📊 Configuração Codecov -├── pana_config.yaml # 🔍 Configuração Pana -├── pubspec.yaml # 📦 Configuração do pacote -├── CHANGELOG.md # 📝 Histórico de mudanças -├── LICENSE # ⚖️ Licença MIT -├── README.md # 📖 Documentação principal -└── analysis_options.yaml # 🔬 Configuração do Dart Analyzer -``` - -#### ⚙️ Arquivos de Configuração - -##### 📊 `codecov.yml` - Cobertura de Código -- Meta de cobertura: 45% (ajustada para dependências externas) -- Exclusões: Arquivos gerados, testes, exemplos -- Integração com PRs para comentários automáticos - -##### 🔍 `pana_config.yaml` - Análise de Qualidade -- Padrão de excelência: 160/160 pontos -- Verificações completas habilitadas -- Exclusão de arquivos de desenvolvimento - -##### 📦 `pubspec.yaml` - Configuração do Pacote -- Dependências otimizadas -- Metadados completos para pub.dev -- Compatibilidade com Flutter >=3.32.0 e Dart >=3.8.0 - -### 🎯 Arquitetura e Padrões - -#### 🏗️ Padrões Arquiteturais -- **Construtor Privado**: Previne instanciação desnecessária -- **API Estática**: Métodos estáticos para facilidade de uso -- **Inicialização Condicional**: Serviços inicializam apenas se habilitados -- **Export Unificado**: Import único para todo o pacote - -#### 🔒 Princípios de Qualidade -- **Tipo-seguro**: Implementação completamente tipada -- **Testável**: Arquitetura focada em testabilidade -- **Configurável**: Flexibilidade total na configuração -- **Performático**: Inicialização sob demanda - -## 🔧 Desenvolvimento - -### 📁 Estrutura Técnica +// Diferentes níveis de log +EngineLog.debug('Debug information'); +EngineLog.info('Informational message'); +EngineLog.warning('Warning message'); +EngineLog.error('Error occurred'); +EngineLog.fatal('Fatal error'); + +// Com contexto adicional +EngineLog.info('User action', context: { + 'action': 'button_click', + 'screen': 'home', + 'user_id': '12345', +}); +// Incluir em analytics (padrão: false para debug/info) +EngineLog.warning('Important warning', includeInAnalytics: true); ``` -lib/ -├── engine_tracking.dart # Ponto de entrada principal -└── src/ - ├── src.dart # Exportações centralizadas - ├── analytics/ # Sistema de analytics - │ ├── analytics.dart # Export barrel - │ └── engine_analytics.dart # Implementação principal - ├── bug_tracking/ # Sistema de bug tracking - │ ├── bug_tracking.dart # Export barrel - │ └── engine_bug_tracking.dart # Implementação principal - ├── config/ # Configurações dos serviços - │ ├── config.dart # Export barrel - │ ├── engine_firebase_analytics_config.dart - │ ├── engine_crashlytics_config.dart - │ ├── engine_faro_config.dart - │ └── engine_google_logging_config.dart - ├── models/ # Modelos de dados - │ ├── models.dart # Export barrel - │ ├── engine_analytics_model.dart - │ └── engine_bug_tracking_model.dart - ├── enums/ # Enumerações - │ ├── enums.dart # Export barrel - │ └── engine_log_level_type.dart - ├── logging/ # Sistema de logging - │ ├── logging.dart # Export barrel - │ └── engine_log.dart # Implementação de logging - └── observers/ # Observadores Flutter - ├── observers.dart # Export barrel - └── engine_navigator_observer.dart - -test/ -├── analytics/ # Testes de analytics -├── bug_tracking/ # Testes de bug tracking -├── config/ # Testes de configuração -├── models/ # Testes de modelos -├── logging/ # Testes de logging -└── test_coverage.dart # Suite completa de testes - -example/ # App de demonstração -├── lib/main.dart # Implementação de exemplo -├── pubspec.yaml # Dependências do exemplo -└── README.md # Documentação do exemplo -``` - -## Sistema de Tracking de Views - -Este sistema fornece funcionalidades automáticas de tracking para widgets StatelessWidget e StatefulWidget, permitindo monitorar o comportamento do usuário passo a passo. - -## 🎯 Widget Tracking -O Engine Tracking oferece um sistema avançado de tracking automático para widgets, permitindo monitoramento transparente de navegação, ações do usuário e ciclo de vida de telas. - -### Características dos Widgets - -- 📊 **Tracking Automático**: Visualizações de tela registradas automaticamente -- 🔄 **Ciclo de Vida**: Monitoramento de init/dispose em StatefulWidgets -- 👆 **Ações do Usuário**: Métodos integrados para logging de interações -- 📝 **Eventos Customizados**: Sistema flexível para eventos específicos -- 🐛 **Tratamento de Erros**: Captura contextualizada de erros por tela -- ⚙️ **Configurável**: Controle granular sobre tracking automático - -### Implementações Disponíveis - -#### 1. Classes Engine (Recomendado) - -As classes Engine oferecem implementação completa com tracking automático integrado. - -##### EngineStatelessWidget +### 👁️ View Tracking com Widgets ```dart -import 'package:engine_tracking/engine_tracking.dart'; - class HomePage extends EngineStatelessWidget { HomePage({super.key}); @override - String get screenName => 'home_page'; - - @override - Map? get screenParameters => { - 'version': '1.0.0', - 'source': 'main_menu', - }; - - @override - Widget buildWithTracking(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Início')), - body: Column( - children: [ - ElevatedButton( - onPressed: () { - logUserAction('button_pressed', parameters: { - 'button_type': 'primary', - 'action': 'navigate_to_settings', - }); - }, - child: const Text('Configurações'), - ), - ElevatedButton( - onPressed: () { - logCustomEvent('feature_accessed', parameters: { - 'feature': 'premium_content', - }); - }, - child: const Text('Conteúdo Premium'), - ), - ], - ), - ); - } -} -``` - -##### EngineStatefulWidget - -```dart -import 'package:engine_tracking/engine_tracking.dart'; - -class ProfilePage extends EngineStatefulWidget { - const ProfilePage({super.key}); - - @override - EngineStatefulWidgetState createState() => _ProfilePageState(); -} - -class _ProfilePageState extends EngineStatefulWidgetState { - String _userName = ''; - - @override - String get screenName => 'profile_page'; + String get screenName => 'home'; @override Map? get screenParameters => { 'user_type': 'premium', + 'version': '1.0.0', }; @override Widget buildWithTracking(BuildContext context) { return Scaffold( - body: Column( - children: [ - TextField( - onChanged: (value) { - setState(() { - _userName = value; - }); - - logStateChange('username_changed', additionalData: { - 'character_count': value.length, - }); - }, - ), - ElevatedButton( - onPressed: () { - logUserAction('profile_updated', parameters: { - 'field': 'username', - 'new_length': _userName.length, - }); - }, - child: const Text('Salvar'), - ), - ], - ), - ); - } -} -``` - -### Exemplo Completo com Classes Engine - -```dart -import 'package:engine_tracking/engine_tracking.dart'; - -class SettingsPage extends EngineStatelessWidget { - SettingsPage({super.key}); - - @override - String get screenName => 'settings_page'; - - @override - Widget buildWithTracking(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Configurações')), - body: ListView( - children: [ - ListTile( - title: const Text('Notificações'), - onTap: () => logUserAction('settings_item_tapped', - parameters: {'item': 'notifications'}), - ), - ListTile( - title: const Text('Privacidade'), - onTap: () => logUserAction('settings_item_tapped', - parameters: {'item': 'privacy'}), - ), - ], - ), - ); - } -} -``` - -```dart -import 'package:engine_tracking/engine_tracking.dart'; - -class LoginPage extends EngineStatefulWidget { - const LoginPage({super.key}); - - @override - EngineStatefulWidgetState createState() => _LoginPageState(); -} - -class _LoginPageState extends EngineStatefulWidgetState { - bool _isLoading = false; - - @override - String get screenName => 'login_page'; - - @override - Widget buildWithTracking(BuildContext context) { - return Scaffold( - body: Column( - children: [ - TextField( - decoration: const InputDecoration(labelText: 'Email'), - onSubmitted: (value) { - logUserAction('field_completed', parameters: { - 'field': 'email', - 'has_value': value.isNotEmpty, - }); - }, - ), - ElevatedButton( - onPressed: _isLoading ? null : () async { - setState(() { - _isLoading = true; - }); - - logStateChange('login_started'); - - try { - await Future.delayed(const Duration(seconds: 2)); - logUserAction('login_success'); - } catch (e) { - logScreenError('Login falhou', - exception: e, - additionalData: {'retry_count': 1}); - } - - setState(() { - _isLoading = false; - }); - }, - child: _isLoading - ? const CircularProgressIndicator() - : const Text('Entrar'), - ), - ], + appBar: AppBar(title: const Text('Home')), + body: ElevatedButton( + onPressed: () { + logUserAction('cta_clicked', parameters: { + 'button_type': 'primary', + 'location': 'header', + }); + }, + child: const Text('Click Me'), ), ); } } ``` -## Métodos Disponíveis - -### Tracking Automático -- `_trackScreenView()`: Registra visualização da tela automaticamente -- **Ciclo de vida**: initState/dispose tracking (apenas StatefulWidget) - -### Métodos de Logging -- `logUserAction(action, {parameters})`: Registra ações do usuário -- `logCustomEvent(eventName, {parameters})`: Registra eventos customizados -- `logScreenError(error, {exception, stackTrace, additionalData})`: Registra erros -- `logStateChange(description, {additionalData})`: Registra mudanças de estado (StatefulWidget) - -### Configurações -- `screenName`: Nome da tela (padrão: nome da classe) -- `screenParameters`: Parâmetros adicionais da tela -- `enableAutoTracking`: Ativa/desativa tracking automático -- `enableLifecycleTracking`: Ativa/desativa tracking de ciclo de vida (StatefulWidget) - -## Dados Coletados - -### Visualização de Tela -```json -{ - "screen_name": "home_page", - "screen_type": "StatelessWidget", - "timestamp": "2023-12-01T10:30:00.000Z", - "parameters": {"version": "1.0.0"} -} -``` - -### Ação do Usuário -```json -{ - "screen_name": "home_page", - "action": "button_pressed", - "widget_type": "StatelessWidget", - "button_type": "primary" -} -``` - -### Fechamento de Tela (StatefulWidget) -```json -{ - "screen_name": "profile_page", - "widget_type": "StatefulWidget", - "time_spent_seconds": 45, - "time_spent_minutes": 0 -} -``` - -## Integração com Analytics - -Todos os eventos são automaticamente enviados para: -- **Firebase Analytics** (se configurado) -- **Grafana Faro** (se configurado) -- **Google Cloud Logging** (se configurado) -- **Engine Log** para debugging - -## Melhores Práticas - -1. **Use nomes descritivos** para telas e ações -2. **Inclua parâmetros relevantes** sem dados sensíveis -3. **Monitore erros** com contexto apropriado -4. **Use classes Engine** para funcionalidade completa -5. **Personalize screenName** para identificação clara -6. **Agrupe ações relacionadas** com prefixos consistentes - -## Desabilitando Tracking - -```dart -class MyPage extends EngineStatelessWidget { - MyPage({super.key}); - - @override - bool get enableAutoTracking => false; // Desabilita tracking automático - - @override - Widget buildWithTracking(BuildContext context) { - // ... resto da implementação - } -} -``` - -## Exemplo de Uso Avançado - ```dart class ShoppingCartPage extends StatefulWidget { final List initialProducts; @@ -1479,45 +549,29 @@ class _ShoppingCartPageState extends EngineStatefulWidgetState } ``` -### 🧪 Scripts de Desenvolvimento - -```bash -# Executar todos os testes -flutter test - -# Testes com cobertura -flutter test --coverage -genhtml coverage/lcov.info -o coverage/html - -# Análise estática -dart analyze - -# Formatação de código -dart format . - -# Publicar (dry-run) -dart pub publish --dry-run -``` - -### 📊 Comandos de Qualidade +### ✅ Verificação de Status -```bash -# Verificar cobertura -dart pub global activate test_coverage -dart pub global run test_coverage --min-coverage=95 +```dart +// Verificar se analytics está habilitado +if (EngineAnalytics.isEnabled) { + print('✅ Analytics está ativo'); +} -# Análise Pana -dart pub global activate pana -dart pub global run pana +// Verificar serviços específicos +if (EngineAnalytics.isFirebaseAnalyticsEnabled) { + print('🔥 Firebase Analytics ativo'); +} -# Verificar dependências -dart pub outdated +if (EngineAnalytics.isFaroEnabled) { + print('📊 Faro Analytics ativo'); +} -# Atualizar dependências -dart pub upgrade +if (EngineAnalytics.isGoogleLoggingInitialized) { + print('☁️ Google Cloud Logging ativo'); +} ``` -## 🧪 Testes +### 🧪 Testes Execute os testes: @@ -1532,11 +586,6 @@ flutter test - ✅ **Testes completos** para sistema de logging e Google Cloud Logging - ✅ **Testes completos** para Session ID com validação UUID v4 RFC 4122 -**Observações:** -- Testes de inicialização com Firebase/Faro são mocados para evitar dependências reais -- Todos os testes de lógica de negócio e configuração passam corretamente -- Cobertura focada em componentes testáveis sem dependências externas - Para cobertura de testes: ```bash @@ -1545,67 +594,9 @@ genhtml coverage/lcov.info -o coverage/html open coverage/html/index.html ``` -## 📱 Plataformas Suportadas - -- ✅ iOS -- ✅ Android - -## 🤖 Integração MCP (Model Context Protocol) - -O Engine Tracking v1.3.0 inclui suporte completo ao **Model Context Protocol (MCP)**, permitindo que assistentes de IA (como Claude, GPT-4, etc.) acessem dados do projeto em tempo real. - -### 🔧 Configuração Rápida - -O projeto inclui configuração automática para os principais serviços: - -```bash -# Ver documentação completa -docs/MCP_CONFIGURATION.md -docs/MCP_QUICK_SETUP.md -``` - -### 🛠️ Serviços Suportados - -| Serviço | Funcionalidades | Status | -|---------|----------------|--------| -| **GitHub** | Repos, Issues, PRs, Code Search | ✅ Configurado | -| **Firebase** | Projetos, Deploy, Firestore, Functions | ✅ Configurado | -| **Supabase** | Tabelas, SQL, Schema, Projetos | ⚙️ Requer tokens | -| **TaskMaster** | Tarefas, Status, Subtarefas | ✅ Configurado | - -### 📋 Ferramentas Incluídas - -```bash -# Testar configurações MCP -node scripts/test_mcp_connections.js - -# Configurar tokens interativamente -node scripts/setup_mcp_tokens.js - -# Ver status atual -node scripts/setup_mcp_tokens.js --status -``` - -### 💡 Capacidades - -Com MCP configurado, sua IA pode: -- 🔍 **Acessar repositórios** GitHub em tempo real -- 🔥 **Gerenciar projetos** Firebase -- 🗄️ **Consultar bancos** Supabase -- 📊 **Monitorar tarefas** TaskMaster -- 📝 **Analisar código** e estrutura do projeto - -### 🚀 Exemplo de Uso - -``` -Pergunta à IA: "Mostre o status dos adaptadores Google Cloud Logging" -Resposta: Lista arquivos, testes e documentação automaticamente - -Pergunta: "Quais tarefas estão pendentes no TaskMaster?" -Resposta: Acessa e mostra tarefas em tempo real -``` +--- -## 🤝 Contribuição +## 🤝 Como Contribuir Contribuições são bem-vindas! Por favor: @@ -1623,14 +614,25 @@ Contribuições são bem-vindas! Por favor: - Teste em Android e iOS - Atualize o CHANGELOG.md -## 📄 Licença +### 📄 Licença Este projeto está licenciado sob a Licença MIT - veja o arquivo LICENSE para detalhes. +--- + ## 🏢 Sobre a STMR -Desenvolvido pela STMR - Especialistas em soluções móveis. +
+ STMR Logo +
+ +Desenvolvido pela **STMR** - Especialistas em soluções móveis. + +A STMR é uma empresa focada no desenvolvimento de soluções tecnológicas inovadoras para dispositivos móveis, especializando-se em arquiteturas robustas, performance otimizada e experiências de usuário excepcionais. + +### 🎯 Nossa Missão +Fornecer ferramentas e bibliotecas Flutter de alta qualidade que aceleram o desenvolvimento de aplicações móveis enterprise, mantendo os mais altos padrões de segurança, performance e usabilidade. --- -**💡 Dica v1.3.0**: Para máxima eficiência, configure apenas os serviços que você realmente utiliza. A biblioteca é otimizada para funcionar com qualquer combinação de serviços habilitados ou desabilitados. Com **Session ID automático**, **Google Cloud Logging** e **MCP**, você agora tem correlação completa de logs, centralização avançada e integração perfeita com assistentes de IA! 🆔🔥 \ No newline at end of file +**💡 Dica v1.4.0**: Para máxima eficiência, configure apenas os serviços que você realmente utiliza. A biblioteca é otimizada para funcionar com qualquer combinação de serviços habilitados ou desabilitados. Com **Session ID automático** e **Google Cloud Logging**, você agora tem correlação completa de logs e centralização avançada! 🆔🔥 \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 94c6912..2b2fbf4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,100 +1,212 @@ -include: package:lints/recommended.yaml +# Flutter/Dart 2025 - Comprehensive Lint Configuration +# Based on flutter_lints 6.0.0 and modern best practices +# Consulte este arquivo como referência para nosso padrão de lint + +include: package:flutter_lints/flutter.yaml formatter: page_width: 120 trailing_commas: preserve +analyzer: + exclude: + - lib/generated/** + - lib/translations/** + - lib/l10n/** + - lib/**/*.g.dart + - test/_data/** + - example/** + - examples/** + - build/** + - .dart_tool/** + + # Language settings for modern Dart features + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + + errors: + # Security and runtime safety + invalid_assignment: error + missing_required_param: error + missing_return: error + dead_code: error + unreachable_from_main: error + + # Performance warnings + unused_local_variable: warning + unused_element: warning + unused_field: warning + unused_import: warning + unused_shown_name: warning + + # Null safety enforcement + receiver_of_type_never: error + null_check_on_nullable_type_parameter: error + + # Records and patterns (Dart 3.0+) + record_literal_one_positional_no_trailing_comma: error + + # Collections best practices + collection_methods_unrelated_type: error + unrelated_type_equality_checks: error + linter: rules: - always_use_package_imports: true + # === SECURITY & SAFETY (2025 Critical) === + # Modern security practices + avoid_dynamic_calls: true + avoid_type_to_string: true + avoid_web_libraries_in_flutter: true + secure_pubspec_urls: true + + # Null safety enforcement + avoid_null_checks_in_equality_operators: true + null_check_on_nullable_type_parameter: true + unnecessary_null_checks: true + unnecessary_null_aware_assignments: true + unnecessary_null_aware_operator_on_extension_on_nullable: true + unnecessary_nullable_for_final_variable_declarations: true + + # === PERFORMANCE (2025 Focus) === + # Memory and performance optimization + avoid_unnecessary_containers: true + avoid_function_literals_in_foreach_calls: true + avoid_slow_async_io: true + prefer_const_constructors: true + prefer_const_constructors_in_immutables: true + prefer_const_declarations: true + prefer_const_literals_to_create_immutables: true + use_colored_box: true + use_decorated_box: true + sized_box_for_whitespace: true + sized_box_shrink_expand: true + + # Async/Future optimization + avoid_void_async: true + unawaited_futures: true + discarded_futures: true await_only_futures: true + unnecessary_await_in_return: true + + # === MODERN DART FEATURES (3.0+) === + # Records and patterns support + use_super_parameters: true + matching_super_parameters: true + use_enums: true + + # === CODING STANDARDS === + # Imports and organization + always_use_package_imports: true + avoid_relative_lib_imports: true + depend_on_referenced_packages: true + directives_ordering: true + + # Code style consistency always_declare_return_types: true - cancel_subscriptions: true - close_sinks: true - comment_references: true - only_throw_errors: true - prefer_final_in_for_each: true + annotate_overrides: true prefer_single_quotes: true - avoid_shadowing_type_parameters: true - avoid_dynamic_calls: false + prefer_final_fields: true + prefer_final_locals: true + prefer_final_parameters: true + prefer_final_in_for_each: true + + # === FLUTTER SPECIFIC (2025) === + # Widget best practices + use_key_in_widget_constructors: true + use_full_hex_values_for_flutter_colors: true + sort_child_properties_last: true + no_logic_in_create_state: true + use_build_context_synchronously: true + + # === ERROR PREVENTION === + # Runtime error prevention avoid_empty_else: true - avoid_print: true - avoid_relative_lib_imports: true - avoid_returning_null_for_future: true - avoid_slow_async_io: true - avoid_type_to_string: true + avoid_returning_null_for_void: true + avoid_shadowing_type_parameters: true avoid_types_as_parameter_names: true - avoid_web_libraries_in_flutter: true - discarded_futures: true - unawaited_futures: true + control_flow_in_finally: true empty_statements: true - prefer_void_to_null: true - always_put_control_body_on_new_line: true - always_require_non_null_named_parameters: true - annotate_overrides: true + exhaustive_cases: true + no_duplicate_case_values: true + throw_in_finally: true + + # Logic and flow avoid_bool_literals_in_conditional_expressions: true - avoid_escaping_inner_quotes: true - parameter_assignments: true - avoid_function_literals_in_foreach_calls: true - avoid_returning_null_for_void: true - avoid_setters_without_getters: true - avoid_types_on_closure_parameters: true - avoid_unnecessary_containers: true - avoid_void_async: true - camel_case_extensions: true - camel_case_types: true - cascade_invocations: true + no_literal_bool_comparisons: true + prefer_conditional_assignment: true + prefer_if_null_operators: true + prefer_null_aware_operators: true + prefer_null_aware_method_calls: true + + # === READABILITY & MAINTAINABILITY === + # Code organization + library_names: true + library_prefixes: true + file_names: true + package_names: true constant_identifier_names: true + non_constant_identifier_names: true + + # Documentation and comments + slash_for_doc_comments: true + comment_references: true + flutter_style_todos: true + + # Code structure curly_braces_in_flow_control_structures: true - directives_ordering: true empty_constructor_bodies: true - exhaustive_cases: true - file_names: true - library_names: true - library_prefixes: true - library_private_types_in_public_api: true - no_leading_underscores_for_library_prefixes: true - omit_local_variable_types: true - prefer_adjacent_string_concatenation: true prefer_collection_literals: true - prefer_conditional_assignment: true - prefer_const_constructors: true - prefer_const_declarations: true prefer_contains: true prefer_expression_function_bodies: true - prefer_final_fields: true - prefer_final_locals: true - prefer_final_parameters: true prefer_foreach: true prefer_function_declarations_over_variables: true - prefer_if_null_operators: true + prefer_if_elements_to_conditional_expressions: true prefer_initializing_formals: true prefer_inlined_adds: true prefer_interpolation_to_compose_strings: true prefer_is_empty: true prefer_is_not_empty: true prefer_is_not_operator: true - prefer_mixin: false - prefer_null_aware_method_calls: true - prefer_null_aware_operators: true prefer_spread_collections: true prefer_typing_uninitialized_variables: true -analyzer: - exclude: - - lib/generated/** - - lib/translations/** - - lib/l10n/** - - lib/**/*.g.dart - - test/_data/** - - example/** - - examples/** - errors: - invalid_assignment: warning - missing_return: error - dead_code: error - unused_local_variable: info - unused_element: info - unused_field: info - unused_import: info - unused_shown_name: info + # === MODERN CLEANUP === + # Remove redundancy + unnecessary_brace_in_string_interps: true + unnecessary_const: true + unnecessary_constructor_name: true + unnecessary_getters_setters: true + unnecessary_lambdas: true + unnecessary_late: true + unnecessary_new: true + unnecessary_overrides: true + unnecessary_parenthesis: true + unnecessary_raw_strings: true + unnecessary_statements: true + unnecessary_string_escapes: true + unnecessary_string_interpolations: true + unnecessary_this: true + unnecessary_to_list_in_spreads: true + + # === PROFESSIONAL STANDARDS === + # Trailing commas for better diffs + require_trailing_commas: true + + # Parameter organization + avoid_positional_boolean_parameters: true + avoid_unused_constructor_parameters: true + + # Type safety + avoid_equals_and_hash_code_on_mutable_classes: true + hash_and_equals: true + test_types_in_equals: true + + # === DISABLED RULES (with reasons) === + # Disabled: Conflicting with our style + # always_specify_types: false # We prefer type inference where clear + # public_member_api_docs: false # Internal packages don't need full docs + # avoid_print: false # Useful in development (use debugPrint in production) + # lines_longer_than_80_chars: false # We use 120 chars + # sort_constructors_first: false # We prefer logical grouping diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..1b78361 Binary files /dev/null and b/assets/images/logo.png differ diff --git a/example/lib/http_tracking_example.dart b/example/lib/http_tracking_example.dart index 452113a..623522c 100644 --- a/example/lib/http_tracking_example.dart +++ b/example/lib/http_tracking_example.dart @@ -871,11 +871,10 @@ class UsersListPage extends EngineStatelessWidget { /// Função para demonstrar a inicialização do sistema com tracking HTTP Future initializeHttpTrackingExample() async { - // Configuração com tracking HTTP habilitado final analyticsModel = EngineAnalyticsModel( firebaseAnalyticsConfig: const EngineFirebaseAnalyticsConfig(enabled: false), faroConfig: const EngineFaroConfig( - enabled: true, // Habilitado para capturar requisições HTTP + enabled: true, endpoint: 'https://faro-collector-prod-us-east-0.grafana.net/collect', appName: 'engine_tracking_http_example', appVersion: '1.0.0', @@ -892,12 +891,22 @@ Future initializeHttpTrackingExample() async { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), + googleLoggingConfig: const EngineGoogleLoggingConfig( + enabled: false, + projectId: '', + logName: '', + credentials: {}, + ), ); final bugTrackingModel = EngineBugTrackingModel( crashlyticsConfig: const EngineCrashlyticsConfig(enabled: false), faroConfig: const EngineFaroConfig( - enabled: true, // Habilitado para capturar erros HTTP + enabled: true, endpoint: 'https://faro-collector-prod-us-east-0.grafana.net/collect', appName: 'engine_tracking_http_example', appVersion: '1.0.0', @@ -906,9 +915,14 @@ Future initializeHttpTrackingExample() async { namespace: 'flutter_app', platform: 'mobile', ), + googleLoggingConfig: const EngineGoogleLoggingConfig( + enabled: false, + projectId: '', + logName: '', + credentials: {}, + ), ); - // Inicializar os sistemas await EngineAnalytics.initWithModel(analyticsModel); await EngineBugTracking.initWithModel(bugTrackingModel); diff --git a/example/lib/main.dart b/example/lib/main.dart index d97b61b..df23286 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,11 +8,17 @@ import 'http_tracking_example.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await initializeTracking(); - runApp(const MyApp()); + final (analyticsModel, bugTrackingModel) = await initializeTracking(); + + runApp( + EngineWidget( + app: const MyApp(), + clarityConfig: analyticsModel.clarityConfig, + ), + ); } -Future initializeTracking() async { +Future<(EngineAnalyticsModel analyticsModel, EngineBugTrackingModel bugTrackingModel)> initializeTracking() async { final faroConfig = EngineFaroConfig( enabled: true, endpoint: 'https://faro-collector-prod-sa-east-1.grafana.net/collect/54d9b2d4c4e2a550c890876a914a3525', @@ -23,7 +29,12 @@ Future initializeTracking() async { namespace: 'engine.stmr.tech', platform: Platform.isAndroid ? 'android' : 'ios', ); - // Configure Analytics + + final clarityConfig = EngineClarityConfig( + enabled: true, + projectId: 's8nukxh19i', + ); + final analyticsModel = EngineAnalyticsModel( firebaseAnalyticsConfig: const EngineFirebaseAnalyticsConfig(enabled: false), faroConfig: faroConfig, @@ -35,38 +46,50 @@ Future initializeTracking() async { sourcetype: '', index: '', ), + clarityConfig: clarityConfig, + googleLoggingConfig: const EngineGoogleLoggingConfig( + enabled: false, + projectId: '', + logName: '', + credentials: {}, + ), ); - // Configure Bug Tracking final bugTrackingModel = EngineBugTrackingModel( crashlyticsConfig: const EngineCrashlyticsConfig(enabled: false), faroConfig: faroConfig, + googleLoggingConfig: const EngineGoogleLoggingConfig( + enabled: false, + projectId: '', + logName: '', + credentials: {}, + ), ); try { - // Initialize services await Future.wait([ EngineAnalytics.initWithModel(analyticsModel), EngineBugTracking.initWithModel(bugTrackingModel), ]); - // Set user information await Future.wait([ EngineAnalytics.setUserId('demo_user_123', 'demo@example.com', 'Demo User'), EngineBugTracking.setUserIdentifier('demo_user_123', 'demo@example.com', 'Demo User'), ]); - // Log app initialization await EngineAnalytics.logAppOpen(); await EngineBugTracking.log('App initialized successfully', level: 'info'); + + return (analyticsModel, bugTrackingModel); } catch (e, stackTrace) { - // Log initialization errors await EngineBugTracking.recordError( e, stackTrace, reason: 'Failed to initialize tracking services', isFatal: false, ); + + return (EngineAnalyticsModelDefault(), EngineBugTrackingModelDefault()); } } @@ -97,7 +120,6 @@ class _HomePageState extends State { @override void initState() { super.initState(); - // Track page view EngineAnalytics.setPage('HomePage'); } @@ -106,14 +128,12 @@ class _HomePageState extends State { _counter++; }); - // Track button click event await EngineAnalytics.logEvent('button_clicked', { 'button_name': 'increment_counter', 'counter_value': _counter, 'timestamp': DateTime.now().millisecondsSinceEpoch, }); - // Log the action await EngineBugTracking.log( 'Counter incremented', level: 'info', @@ -123,10 +143,8 @@ class _HomePageState extends State { void _simulateError() async { try { - // Simulate an error for demonstration throw Exception('This is a simulated error for testing purposes'); } catch (error, stackTrace) { - // Record the error await EngineBugTracking.recordError( error, stackTrace, @@ -136,7 +154,6 @@ class _HomePageState extends State { data: {'counter_value': _counter, 'error_type': 'simulated'}, ); - // Show snackbar to user if (mounted) { ScaffoldMessenger.of( context, @@ -199,7 +216,6 @@ class _HomePageState extends State { Text('$_counter', style: Theme.of(context).textTheme.headlineMedium), const SizedBox(height: 40), - // Service Status Card( margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Padding( @@ -218,7 +234,6 @@ class _HomePageState extends State { const SizedBox(height: 20), - // Action Buttons Wrap( spacing: 10, runSpacing: 10, @@ -286,7 +301,6 @@ class _SecondPageState extends State { @override void initState() { super.initState(); - // Track page view with previous screen EngineAnalytics.setPage('SecondPage', 'HomePage'); } diff --git a/example/lib/view_tracking_example.dart b/example/lib/view_tracking_example.dart index 631487e..1531704 100644 --- a/example/lib/view_tracking_example.dart +++ b/example/lib/view_tracking_example.dart @@ -483,7 +483,6 @@ class _ShoppingCartPageState extends EngineStatefulWidgetState /// Função para demonstrar a inicialização do sistema Future initializeTrackingExample() async { - // Configuração básica do Analytics final analyticsModel = EngineAnalyticsModel( firebaseAnalyticsConfig: const EngineFirebaseAnalyticsConfig(enabled: false), faroConfig: const EngineFaroConfig( @@ -504,9 +503,18 @@ Future initializeTrackingExample() async { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), + googleLoggingConfig: const EngineGoogleLoggingConfig( + enabled: false, + projectId: '', + logName: '', + credentials: {}, + ), ); - // Configuração básica do Bug Tracking final bugTrackingModel = EngineBugTrackingModel( crashlyticsConfig: const EngineCrashlyticsConfig(enabled: false), faroConfig: const EngineFaroConfig( @@ -519,9 +527,14 @@ Future initializeTrackingExample() async { namespace: '', platform: '', ), + googleLoggingConfig: const EngineGoogleLoggingConfig( + enabled: false, + projectId: '', + logName: '', + credentials: {}, + ), ); - // Inicializar os sistemas await EngineAnalytics.initWithModel(analyticsModel); await EngineBugTracking.initWithModel(bugTrackingModel); diff --git a/lib/src/analytics/adapters/engine_firebase_analytics_adapter.dart b/lib/src/analytics/adapters/engine_firebase_analytics_adapter.dart index 0bc0337..bcc3dfd 100644 --- a/lib/src/analytics/adapters/engine_firebase_analytics_adapter.dart +++ b/lib/src/analytics/adapters/engine_firebase_analytics_adapter.dart @@ -108,7 +108,7 @@ class EngineFirebaseAnalyticsAdapter implements IEngineAnalyticsAdapter { await _firebaseAnalytics?.logScreenView( screenName: screenName, - screenClass: enrichedParameters?['screen_class'] ?? 'Flutter', + screenClass: enrichedParameters?['screen_class']?.toString() ?? 'Flutter', parameters: enrichedParameters?.map( (final k, final v) => MapEntry(k, v as Object), ), diff --git a/lib/src/analytics/adapters/engine_splunk_analytics_adapter.dart b/lib/src/analytics/adapters/engine_splunk_analytics_adapter.dart index 9a3ba89..4786f14 100644 --- a/lib/src/analytics/adapters/engine_splunk_analytics_adapter.dart +++ b/lib/src/analytics/adapters/engine_splunk_analytics_adapter.dart @@ -161,7 +161,6 @@ class EngineSplunkAnalyticsAdapter implements IEngineAnalyticsAdapter { try { await _sendToSplunk({ 'event': 'analytics_reset', - 'data': {}, 'timestamp': DateTime.now().millisecondsSinceEpoch / 1000, }); } catch (e) { @@ -186,6 +185,6 @@ class EngineSplunkAnalyticsAdapter implements IEngineAnalyticsAdapter { request.write(body); final response = await request.close(); - await response.drain(); + await response.drain>(); } } diff --git a/lib/src/bug_tracking/adapters/engine_faro_bug_tracking_adapter.dart b/lib/src/bug_tracking/adapters/engine_faro_bug_tracking_adapter.dart index dee6f04..f6914dc 100644 --- a/lib/src/bug_tracking/adapters/engine_faro_bug_tracking_adapter.dart +++ b/lib/src/bug_tracking/adapters/engine_faro_bug_tracking_adapter.dart @@ -69,9 +69,6 @@ class EngineFaroBugTrackingAdapter implements IEngineBugTrackingAdapter { debugPrint('setCustomKey: Faro Bug Tracking is not initialized'); return; } - - // Faro doesn't have a direct equivalent for custom keys - // We can use context for similar functionality } @override diff --git a/lib/src/config/config.dart b/lib/src/config/config.dart index 1dbef77..1b665ed 100644 --- a/lib/src/config/config.dart +++ b/lib/src/config/config.dart @@ -1,3 +1,4 @@ +export 'engine_clarity_config.dart'; export 'engine_crashlytics_config.dart'; export 'engine_faro_config.dart'; export 'engine_firebase_analytics_config.dart'; diff --git a/lib/src/config/engine_clarity_config.dart b/lib/src/config/engine_clarity_config.dart new file mode 100644 index 0000000..5e0739a --- /dev/null +++ b/lib/src/config/engine_clarity_config.dart @@ -0,0 +1,16 @@ +class EngineClarityConfig { + const EngineClarityConfig({ + required this.enabled, + required this.projectId, + this.userId, + }); + + final bool enabled; + + final String projectId; + + final String? userId; + + @override + String toString() => 'EngineClarityConfig(enabled: $enabled, projectId: *****, userId: *****)'; +} diff --git a/lib/src/config/engine_firebase_analytics_config.dart b/lib/src/config/engine_firebase_analytics_config.dart index d120303..6939de0 100644 --- a/lib/src/config/engine_firebase_analytics_config.dart +++ b/lib/src/config/engine_firebase_analytics_config.dart @@ -5,12 +5,4 @@ class EngineFirebaseAnalyticsConfig { @override String toString() => 'EngineFirebaseAnalyticsConfig(enabled: $enabled)'; - - @override - bool operator ==(final Object other) => - identical(this, other) || - other is EngineFirebaseAnalyticsConfig && runtimeType == other.runtimeType && enabled == other.enabled; - - @override - int get hashCode => enabled.hashCode; } diff --git a/lib/src/config/engine_splunk_config.dart b/lib/src/config/engine_splunk_config.dart index 761af45..cf7c7d0 100644 --- a/lib/src/config/engine_splunk_config.dart +++ b/lib/src/config/engine_splunk_config.dart @@ -18,20 +18,4 @@ class EngineSplunkConfig { @override String toString() => 'EngineSplunkConfig(enabled: $enabled, endpoint: $endpoint, source: $source, sourcetype: $sourcetype, index: $index, token: ****)'; - - @override - bool operator ==(final Object other) => - identical(this, other) || - other is EngineSplunkConfig && - runtimeType == other.runtimeType && - enabled == other.enabled && - endpoint == other.endpoint && - token == other.token && - source == other.source && - sourcetype == other.sourcetype && - index == other.index; - - @override - int get hashCode => - enabled.hashCode ^ endpoint.hashCode ^ token.hashCode ^ source.hashCode ^ sourcetype.hashCode ^ index.hashCode; } diff --git a/lib/src/models/engine_analytics_model.dart b/lib/src/models/engine_analytics_model.dart index 52ca64f..5015e41 100644 --- a/lib/src/models/engine_analytics_model.dart +++ b/lib/src/models/engine_analytics_model.dart @@ -1,3 +1,4 @@ +import 'package:engine_tracking/src/config/engine_clarity_config.dart'; import 'package:engine_tracking/src/config/engine_faro_config.dart'; import 'package:engine_tracking/src/config/engine_firebase_analytics_config.dart'; import 'package:engine_tracking/src/config/engine_google_logging_config.dart'; @@ -5,12 +6,14 @@ import 'package:engine_tracking/src/config/engine_splunk_config.dart'; class EngineAnalyticsModel { EngineAnalyticsModel({ + required this.clarityConfig, required this.firebaseAnalyticsConfig, required this.faroConfig, required this.googleLoggingConfig, required this.splunkConfig, }); + final EngineClarityConfig clarityConfig; final EngineFirebaseAnalyticsConfig firebaseAnalyticsConfig; final EngineFaroConfig faroConfig; final EngineGoogleLoggingConfig googleLoggingConfig; @@ -18,10 +21,13 @@ class EngineAnalyticsModel { @override String toString() => - 'EngineAnalyticsModel(firebaseAnalyticsConfig: $firebaseAnalyticsConfig, faroConfig: $faroConfig, googleLoggingConfig: $googleLoggingConfig, splunkConfig: $splunkConfig)'; + 'EngineAnalyticsModel(clarityConfig: $clarityConfig, firebaseAnalyticsConfig: $firebaseAnalyticsConfig, faroConfig: $faroConfig, googleLoggingConfig: $googleLoggingConfig, splunkConfig: $splunkConfig)'; } class EngineAnalyticsModelDefault implements EngineAnalyticsModel { + @override + EngineClarityConfig get clarityConfig => const EngineClarityConfig(enabled: false, projectId: ''); + @override EngineFirebaseAnalyticsConfig get firebaseAnalyticsConfig => const EngineFirebaseAnalyticsConfig(enabled: false); diff --git a/lib/src/session/engine_session.dart b/lib/src/session/engine_session.dart index 1f7ef37..d4dba39 100644 --- a/lib/src/session/engine_session.dart +++ b/lib/src/session/engine_session.dart @@ -8,30 +8,21 @@ class EngineSession { String? _sessionId; - // Getter público - único ponto de acesso String get sessionId => _sessionId ??= _generateUUID(); - // Gerador de string hexadecimal privado String _generateHex(final int length) { final random = Random(); const chars = '0123456789abcdef'; return List.generate(length, (final index) => chars[random.nextInt(chars.length)]).join(); } - // Gerador UUID v4 padrão String _generateUUID() { final random = Random(); - // UUID v4 formato: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - // Onde: - // - 13º caractere é sempre "4" (versão) - // - 17º caractere é sempre "8", "9", "a" ou "b" (variant) - final part1 = _generateHex(8); final part2 = _generateHex(4); - final part3 = '4${_generateHex(3)}'; // Versão 4 + final part3 = '4${_generateHex(3)}'; - // Para a parte 4, primeiro caractere deve ser 8, 9, a ou b const variantChars = '89ab'; final variantChar = variantChars[random.nextInt(variantChars.length)]; final part4 = '$variantChar${_generateHex(3)}'; @@ -41,14 +32,12 @@ class EngineSession { return '$part1-$part2-$part3-$part4-$part5'; } - // Método para enriquecer eventos automaticamente Map? enrichWithSessionId(final Map? data) { final enriched = {...?data}; enriched['session_id'] = sessionId; return enriched; } - // Método para reset - usado apenas em testes static void resetForTesting() { _instance = null; } diff --git a/lib/src/widgets/engine_mask_widget.dart b/lib/src/widgets/engine_mask_widget.dart new file mode 100644 index 0000000..a5d3267 --- /dev/null +++ b/lib/src/widgets/engine_mask_widget.dart @@ -0,0 +1,59 @@ +import 'package:clarity_flutter/clarity_flutter.dart'; +import 'package:flutter/material.dart'; + +/// Widget to mask sensitive content in recordings +/// +/// Use this widget to wrap elements that contain sensitive information +/// that should not appear in recorded sessions. +/// +/// Example: +/// ```dart +/// EngineMaskWidget( +/// child: Text('Sensitive information'), +/// ) +/// ``` +class EngineMaskWidget extends StatelessWidget { + const EngineMaskWidget({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(final BuildContext context) => ClarityMask( + child: child, + ); +} + +/// Widget to unmask content within a masked area +/// +/// Use this widget when you need to show specific content +/// within an area that has been masked with EngineMaskWidget. +/// +/// Example: +/// ```dart +/// EngineMaskWidget( +/// child: Column( +/// children: [ +/// Text('Sensitive information'), +/// EngineUnmaskWidget( +/// child: Text('Non-sensitive information'), +/// ), +/// ], +/// ), +/// ) +/// ``` +class EngineUnmaskWidget extends StatelessWidget { + const EngineUnmaskWidget({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(final BuildContext context) => ClarityUnmask( + child: child, + ); +} diff --git a/lib/src/widgets/engine_stateful_widget.dart b/lib/src/widgets/engine_stateful_widget.dart index 342d8c1..22285f1 100644 --- a/lib/src/widgets/engine_stateful_widget.dart +++ b/lib/src/widgets/engine_stateful_widget.dart @@ -3,31 +3,6 @@ import 'dart:async'; import 'package:engine_tracking/engine_tracking.dart'; import 'package:flutter/widgets.dart'; -/// class for StatefulWidget with automatic tracking functionalities -/// -/// Usage: -/// ```dart -/// class MinhaTelaPage extends EngineStatefulWidget { -/// const MinhaTelaPage({super.key}); -/// -/// @override -/// EngineStatefulWidgetState createState() => _MinhaTelaPageState(); -/// } -/// -/// class _MinhaTelaPageState extends EngineStatefulWidgetState { -/// @override -/// Widget buildWithTracking(BuildContext context) { -/// return Scaffold( -/// body: Center( -/// child: ElevatedButton( -/// onPressed: () => logUserAction('botao_clicado'), -/// child: Text('Clique aqui'), -/// ), -/// ), -/// ); -/// } -/// } -/// ``` abstract class EngineStatefulWidget extends StatefulWidget { const EngineStatefulWidget({super.key}); @@ -35,22 +10,17 @@ abstract class EngineStatefulWidget extends StatefulWidget { EngineStatefulWidgetState createState(); } -/// state for StatefulWidget with automatic tracking functionalities abstract class EngineStatefulWidgetState extends State { EngineStatefulWidgetState() { _screenOpenTime = DateTime.now(); } - /// Screen name for tracking. By default uses the class name String get screenName => runtimeType.toString(); - /// Additional parameters to send in screen tracking Map? get screenParameters => null; - /// Whether to automatically track screen views bool get enableAutoTracking => true; - /// Whether to automatically track lifecycle events bool get enableLifecycleTracking => true; late final DateTime _screenOpenTime; @@ -72,7 +42,6 @@ abstract class EngineStatefulWidgetState extends } if (enableAutoTracking) { - // Execute screen tracking after the first frame WidgetsBinding.instance.addPostFrameCallback((_) async { await _trackScreenView(); }); @@ -98,14 +67,11 @@ abstract class EngineStatefulWidgetState extends super.dispose(); } - /// Builds the widget with automatic tracking @override Widget build(final BuildContext context) => buildWithTracking(context); - /// Method that should be implemented instead of the original build Widget buildWithTracking(final BuildContext context); - /// Tracks the screen view Future _trackScreenView() async { await EngineLog.debug( 'screen_viewed', @@ -117,7 +83,6 @@ abstract class EngineStatefulWidgetState extends ); } - /// Logs a user action on the current screen Future logUserAction( final String action, { final Map? parameters, @@ -135,7 +100,6 @@ abstract class EngineStatefulWidgetState extends ); } - /// Logs a custom event on the current screen Future logCustomEvent( final String eventName, { final Map? parameters, @@ -153,7 +117,6 @@ abstract class EngineStatefulWidgetState extends ); } - /// Logs a screen-specific error Future logScreenError( final String reason, { final Object? exception, @@ -174,7 +137,6 @@ abstract class EngineStatefulWidgetState extends ); } - /// Logs a state change on the screen Future logStateChange( final String stateDescription, { final Map? additionalData, diff --git a/lib/src/widgets/engine_widget.dart b/lib/src/widgets/engine_widget.dart new file mode 100644 index 0000000..665df35 --- /dev/null +++ b/lib/src/widgets/engine_widget.dart @@ -0,0 +1,29 @@ +import 'package:clarity_flutter/clarity_flutter.dart'; +import 'package:engine_tracking/src/config/engine_clarity_config.dart'; +import 'package:flutter/material.dart'; + +class EngineWidget extends StatelessWidget { + const EngineWidget({ + required this.app, + required this.clarityConfig, + super.key, + }); + + final Widget app; + final EngineClarityConfig clarityConfig; + + @override + Widget build(final BuildContext context) { + if (clarityConfig.enabled) { + return ClarityWidget( + app: app, + clarityConfig: ClarityConfig( + projectId: clarityConfig.projectId, + userId: clarityConfig.userId, + logLevel: LogLevel.Verbose, + ), + ); + } + return app; + } +} diff --git a/lib/src/widgets/widgets.dart b/lib/src/widgets/widgets.dart index 037b665..2f9d110 100644 --- a/lib/src/widgets/widgets.dart +++ b/lib/src/widgets/widgets.dart @@ -1,2 +1,4 @@ +export 'engine_mask_widget.dart'; export 'engine_stateful_widget.dart'; export 'engine_stateless_widget.dart'; +export 'engine_widget.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index e16a241..b5e3a5a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: engine_tracking description: Plugin Flutter para tracking, analytics, crashlytics e logs do Engine Framework -version: 1.3.0 +version: 1.4.0 homepage: https://stmr.tech repository: https://github.com/moreirawebmaster/engine-tracking issue_tracker: https://github.com/moreirawebmaster/engine-tracking/issues @@ -16,6 +16,10 @@ topics: - crashlytics - logging +screenshots: + - description: "Engine Tracking Logo" + path: assets/images/logo.png + platforms: android: ios: @@ -33,6 +37,7 @@ dependencies: faro: ^0.3.6 googleapis: ^14.0.0 googleapis_auth: ^2.0.0 + clarity_flutter: ^1.0.0 dev_dependencies: flutter_test: diff --git a/test/analytics/engine_analytics_test.dart b/test/analytics/engine_analytics_test.dart index 10257a1..d3c3be5 100644 --- a/test/analytics/engine_analytics_test.dart +++ b/test/analytics/engine_analytics_test.dart @@ -66,6 +66,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -96,6 +100,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -125,6 +133,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -154,6 +166,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -183,6 +199,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -212,6 +232,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -241,6 +265,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -271,6 +299,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); @@ -300,6 +332,10 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); await EngineAnalytics.initWithModel(model); diff --git a/test/bug_tracking/engine_bug_tracking_test.dart b/test/bug_tracking/engine_bug_tracking_test.dart index ce75454..203391b 100644 --- a/test/bug_tracking/engine_bug_tracking_test.dart +++ b/test/bug_tracking/engine_bug_tracking_test.dart @@ -6,9 +6,7 @@ import '../helpers/test_configs.dart'; void main() { group('EngineBugTracking', () { - setUp(() { - EngineBugTracking.reset(); - }); + setUp(EngineBugTracking.reset); group('Initialization', () { test('should reset properly', () { diff --git a/test/models/engine_analytics_model_test.dart b/test/models/engine_analytics_model_test.dart index 848e3c7..80dcdaa 100644 --- a/test/models/engine_analytics_model_test.dart +++ b/test/models/engine_analytics_model_test.dart @@ -25,18 +25,24 @@ void main() { sourcetype: 'json', index: 'main', ); + const clarityConfig = EngineClarityConfig( + enabled: false, + projectId: '', + ); final model = EngineAnalyticsModel( firebaseAnalyticsConfig: firebaseConfig, faroConfig: faroConfig, googleLoggingConfig: TestConfigs.googleLoggingConfigEnabled, splunkConfig: splunkConfig, + clarityConfig: clarityConfig, ); expect(model.firebaseAnalyticsConfig, equals(firebaseConfig)); expect(model.faroConfig, equals(faroConfig)); expect(model.googleLoggingConfig, equals(TestConfigs.googleLoggingConfigEnabled)); expect(model.splunkConfig, equals(splunkConfig)); + expect(model.clarityConfig, equals(clarityConfig)); }); test('should not be equal to different types', () { @@ -65,6 +71,10 @@ void main() { faroConfig: faroConfig, googleLoggingConfig: TestConfigs.googleLoggingConfig, splunkConfig: splunkConfig, + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); expect(model, isNot(equals('string'))); @@ -98,6 +108,10 @@ void main() { faroConfig: faroConfig, googleLoggingConfig: TestConfigs.googleLoggingConfigEnabled, splunkConfig: splunkConfig, + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ); final stringRepresentation = model.toString(); @@ -106,6 +120,7 @@ void main() { expect(stringRepresentation, contains('faroConfig')); expect(stringRepresentation, contains('googleLoggingConfig')); expect(stringRepresentation, contains('splunkConfig')); + expect(stringRepresentation, contains('clarityConfig')); }); }); diff --git a/test/session/engine_session_test.dart b/test/session/engine_session_test.dart index 7e29e2e..0aa9406 100644 --- a/test/session/engine_session_test.dart +++ b/test/session/engine_session_test.dart @@ -3,10 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('EngineSession', () { - setUp(() { - // Reset instance para cada teste usando método público - EngineSession.resetForTesting(); - }); + setUp(EngineSession.resetForTesting); test('should generate unique session IDs', () { final session1 = EngineSession.instance.sessionId; diff --git a/test/test_coverage.dart b/test/test_coverage.dart index ac1b1ec..7a44116 100644 --- a/test/test_coverage.dart +++ b/test/test_coverage.dart @@ -66,10 +66,14 @@ void main() { sourcetype: '', index: '', ), + clarityConfig: const EngineClarityConfig( + enabled: false, + projectId: '', + ), ), returnsNormally, ); - expect(() => EngineAnalyticsModelDefault(), returnsNormally); + expect(EngineAnalyticsModelDefault.new, returnsNormally); // Bug Tracking expect(() => const EngineCrashlyticsConfig(enabled: true), returnsNormally); @@ -90,7 +94,7 @@ void main() { ), returnsNormally, ); - expect(() => EngineBugTrackingModelDefault(), returnsNormally); + expect(EngineBugTrackingModelDefault.new, returnsNormally); }); test('should have static methods available', () {