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>
64 lines
1.6 KiB
TypeScript
64 lines
1.6 KiB
TypeScript
/**
|
|
* Adaptive camera component that uses expo-camera in Expo Go
|
|
* and react-native-vision-camera in production builds.
|
|
*/
|
|
|
|
import React, { forwardRef, lazy, Suspense } from "react";
|
|
import { View, ActivityIndicator, StyleSheet, Text } from "react-native";
|
|
import Constants from "expo-constants";
|
|
|
|
export interface CameraHandle {
|
|
takePhoto: () => Promise<{ uri: string }>;
|
|
}
|
|
|
|
interface AdaptiveCameraProps {
|
|
flashEnabled?: boolean;
|
|
}
|
|
|
|
// Detect if running in Expo Go
|
|
const isExpoGo = Constants.appOwnership === "expo";
|
|
|
|
// Lazy load the appropriate camera component
|
|
const ExpoCamera = lazy(() =>
|
|
import("./ExpoCamera").then((m) => ({ default: m.ExpoCamera }))
|
|
);
|
|
const VisionCamera = lazy(() =>
|
|
import("./VisionCamera").then((m) => ({ default: m.VisionCamera }))
|
|
);
|
|
|
|
const CameraLoading = () => (
|
|
<View style={styles.loading}>
|
|
<ActivityIndicator size="large" color="#007AFF" />
|
|
<Text style={styles.loadingText}>Initializing camera...</Text>
|
|
</View>
|
|
);
|
|
|
|
export const AdaptiveCamera = forwardRef<CameraHandle, AdaptiveCameraProps>(
|
|
(props, ref) => {
|
|
const CameraComponent = isExpoGo ? ExpoCamera : VisionCamera;
|
|
|
|
return (
|
|
<Suspense fallback={<CameraLoading />}>
|
|
<CameraComponent ref={ref} {...props} />
|
|
</Suspense>
|
|
);
|
|
}
|
|
);
|
|
|
|
AdaptiveCamera.displayName = "AdaptiveCamera";
|
|
|
|
const styles = StyleSheet.create({
|
|
loading: {
|
|
...StyleSheet.absoluteFillObject,
|
|
backgroundColor: "#000",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
},
|
|
loadingText: {
|
|
marginTop: 16,
|
|
color: "#888",
|
|
fontSize: 14,
|
|
},
|
|
});
|
|
|
|
export { isExpoGo };
|