Complete rewrite of Scry using TypeScript stack:
- Expo/React Native with Expo Router (file-based routing)
- Convex backend (serverless functions + real-time database)
- Adaptive camera system (expo-camera in Expo Go, Vision Camera in production)
- React Native Skia + fast-opencv for image processing
- GDPR-compliant auth setup with Zitadel OIDC (pending configuration)
Key features:
- Card recognition pipeline ported to TypeScript
- Perceptual hashing (192-bit color pHash)
- CLAHE preprocessing for lighting normalization
- Local SQLite cache with Convex sync
- Collection management with offline support
Removes all .NET/MAUI code (src/, test/, tools/).
💘 Generated with Crush
Assisted-by: Claude Opus 4.5 via Crush <crush@charm.land>
102 lines
2.6 KiB
TypeScript
102 lines
2.6 KiB
TypeScript
/**
|
|
* Hook for managing offline sync with local SQLite cache.
|
|
*/
|
|
|
|
import { useEffect, useState, useCallback, useRef } from "react";
|
|
import { useConvex } from "convex/react";
|
|
import { createSyncService, type SyncService, type SyncStatus } from "../db/syncService";
|
|
import { useHashCache } from "../context";
|
|
|
|
/**
|
|
* Hook to manage sync service lifecycle and status.
|
|
* Initializes local database, syncs with Convex, and provides hashes for recognition.
|
|
*/
|
|
export function useSync() {
|
|
const convex = useConvex();
|
|
const syncServiceRef = useRef<SyncService | null>(null);
|
|
const [status, setStatus] = useState<SyncStatus>({
|
|
isInitialized: false,
|
|
isSyncing: false,
|
|
lastSync: 0,
|
|
localCardCount: 0,
|
|
});
|
|
|
|
const { setCardHashes } = useHashCache();
|
|
|
|
// Initialize sync service
|
|
useEffect(() => {
|
|
if (!convex || syncServiceRef.current) return;
|
|
|
|
const service = createSyncService(convex);
|
|
syncServiceRef.current = service;
|
|
|
|
// Subscribe to status changes
|
|
const unsubscribe = service.onStatusChange(setStatus);
|
|
|
|
// Initialize and load cached hashes
|
|
const init = async () => {
|
|
await service.initialize();
|
|
|
|
// Load cached hashes into app store
|
|
const hashes = await service.getHashes();
|
|
if (hashes.length > 0) {
|
|
setCardHashes(hashes);
|
|
console.log(`[useSync] Loaded ${hashes.length} cached hashes`);
|
|
}
|
|
|
|
// Start background sync
|
|
service.sync().then(async () => {
|
|
// Reload hashes after sync
|
|
const updatedHashes = await service.getHashes();
|
|
setCardHashes(updatedHashes);
|
|
});
|
|
};
|
|
|
|
init();
|
|
|
|
return () => {
|
|
unsubscribe();
|
|
};
|
|
}, [convex, setCardHashes]);
|
|
|
|
// Manual sync trigger
|
|
const sync = useCallback(async () => {
|
|
if (!syncServiceRef.current) return;
|
|
|
|
await syncServiceRef.current.sync();
|
|
|
|
// Reload hashes after sync
|
|
const hashes = await syncServiceRef.current.getHashes();
|
|
setCardHashes(hashes);
|
|
}, [setCardHashes]);
|
|
|
|
// Clear local cache
|
|
const clearCache = useCallback(async () => {
|
|
if (!syncServiceRef.current) return;
|
|
|
|
await syncServiceRef.current.clearLocalCache();
|
|
setCardHashes([]);
|
|
}, [setCardHashes]);
|
|
|
|
// Get current hashes from cache
|
|
const getHashes = useCallback(async () => {
|
|
if (!syncServiceRef.current) return [];
|
|
return syncServiceRef.current.getHashes();
|
|
}, []);
|
|
|
|
return {
|
|
...status,
|
|
sync,
|
|
clearCache,
|
|
getHashes,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Context-free sync initialization for use in _layout.tsx
|
|
* Returns a component that initializes sync when mounted.
|
|
*/
|
|
export function SyncInitializer() {
|
|
useSync();
|
|
return null;
|
|
}
|