1239 lines
33 KiB
Markdown
1239 lines
33 KiB
Markdown
|
|
# Guia de Implementação - Sincronização Offline
|
||
|
|
|
||
|
|
## Visão Geral
|
||
|
|
|
||
|
|
Este guia fornece instruções passo a passo para implementar a sincronização offline no aplicativo de entregas, baseado na análise completa da arquitetura existente.
|
||
|
|
|
||
|
|
## Fase 1: Preparação e Configuração
|
||
|
|
|
||
|
|
### 1.1 Instalação de Dependências
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Dependências para sincronização
|
||
|
|
npm install lz-string crypto-js
|
||
|
|
npm install @react-native-async-storage/async-storage
|
||
|
|
npm install expo-background-fetch expo-task-manager
|
||
|
|
|
||
|
|
# Dependências para desenvolvimento
|
||
|
|
npm install --save-dev @types/lz-string @types/crypto-js
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.2 Configuração de Variáveis de Ambiente
|
||
|
|
|
||
|
|
**Arquivo**: `.env`
|
||
|
|
|
||
|
|
```env
|
||
|
|
# Configurações de sincronização
|
||
|
|
SYNC_INTERVAL=900000
|
||
|
|
SYNC_RETRY_ATTEMPTS=3
|
||
|
|
SYNC_TIMEOUT=30000
|
||
|
|
MAX_CACHE_SIZE=100
|
||
|
|
ENCRYPTION_KEY=your-secret-encryption-key
|
||
|
|
|
||
|
|
# Configurações de API
|
||
|
|
API_BASE_URL=https://api.example.com
|
||
|
|
API_TIMEOUT=10000
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.3 Atualização do Banco de Dados
|
||
|
|
|
||
|
|
**Arquivo**: `src/services/database.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Adicionar ao setupDatabase
|
||
|
|
export const setupDatabase = async (): Promise<void> => {
|
||
|
|
if (usingSQLite) {
|
||
|
|
return new Promise<void>((resolve, reject) => {
|
||
|
|
db.transaction(
|
||
|
|
(tx: any) => {
|
||
|
|
// Tabelas existentes...
|
||
|
|
|
||
|
|
// Nova tabela de controle de sincronização
|
||
|
|
tx.executeSql(
|
||
|
|
`CREATE TABLE IF NOT EXISTS sync_control (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
table_name TEXT NOT NULL,
|
||
|
|
last_sync_timestamp INTEGER,
|
||
|
|
sync_status TEXT DEFAULT 'pending',
|
||
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||
|
|
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
|
||
|
|
);`
|
||
|
|
);
|
||
|
|
|
||
|
|
// Nova tabela de conflitos
|
||
|
|
tx.executeSql(
|
||
|
|
`CREATE TABLE IF NOT EXISTS sync_conflicts (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
table_name TEXT NOT NULL,
|
||
|
|
record_id TEXT NOT NULL,
|
||
|
|
local_data TEXT,
|
||
|
|
server_data TEXT,
|
||
|
|
conflict_fields TEXT,
|
||
|
|
resolution TEXT,
|
||
|
|
resolved_at INTEGER,
|
||
|
|
created_at INTEGER DEFAULT (strftime('%s', 'now'))
|
||
|
|
);`
|
||
|
|
);
|
||
|
|
|
||
|
|
// Nova tabela de log de sincronização
|
||
|
|
tx.executeSql(
|
||
|
|
`CREATE TABLE IF NOT EXISTS sync_log (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
sync_type TEXT NOT NULL,
|
||
|
|
table_name TEXT,
|
||
|
|
record_id TEXT,
|
||
|
|
action TEXT,
|
||
|
|
success INTEGER DEFAULT 1,
|
||
|
|
error_message TEXT,
|
||
|
|
duration INTEGER,
|
||
|
|
timestamp INTEGER DEFAULT (strftime('%s', 'now'))
|
||
|
|
);`
|
||
|
|
);
|
||
|
|
|
||
|
|
// Adicionar campos de controle às tabelas existentes
|
||
|
|
tx.executeSql(
|
||
|
|
`ALTER TABLE deliveries ADD COLUMN version INTEGER DEFAULT 1;`
|
||
|
|
);
|
||
|
|
tx.executeSql(
|
||
|
|
`ALTER TABLE deliveries ADD COLUMN last_modified INTEGER DEFAULT (strftime('%s', 'now'));`
|
||
|
|
);
|
||
|
|
tx.executeSql(
|
||
|
|
`ALTER TABLE deliveries ADD COLUMN sync_timestamp INTEGER;`
|
||
|
|
);
|
||
|
|
|
||
|
|
// Inserir configurações de sincronização
|
||
|
|
tx.executeSql(
|
||
|
|
`INSERT OR IGNORE INTO settings (key, value) VALUES
|
||
|
|
('initial_sync_complete', 'false'),
|
||
|
|
('last_sync_time', '0'),
|
||
|
|
('sync_interval', '900000'),
|
||
|
|
('auto_sync_enabled', 'true');`
|
||
|
|
);
|
||
|
|
},
|
||
|
|
(error: any) => reject(error),
|
||
|
|
() => resolve()
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
## Fase 2: Implementação dos Serviços Base
|
||
|
|
|
||
|
|
### 2.1 Serviço de Sincronização Principal
|
||
|
|
|
||
|
|
**Arquivo**: `src/services/syncService.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { api } from './api';
|
||
|
|
import { executeQuery, saveSetting, getSetting } from './database';
|
||
|
|
import { offlineStorage } from './offlineStorage';
|
||
|
|
|
||
|
|
export enum SyncStatus {
|
||
|
|
SYNCED = 'synced',
|
||
|
|
PENDING = 'pending',
|
||
|
|
CONFLICT = 'conflict',
|
||
|
|
ERROR = 'error',
|
||
|
|
OFFLINE = 'offline'
|
||
|
|
}
|
||
|
|
|
||
|
|
export enum SyncType {
|
||
|
|
FULL = 'full',
|
||
|
|
INCREMENTAL = 'incremental',
|
||
|
|
SELECTIVE = 'selective'
|
||
|
|
}
|
||
|
|
|
||
|
|
interface SyncResult {
|
||
|
|
success: boolean;
|
||
|
|
totalRecords: number;
|
||
|
|
syncedRecords: number;
|
||
|
|
errors: Array<{ recordId: string; error: string }>;
|
||
|
|
duration: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
class SyncService {
|
||
|
|
private isInitialized = false;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.initializeSyncService();
|
||
|
|
}
|
||
|
|
|
||
|
|
private async initializeSyncService(): Promise<void> {
|
||
|
|
try {
|
||
|
|
// Verificar se já foi feita sincronização inicial
|
||
|
|
const initialSyncComplete = await getSetting('initial_sync_complete');
|
||
|
|
|
||
|
|
if (initialSyncComplete === 'false') {
|
||
|
|
console.log('Sincronização inicial necessária');
|
||
|
|
// Não iniciar automaticamente - aguardar ação do usuário
|
||
|
|
} else {
|
||
|
|
console.log('Sincronização inicial já foi realizada');
|
||
|
|
this.isInitialized = true;
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao inicializar serviço de sincronização:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sincronização inicial completa
|
||
|
|
async performInitialSync(): Promise<SyncResult> {
|
||
|
|
try {
|
||
|
|
console.log('=== INICIANDO SINCRONIZAÇÃO INICIAL ===');
|
||
|
|
|
||
|
|
const syncResult: SyncResult = {
|
||
|
|
success: true,
|
||
|
|
totalRecords: 0,
|
||
|
|
syncedRecords: 0,
|
||
|
|
errors: [],
|
||
|
|
duration: 0
|
||
|
|
};
|
||
|
|
|
||
|
|
const startTime = Date.now();
|
||
|
|
|
||
|
|
// 1. Verificar conectividade
|
||
|
|
const isOnline = await this.checkConnectivity();
|
||
|
|
if (!isOnline) {
|
||
|
|
throw new Error('Sem conexão com a internet');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Sincronizar dados de usuário
|
||
|
|
await this.syncUserData();
|
||
|
|
syncResult.syncedRecords += 1;
|
||
|
|
|
||
|
|
// 3. Sincronizar entregas
|
||
|
|
const deliveriesResult = await this.syncDeliveries();
|
||
|
|
syncResult.syncedRecords += deliveriesResult.count;
|
||
|
|
|
||
|
|
// 4. Sincronizar configurações
|
||
|
|
await this.syncSettings();
|
||
|
|
syncResult.syncedRecords += 1;
|
||
|
|
|
||
|
|
// 5. Marcar sincronização como completa
|
||
|
|
await saveSetting('initial_sync_complete', 'true');
|
||
|
|
await saveSetting('last_sync_time', Date.now().toString());
|
||
|
|
|
||
|
|
syncResult.duration = Date.now() - startTime;
|
||
|
|
syncResult.totalRecords = syncResult.syncedRecords;
|
||
|
|
|
||
|
|
this.isInitialized = true;
|
||
|
|
|
||
|
|
console.log('=== SINCRONIZAÇÃO INICIAL CONCLUÍDA ===');
|
||
|
|
return syncResult;
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro na sincronização inicial:', error);
|
||
|
|
throw new Error(`Falha na sincronização inicial: ${error.message}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sincronização incremental
|
||
|
|
async performIncrementalSync(): Promise<SyncResult> {
|
||
|
|
try {
|
||
|
|
console.log('=== INICIANDO SINCRONIZAÇÃO INCREMENTAL ===');
|
||
|
|
|
||
|
|
const lastSyncTime = await getSetting('last_sync_time');
|
||
|
|
const syncResult: SyncResult = {
|
||
|
|
success: true,
|
||
|
|
totalRecords: 0,
|
||
|
|
syncedRecords: 0,
|
||
|
|
errors: [],
|
||
|
|
duration: 0
|
||
|
|
};
|
||
|
|
|
||
|
|
const startTime = Date.now();
|
||
|
|
|
||
|
|
// 1. Verificar conectividade
|
||
|
|
const isOnline = await this.checkConnectivity();
|
||
|
|
if (!isOnline) {
|
||
|
|
throw new Error('Sem conexão com a internet');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Buscar mudanças do servidor
|
||
|
|
const serverChanges = await this.getServerChangesSince(parseInt(lastSyncTime || '0'));
|
||
|
|
|
||
|
|
// 3. Buscar mudanças locais não sincronizadas
|
||
|
|
const localChanges = await this.getLocalUnsyncedChanges();
|
||
|
|
|
||
|
|
// 4. Aplicar mudanças do servidor
|
||
|
|
await this.applyServerChanges(serverChanges);
|
||
|
|
syncResult.syncedRecords += serverChanges.length;
|
||
|
|
|
||
|
|
// 5. Enviar mudanças locais
|
||
|
|
await this.sendLocalChanges(localChanges);
|
||
|
|
syncResult.syncedRecords += localChanges.length;
|
||
|
|
|
||
|
|
// 6. Atualizar timestamp de sincronização
|
||
|
|
await saveSetting('last_sync_time', Date.now().toString());
|
||
|
|
|
||
|
|
syncResult.duration = Date.now() - startTime;
|
||
|
|
syncResult.totalRecords = syncResult.syncedRecords;
|
||
|
|
|
||
|
|
console.log('=== SINCRONIZAÇÃO INCREMENTAL CONCLUÍDA ===');
|
||
|
|
return syncResult;
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro na sincronização incremental:', error);
|
||
|
|
throw new Error(`Falha na sincronização incremental: ${error.message}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Métodos auxiliares
|
||
|
|
private async checkConnectivity(): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
// Implementar verificação de conectividade
|
||
|
|
const response = await fetch('https://www.google.com', {
|
||
|
|
method: 'HEAD',
|
||
|
|
timeout: 5000
|
||
|
|
});
|
||
|
|
return response.ok;
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async syncUserData(): Promise<void> {
|
||
|
|
// Implementar sincronização de dados do usuário
|
||
|
|
console.log('Sincronizando dados do usuário...');
|
||
|
|
}
|
||
|
|
|
||
|
|
private async syncDeliveries(): Promise<{ count: number }> {
|
||
|
|
try {
|
||
|
|
const deliveries = await api.getDeliveries();
|
||
|
|
let count = 0;
|
||
|
|
|
||
|
|
for (const delivery of deliveries) {
|
||
|
|
await this.saveDeliveryLocally(delivery);
|
||
|
|
count += 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return { count };
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao sincronizar entregas:', error);
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async syncSettings(): Promise<void> {
|
||
|
|
// Implementar sincronização de configurações
|
||
|
|
console.log('Sincronizando configurações...');
|
||
|
|
}
|
||
|
|
|
||
|
|
private async saveDeliveryLocally(delivery: any): Promise<void> {
|
||
|
|
try {
|
||
|
|
await executeQuery(
|
||
|
|
`INSERT OR REPLACE INTO deliveries (
|
||
|
|
id, outId, customerName, street, streetNumber, neighborhood,
|
||
|
|
city, state, zipCode, customerPhone, lat, lng, deliverySeq,
|
||
|
|
routing, status, outDate, notes, version, last_modified, sync_timestamp
|
||
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
|
|
[
|
||
|
|
delivery.id, delivery.outId, delivery.customerName,
|
||
|
|
delivery.street, delivery.streetNumber, delivery.neighborhood,
|
||
|
|
delivery.city, delivery.state, delivery.zipCode,
|
||
|
|
delivery.customerPhone, delivery.lat, delivery.lng,
|
||
|
|
delivery.deliverySeq, delivery.routing, delivery.status,
|
||
|
|
delivery.outDate, delivery.notes, 1, Date.now(), Date.now()
|
||
|
|
]
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao salvar entrega localmente:', error);
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async getServerChangesSince(timestamp: number): Promise<any[]> {
|
||
|
|
// Implementar busca de mudanças do servidor
|
||
|
|
// Por enquanto, retornar array vazio
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
private async getLocalUnsyncedChanges(): Promise<any[]> {
|
||
|
|
try {
|
||
|
|
const result = await executeQuery(
|
||
|
|
"SELECT * FROM deliveries WHERE sync_timestamp IS NULL OR sync_timestamp < last_modified"
|
||
|
|
);
|
||
|
|
return result.rows._array;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao buscar mudanças locais:', error);
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async applyServerChanges(changes: any[]): Promise<void> {
|
||
|
|
for (const change of changes) {
|
||
|
|
await this.saveDeliveryLocally(change);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async sendLocalChanges(changes: any[]): Promise<void> {
|
||
|
|
for (const change of changes) {
|
||
|
|
try {
|
||
|
|
await api.createDelivery(change);
|
||
|
|
// Marcar como sincronizado
|
||
|
|
await executeQuery(
|
||
|
|
"UPDATE deliveries SET sync_timestamp = ? WHERE id = ?",
|
||
|
|
[Date.now(), change.id]
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao enviar mudança local:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verificar se sincronização inicial foi realizada
|
||
|
|
async isInitialSyncComplete(): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
const result = await getSetting('initial_sync_complete');
|
||
|
|
return result === 'true';
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao verificar sincronização inicial:', error);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Obter estatísticas de sincronização
|
||
|
|
async getSyncStats(): Promise<any> {
|
||
|
|
try {
|
||
|
|
const totalResult = await executeQuery("SELECT COUNT(*) as count FROM deliveries");
|
||
|
|
const syncedResult = await executeQuery(
|
||
|
|
"SELECT COUNT(*) as count FROM deliveries WHERE sync_timestamp IS NOT NULL"
|
||
|
|
);
|
||
|
|
const pendingResult = await executeQuery(
|
||
|
|
"SELECT COUNT(*) as count FROM deliveries WHERE sync_timestamp IS NULL"
|
||
|
|
);
|
||
|
|
|
||
|
|
return {
|
||
|
|
totalRecords: totalResult.rows._array[0].count,
|
||
|
|
syncedRecords: syncedResult.rows._array[0].count,
|
||
|
|
pendingRecords: pendingResult.rows._array[0].count,
|
||
|
|
lastSyncTime: await getSetting('last_sync_time')
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao obter estatísticas de sincronização:', error);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const syncService = new SyncService();
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 Contexto de Sincronização Inicial
|
||
|
|
|
||
|
|
**Arquivo**: `src/contexts/InitialSyncContext.tsx`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||
|
|
import { syncService, SyncResult } from '../services/syncService';
|
||
|
|
|
||
|
|
interface InitialSyncContextData {
|
||
|
|
// Estados
|
||
|
|
isInitialSyncComplete: boolean;
|
||
|
|
syncProgress: number;
|
||
|
|
syncStatus: string;
|
||
|
|
lastSyncTime: number | null;
|
||
|
|
pendingChanges: number;
|
||
|
|
isLoading: boolean;
|
||
|
|
error: string | null;
|
||
|
|
|
||
|
|
// Métodos
|
||
|
|
startInitialSync: () => Promise<void>;
|
||
|
|
retryInitialSync: () => Promise<void>;
|
||
|
|
performIncrementalSync: () => Promise<void>;
|
||
|
|
getSyncStats: () => Promise<any>;
|
||
|
|
}
|
||
|
|
|
||
|
|
const InitialSyncContext = createContext<InitialSyncContextData>({} as InitialSyncContextData);
|
||
|
|
|
||
|
|
export const useInitialSync = () => {
|
||
|
|
const context = useContext(InitialSyncContext);
|
||
|
|
if (!context) {
|
||
|
|
throw new Error('useInitialSync deve ser usado dentro de um InitialSyncProvider');
|
||
|
|
}
|
||
|
|
return context;
|
||
|
|
};
|
||
|
|
|
||
|
|
export const InitialSyncProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||
|
|
const [isInitialSyncComplete, setIsInitialSyncComplete] = useState(false);
|
||
|
|
const [syncProgress, setSyncProgress] = useState(0);
|
||
|
|
const [syncStatus, setSyncStatus] = useState('idle');
|
||
|
|
const [lastSyncTime, setLastSyncTime] = useState<number | null>(null);
|
||
|
|
const [pendingChanges, setPendingChanges] = useState(0);
|
||
|
|
const [isLoading, setIsLoading] = useState(false);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
// Verificar status inicial
|
||
|
|
useEffect(() => {
|
||
|
|
checkInitialSyncStatus();
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const checkInitialSyncStatus = async () => {
|
||
|
|
try {
|
||
|
|
const isComplete = await syncService.isInitialSyncComplete();
|
||
|
|
setIsInitialSyncComplete(isComplete);
|
||
|
|
|
||
|
|
if (isComplete) {
|
||
|
|
const stats = await syncService.getSyncStats();
|
||
|
|
if (stats) {
|
||
|
|
setLastSyncTime(parseInt(stats.lastSyncTime || '0'));
|
||
|
|
setPendingChanges(stats.pendingRecords);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao verificar status de sincronização:', error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const startInitialSync = async () => {
|
||
|
|
setIsLoading(true);
|
||
|
|
setError(null);
|
||
|
|
setSyncStatus('starting');
|
||
|
|
setSyncProgress(0);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Simular progresso
|
||
|
|
const progressInterval = setInterval(() => {
|
||
|
|
setSyncProgress(prev => {
|
||
|
|
if (prev >= 90) {
|
||
|
|
clearInterval(progressInterval);
|
||
|
|
return prev;
|
||
|
|
}
|
||
|
|
return prev + 10;
|
||
|
|
});
|
||
|
|
}, 200);
|
||
|
|
|
||
|
|
const result = await syncService.performInitialSync();
|
||
|
|
|
||
|
|
clearInterval(progressInterval);
|
||
|
|
setSyncProgress(100);
|
||
|
|
setSyncStatus('completed');
|
||
|
|
setIsInitialSyncComplete(true);
|
||
|
|
setLastSyncTime(Date.now());
|
||
|
|
setPendingChanges(0);
|
||
|
|
|
||
|
|
console.log('Sincronização inicial concluída:', result);
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro na sincronização inicial:', error);
|
||
|
|
setError(error.message);
|
||
|
|
setSyncStatus('error');
|
||
|
|
} finally {
|
||
|
|
setIsLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const retryInitialSync = async () => {
|
||
|
|
setError(null);
|
||
|
|
await startInitialSync();
|
||
|
|
};
|
||
|
|
|
||
|
|
const performIncrementalSync = async () => {
|
||
|
|
setIsLoading(true);
|
||
|
|
setError(null);
|
||
|
|
setSyncStatus('syncing');
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await syncService.performIncrementalSync();
|
||
|
|
setSyncStatus('completed');
|
||
|
|
setLastSyncTime(Date.now());
|
||
|
|
setPendingChanges(0);
|
||
|
|
|
||
|
|
console.log('Sincronização incremental concluída:', result);
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro na sincronização incremental:', error);
|
||
|
|
setError(error.message);
|
||
|
|
setSyncStatus('error');
|
||
|
|
} finally {
|
||
|
|
setIsLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getSyncStats = async () => {
|
||
|
|
try {
|
||
|
|
return await syncService.getSyncStats();
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro ao obter estatísticas:', error);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<InitialSyncContext.Provider
|
||
|
|
value={{
|
||
|
|
isInitialSyncComplete,
|
||
|
|
syncProgress,
|
||
|
|
syncStatus,
|
||
|
|
lastSyncTime,
|
||
|
|
pendingChanges,
|
||
|
|
isLoading,
|
||
|
|
error,
|
||
|
|
startInitialSync,
|
||
|
|
retryInitialSync,
|
||
|
|
performIncrementalSync,
|
||
|
|
getSyncStats,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{children}
|
||
|
|
</InitialSyncContext.Provider>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
## Fase 3: Implementação das Telas
|
||
|
|
|
||
|
|
### 3.1 Tela de Sincronização Inicial
|
||
|
|
|
||
|
|
**Arquivo**: `src/screens/sync/InitialSyncScreen.tsx`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import React, { useState } from 'react';
|
||
|
|
import {
|
||
|
|
View,
|
||
|
|
Text,
|
||
|
|
StyleSheet,
|
||
|
|
TouchableOpacity,
|
||
|
|
Alert,
|
||
|
|
ActivityIndicator,
|
||
|
|
} from 'react-native';
|
||
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
||
|
|
import { Ionicons } from '@expo/vector-icons';
|
||
|
|
import { useInitialSync } from '../../contexts/InitialSyncContext';
|
||
|
|
import { COLORS, SHADOWS } from '../../constants/theme';
|
||
|
|
|
||
|
|
const InitialSyncScreen: React.FC = () => {
|
||
|
|
const {
|
||
|
|
syncProgress,
|
||
|
|
syncStatus,
|
||
|
|
isLoading,
|
||
|
|
error,
|
||
|
|
startInitialSync,
|
||
|
|
retryInitialSync,
|
||
|
|
} = useInitialSync();
|
||
|
|
|
||
|
|
const [hasStarted, setHasStarted] = useState(false);
|
||
|
|
|
||
|
|
const handleStartSync = async () => {
|
||
|
|
setHasStarted(true);
|
||
|
|
try {
|
||
|
|
await startInitialSync();
|
||
|
|
// Navegação será feita pelo contexto pai
|
||
|
|
} catch (error) {
|
||
|
|
Alert.alert('Erro', 'Falha na sincronização inicial');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleRetry = async () => {
|
||
|
|
try {
|
||
|
|
await retryInitialSync();
|
||
|
|
} catch (error) {
|
||
|
|
Alert.alert('Erro', 'Falha ao tentar novamente');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getStatusText = (status: string) => {
|
||
|
|
switch (status) {
|
||
|
|
case 'starting': return 'Iniciando sincronização...';
|
||
|
|
case 'syncing': return 'Sincronizando dados...';
|
||
|
|
case 'completed': return 'Sincronização concluída!';
|
||
|
|
case 'error': return 'Erro na sincronização';
|
||
|
|
default: return 'Pronto para sincronizar';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getStatusColor = (status: string) => {
|
||
|
|
switch (status) {
|
||
|
|
case 'completed': return COLORS.success;
|
||
|
|
case 'error': return COLORS.danger;
|
||
|
|
default: return COLORS.primary;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<SafeAreaView style={styles.container}>
|
||
|
|
<LinearGradient
|
||
|
|
colors={[COLORS.primary, '#3B82F6']}
|
||
|
|
style={styles.header}
|
||
|
|
>
|
||
|
|
<View style={styles.headerContent}>
|
||
|
|
<Ionicons name="cloud-download" size={48} color="white" />
|
||
|
|
<Text style={styles.title}>Sincronização Inicial</Text>
|
||
|
|
<Text style={styles.subtitle}>
|
||
|
|
Baixando dados necessários para funcionamento offline
|
||
|
|
</Text>
|
||
|
|
</View>
|
||
|
|
</LinearGradient>
|
||
|
|
|
||
|
|
<View style={styles.content}>
|
||
|
|
<View style={styles.progressContainer}>
|
||
|
|
<View style={styles.progressBar}>
|
||
|
|
<View
|
||
|
|
style={[
|
||
|
|
styles.progressFill,
|
||
|
|
{ width: `${syncProgress}%` }
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
</View>
|
||
|
|
<Text style={styles.progressText}>
|
||
|
|
{Math.round(syncProgress)}% concluído
|
||
|
|
</Text>
|
||
|
|
</View>
|
||
|
|
|
||
|
|
<View style={styles.statusContainer}>
|
||
|
|
<Text style={[styles.statusText, { color: getStatusColor(syncStatus) }]}>
|
||
|
|
{getStatusText(syncStatus)}
|
||
|
|
</Text>
|
||
|
|
</View>
|
||
|
|
|
||
|
|
{error && (
|
||
|
|
<View style={styles.errorContainer}>
|
||
|
|
<Ionicons name="alert-circle" size={24} color={COLORS.danger} />
|
||
|
|
<Text style={styles.errorText}>{error}</Text>
|
||
|
|
</View>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<View style={styles.buttonContainer}>
|
||
|
|
{!hasStarted || syncStatus === 'error' ? (
|
||
|
|
<TouchableOpacity
|
||
|
|
style={[styles.syncButton, isLoading && styles.syncButtonDisabled]}
|
||
|
|
onPress={syncStatus === 'error' ? handleRetry : handleStartSync}
|
||
|
|
disabled={isLoading}
|
||
|
|
>
|
||
|
|
{isLoading ? (
|
||
|
|
<ActivityIndicator color="white" />
|
||
|
|
) : (
|
||
|
|
<Ionicons
|
||
|
|
name={syncStatus === 'error' ? 'refresh' : 'cloud-download'}
|
||
|
|
size={24}
|
||
|
|
color="white"
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
<Text style={styles.syncButtonText}>
|
||
|
|
{syncStatus === 'error' ? 'Tentar Novamente' : 'Iniciar Sincronização'}
|
||
|
|
</Text>
|
||
|
|
</TouchableOpacity>
|
||
|
|
) : (
|
||
|
|
<View style={styles.completedContainer}>
|
||
|
|
<Ionicons name="checkmark-circle" size={48} color={COLORS.success} />
|
||
|
|
<Text style={styles.completedText}>
|
||
|
|
Sincronização concluída com sucesso!
|
||
|
|
</Text>
|
||
|
|
</View>
|
||
|
|
)}
|
||
|
|
</View>
|
||
|
|
|
||
|
|
<View style={styles.infoContainer}>
|
||
|
|
<Text style={styles.infoText}>
|
||
|
|
• Dados de entregas serão baixados
|
||
|
|
</Text>
|
||
|
|
<Text style={styles.infoText}>
|
||
|
|
• Configurações serão sincronizadas
|
||
|
|
</Text>
|
||
|
|
<Text style={styles.infoText}>
|
||
|
|
• Aplicativo funcionará offline após sincronização
|
||
|
|
</Text>
|
||
|
|
</View>
|
||
|
|
</View>
|
||
|
|
</SafeAreaView>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
const styles = StyleSheet.create({
|
||
|
|
container: {
|
||
|
|
flex: 1,
|
||
|
|
backgroundColor: COLORS.background,
|
||
|
|
},
|
||
|
|
header: {
|
||
|
|
paddingVertical: 40,
|
||
|
|
paddingHorizontal: 20,
|
||
|
|
alignItems: 'center',
|
||
|
|
},
|
||
|
|
headerContent: {
|
||
|
|
alignItems: 'center',
|
||
|
|
},
|
||
|
|
title: {
|
||
|
|
fontSize: 24,
|
||
|
|
fontWeight: 'bold',
|
||
|
|
color: 'white',
|
||
|
|
marginTop: 16,
|
||
|
|
marginBottom: 8,
|
||
|
|
},
|
||
|
|
subtitle: {
|
||
|
|
fontSize: 16,
|
||
|
|
color: 'rgba(255, 255, 255, 0.8)',
|
||
|
|
textAlign: 'center',
|
||
|
|
},
|
||
|
|
content: {
|
||
|
|
flex: 1,
|
||
|
|
padding: 20,
|
||
|
|
},
|
||
|
|
progressContainer: {
|
||
|
|
marginBottom: 30,
|
||
|
|
},
|
||
|
|
progressBar: {
|
||
|
|
height: 8,
|
||
|
|
backgroundColor: COLORS.border,
|
||
|
|
borderRadius: 4,
|
||
|
|
overflow: 'hidden',
|
||
|
|
marginBottom: 8,
|
||
|
|
},
|
||
|
|
progressFill: {
|
||
|
|
height: '100%',
|
||
|
|
backgroundColor: COLORS.primary,
|
||
|
|
borderRadius: 4,
|
||
|
|
},
|
||
|
|
progressText: {
|
||
|
|
fontSize: 16,
|
||
|
|
fontWeight: '600',
|
||
|
|
color: COLORS.text,
|
||
|
|
textAlign: 'center',
|
||
|
|
},
|
||
|
|
statusContainer: {
|
||
|
|
alignItems: 'center',
|
||
|
|
marginBottom: 20,
|
||
|
|
},
|
||
|
|
statusText: {
|
||
|
|
fontSize: 18,
|
||
|
|
fontWeight: '600',
|
||
|
|
},
|
||
|
|
errorContainer: {
|
||
|
|
flexDirection: 'row',
|
||
|
|
alignItems: 'center',
|
||
|
|
backgroundColor: '#FEF2F2',
|
||
|
|
padding: 16,
|
||
|
|
borderRadius: 12,
|
||
|
|
marginBottom: 20,
|
||
|
|
},
|
||
|
|
errorText: {
|
||
|
|
fontSize: 14,
|
||
|
|
color: COLORS.danger,
|
||
|
|
marginLeft: 8,
|
||
|
|
flex: 1,
|
||
|
|
},
|
||
|
|
buttonContainer: {
|
||
|
|
marginBottom: 30,
|
||
|
|
},
|
||
|
|
syncButton: {
|
||
|
|
flexDirection: 'row',
|
||
|
|
alignItems: 'center',
|
||
|
|
justifyContent: 'center',
|
||
|
|
backgroundColor: COLORS.primary,
|
||
|
|
paddingVertical: 16,
|
||
|
|
paddingHorizontal: 24,
|
||
|
|
borderRadius: 12,
|
||
|
|
...SHADOWS.medium,
|
||
|
|
},
|
||
|
|
syncButtonDisabled: {
|
||
|
|
backgroundColor: COLORS.textLight,
|
||
|
|
},
|
||
|
|
syncButtonText: {
|
||
|
|
fontSize: 16,
|
||
|
|
fontWeight: '600',
|
||
|
|
color: 'white',
|
||
|
|
marginLeft: 8,
|
||
|
|
},
|
||
|
|
completedContainer: {
|
||
|
|
alignItems: 'center',
|
||
|
|
padding: 20,
|
||
|
|
},
|
||
|
|
completedText: {
|
||
|
|
fontSize: 18,
|
||
|
|
fontWeight: '600',
|
||
|
|
color: COLORS.success,
|
||
|
|
marginTop: 16,
|
||
|
|
textAlign: 'center',
|
||
|
|
},
|
||
|
|
infoContainer: {
|
||
|
|
backgroundColor: COLORS.secondary,
|
||
|
|
padding: 20,
|
||
|
|
borderRadius: 12,
|
||
|
|
},
|
||
|
|
infoText: {
|
||
|
|
fontSize: 14,
|
||
|
|
color: COLORS.textLight,
|
||
|
|
marginBottom: 8,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
export default InitialSyncScreen;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.2 Atualização da Navegação
|
||
|
|
|
||
|
|
**Arquivo**: `src/navigation/index.tsx`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Adicionar import
|
||
|
|
import InitialSyncScreen from '../screens/sync/InitialSyncScreen';
|
||
|
|
|
||
|
|
// Adicionar ao AuthNavigator
|
||
|
|
const AuthNavigator = () => {
|
||
|
|
return (
|
||
|
|
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||
|
|
<Stack.Screen name="Login" component={LoginScreen} />
|
||
|
|
<Stack.Screen name="InitialSync" component={InitialSyncScreen} />
|
||
|
|
</Stack.Navigator>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
// Atualizar Navigation component
|
||
|
|
const Navigation = () => {
|
||
|
|
const { user, isLoading } = useAuth();
|
||
|
|
const { isInitialSyncComplete } = useInitialSync();
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
console.log('=== DEBUG: NAVIGATION STATE ===');
|
||
|
|
console.log('isLoading:', isLoading);
|
||
|
|
console.log('user:', user ? 'Logado' : 'Não logado');
|
||
|
|
console.log('isInitialSyncComplete:', isInitialSyncComplete);
|
||
|
|
|
||
|
|
if (!isLoading && !user) {
|
||
|
|
console.log('Redirecionando para Auth...');
|
||
|
|
navigationRef.current?.reset({
|
||
|
|
index: 0,
|
||
|
|
routes: [{ name: 'Auth' }]
|
||
|
|
});
|
||
|
|
} else if (!isLoading && user && !isInitialSyncComplete) {
|
||
|
|
console.log('Redirecionando para InitialSync...');
|
||
|
|
navigationRef.current?.reset({
|
||
|
|
index: 0,
|
||
|
|
routes: [{ name: 'InitialSync' }]
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}, [user, isLoading, isInitialSyncComplete]);
|
||
|
|
|
||
|
|
if (isLoading) {
|
||
|
|
return (
|
||
|
|
<View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "#FFFFFF" }}>
|
||
|
|
<Text>Carregando...</Text>
|
||
|
|
</View>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||
|
|
{user ? (
|
||
|
|
<>
|
||
|
|
{!isInitialSyncComplete ? (
|
||
|
|
<Stack.Screen name="InitialSync" component={InitialSyncScreen} />
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
<Stack.Screen
|
||
|
|
name="Routing"
|
||
|
|
component={RoutingScreen}
|
||
|
|
options={{
|
||
|
|
headerShown: false,
|
||
|
|
presentation: 'modal'
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<Stack.Screen name="Main" component={TabNavigator} />
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</>
|
||
|
|
) : (
|
||
|
|
<Stack.Screen name="Auth" component={AuthNavigator} />
|
||
|
|
)}
|
||
|
|
</Stack.Navigator>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.3 Atualização do App.tsx
|
||
|
|
|
||
|
|
**Arquivo**: `App.tsx`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Adicionar import
|
||
|
|
import { InitialSyncProvider } from "./src/contexts/InitialSyncContext";
|
||
|
|
|
||
|
|
// Atualizar estrutura de providers
|
||
|
|
return (
|
||
|
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
||
|
|
<SafeAreaProvider onLayout={onLayoutRootView}>
|
||
|
|
<AuthProvider>
|
||
|
|
<InitialSyncProvider>
|
||
|
|
<SyncProvider>
|
||
|
|
<DeliveriesProvider>
|
||
|
|
<OfflineProvider>
|
||
|
|
<NavigationContainer ref={navigationRef}>
|
||
|
|
{Platform.OS === 'android' ? (
|
||
|
|
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
|
||
|
|
<Navigation />
|
||
|
|
</SafeAreaView>
|
||
|
|
) : (
|
||
|
|
<Navigation />
|
||
|
|
)}
|
||
|
|
<StatusBar style="light" backgroundColor={COLORS.primary} />
|
||
|
|
</NavigationContainer>
|
||
|
|
</OfflineProvider>
|
||
|
|
</DeliveriesProvider>
|
||
|
|
</SyncProvider>
|
||
|
|
</InitialSyncProvider>
|
||
|
|
</AuthProvider>
|
||
|
|
</SafeAreaProvider>
|
||
|
|
</GestureHandlerRootView>
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
## Fase 4: Implementação de Sincronização Incremental
|
||
|
|
|
||
|
|
### 4.1 Hook para Sincronização Automática
|
||
|
|
|
||
|
|
**Arquivo**: `src/hooks/useAutoSync.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { useEffect, useRef } from 'react';
|
||
|
|
import { AppState, AppStateStatus } from 'react-native';
|
||
|
|
import { useInitialSync } from '../contexts/InitialSyncContext';
|
||
|
|
import { useSync } from '../contexts/SyncContext';
|
||
|
|
|
||
|
|
export const useAutoSync = () => {
|
||
|
|
const { isInitialSyncComplete, performIncrementalSync } = useInitialSync();
|
||
|
|
const { isOnline } = useSync();
|
||
|
|
const appState = useRef(AppState.currentState);
|
||
|
|
const syncTimeoutRef = useRef<NodeJS.Timeout>();
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
||
|
|
if (
|
||
|
|
appState.current.match(/inactive|background/) &&
|
||
|
|
nextAppState === 'active' &&
|
||
|
|
isInitialSyncComplete &&
|
||
|
|
isOnline
|
||
|
|
) {
|
||
|
|
// App voltou ao foreground e está online
|
||
|
|
console.log('App ativo - verificando sincronização');
|
||
|
|
scheduleSync();
|
||
|
|
}
|
||
|
|
appState.current = nextAppState;
|
||
|
|
};
|
||
|
|
|
||
|
|
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
subscription?.remove();
|
||
|
|
if (syncTimeoutRef.current) {
|
||
|
|
clearTimeout(syncTimeoutRef.current);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}, [isInitialSyncComplete, isOnline]);
|
||
|
|
|
||
|
|
const scheduleSync = () => {
|
||
|
|
// Aguardar 5 segundos antes de sincronizar
|
||
|
|
syncTimeoutRef.current = setTimeout(async () => {
|
||
|
|
try {
|
||
|
|
console.log('Executando sincronização automática');
|
||
|
|
await performIncrementalSync();
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Erro na sincronização automática:', error);
|
||
|
|
}
|
||
|
|
}, 5000);
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
scheduleSync,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 Atualização do HomeScreen
|
||
|
|
|
||
|
|
**Arquivo**: `src/screens/main/HomeScreen.tsx`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Adicionar imports
|
||
|
|
import { useInitialSync } from '../../contexts/InitialSyncContext';
|
||
|
|
import { useAutoSync } from '../../hooks/useAutoSync';
|
||
|
|
|
||
|
|
// Adicionar ao componente
|
||
|
|
const HomeScreen = () => {
|
||
|
|
// ... código existente ...
|
||
|
|
|
||
|
|
const {
|
||
|
|
isInitialSyncComplete,
|
||
|
|
pendingChanges,
|
||
|
|
performIncrementalSync,
|
||
|
|
getSyncStats
|
||
|
|
} = useInitialSync();
|
||
|
|
|
||
|
|
const { scheduleSync } = useAutoSync();
|
||
|
|
|
||
|
|
// Usar auto sync
|
||
|
|
useEffect(() => {
|
||
|
|
if (isInitialSyncComplete) {
|
||
|
|
scheduleSync();
|
||
|
|
}
|
||
|
|
}, [isInitialSyncComplete, scheduleSync]);
|
||
|
|
|
||
|
|
// ... resto do código ...
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
## Fase 5: Testes e Validação
|
||
|
|
|
||
|
|
### 5.1 Script de Teste
|
||
|
|
|
||
|
|
**Arquivo**: `scripts/test-sync.js`
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const { execSync } = require('child_process');
|
||
|
|
|
||
|
|
console.log('=== TESTANDO SINCRONIZAÇÃO OFFLINE ===');
|
||
|
|
|
||
|
|
// Teste 1: Verificar se banco de dados foi criado
|
||
|
|
console.log('1. Verificando estrutura do banco...');
|
||
|
|
try {
|
||
|
|
execSync('npx react-native run-android --variant=debug', { stdio: 'inherit' });
|
||
|
|
console.log('✅ Banco de dados criado com sucesso');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Erro ao criar banco de dados:', error.message);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Teste 2: Verificar sincronização inicial
|
||
|
|
console.log('2. Testando sincronização inicial...');
|
||
|
|
// Implementar testes específicos
|
||
|
|
|
||
|
|
// Teste 3: Verificar modo offline
|
||
|
|
console.log('3. Testando modo offline...');
|
||
|
|
// Implementar testes específicos
|
||
|
|
|
||
|
|
console.log('=== TESTES CONCLUÍDOS ===');
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2 Checklist de Implementação
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
## Checklist de Implementação
|
||
|
|
|
||
|
|
### Fase 1: Preparação
|
||
|
|
- [ ] Instalar dependências necessárias
|
||
|
|
- [ ] Configurar variáveis de ambiente
|
||
|
|
- [ ] Atualizar estrutura do banco de dados
|
||
|
|
- [ ] Criar tabelas de sincronização
|
||
|
|
|
||
|
|
### Fase 2: Serviços Base
|
||
|
|
- [ ] Implementar SyncService
|
||
|
|
- [ ] Criar InitialSyncContext
|
||
|
|
- [ ] Implementar métodos de sincronização
|
||
|
|
- [ ] Adicionar tratamento de erros
|
||
|
|
|
||
|
|
### Fase 3: Interface
|
||
|
|
- [ ] Criar InitialSyncScreen
|
||
|
|
- [ ] Atualizar navegação
|
||
|
|
- [ ] Integrar com App.tsx
|
||
|
|
- [ ] Testar fluxo de navegação
|
||
|
|
|
||
|
|
### Fase 4: Sincronização Automática
|
||
|
|
- [ ] Implementar useAutoSync hook
|
||
|
|
- [ ] Adicionar sincronização incremental
|
||
|
|
- [ ] Atualizar HomeScreen
|
||
|
|
- [ ] Testar sincronização automática
|
||
|
|
|
||
|
|
### Fase 5: Testes
|
||
|
|
- [ ] Testar sincronização inicial
|
||
|
|
- [ ] Testar modo offline
|
||
|
|
- [ ] Testar sincronização incremental
|
||
|
|
- [ ] Validar performance
|
||
|
|
- [ ] Testar tratamento de erros
|
||
|
|
|
||
|
|
### Fase 6: Otimizações
|
||
|
|
- [ ] Implementar compressão de dados
|
||
|
|
- [ ] Adicionar cache inteligente
|
||
|
|
- [ ] Otimizar queries do banco
|
||
|
|
- [ ] Implementar logs de debug
|
||
|
|
```
|
||
|
|
|
||
|
|
## Fase 6: Deploy e Monitoramento
|
||
|
|
|
||
|
|
### 6.1 Configuração de Build
|
||
|
|
|
||
|
|
**Arquivo**: `eas.json`
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"cli": {
|
||
|
|
"version": ">= 5.9.0"
|
||
|
|
},
|
||
|
|
"build": {
|
||
|
|
"development": {
|
||
|
|
"developmentClient": true,
|
||
|
|
"distribution": "internal"
|
||
|
|
},
|
||
|
|
"preview": {
|
||
|
|
"distribution": "internal",
|
||
|
|
"env": {
|
||
|
|
"API_BASE_URL": "https://api-staging.example.com"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"production": {
|
||
|
|
"env": {
|
||
|
|
"API_BASE_URL": "https://api.example.com"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"submit": {
|
||
|
|
"production": {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.2 Monitoramento de Sincronização
|
||
|
|
|
||
|
|
**Arquivo**: `src/services/syncMonitor.ts`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
class SyncMonitor {
|
||
|
|
private metrics: Map<string, any> = new Map();
|
||
|
|
|
||
|
|
recordSyncAttempt(type: string, success: boolean, duration: number) {
|
||
|
|
const key = `${type}_${new Date().toISOString().split('T')[0]}`;
|
||
|
|
const current = this.metrics.get(key) || {
|
||
|
|
attempts: 0,
|
||
|
|
successes: 0,
|
||
|
|
failures: 0,
|
||
|
|
totalDuration: 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
current.attempts += 1;
|
||
|
|
current.totalDuration += duration;
|
||
|
|
|
||
|
|
if (success) {
|
||
|
|
current.successes += 1;
|
||
|
|
} else {
|
||
|
|
current.failures += 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.metrics.set(key, current);
|
||
|
|
}
|
||
|
|
|
||
|
|
getMetrics(): any {
|
||
|
|
return Object.fromEntries(this.metrics);
|
||
|
|
}
|
||
|
|
|
||
|
|
exportMetrics(): string {
|
||
|
|
return JSON.stringify(this.getMetrics(), null, 2);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const syncMonitor = new SyncMonitor();
|
||
|
|
```
|
||
|
|
|
||
|
|
## Conclusão
|
||
|
|
|
||
|
|
Este guia fornece uma implementação completa e passo a passo da sincronização offline no aplicativo de entregas. A implementação é modular e pode ser feita incrementalmente, permitindo testes e validação em cada fase.
|
||
|
|
|
||
|
|
**Próximos Passos**:
|
||
|
|
1. Implementar Fase 1 (Preparação)
|
||
|
|
2. Testar estrutura do banco de dados
|
||
|
|
3. Implementar Fase 2 (Serviços Base)
|
||
|
|
4. Testar sincronização inicial
|
||
|
|
5. Continuar com as demais fases
|
||
|
|
|
||
|
|
**Considerações Importantes**:
|
||
|
|
- Sempre testar em ambiente de desenvolvimento primeiro
|
||
|
|
- Implementar logs detalhados para debugging
|
||
|
|
- Considerar performance e uso de bateria
|
||
|
|
- Implementar fallbacks para cenários de erro
|
||
|
|
- Monitorar uso de dados e armazenamento
|