Migrate from .NET MAUI to Expo + Convex
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>
This commit is contained in:
parent
56499d5af9
commit
83ab4df537
138 changed files with 19136 additions and 7681 deletions
116
lib/hooks/useCamera.ts
Normal file
116
lib/hooks/useCamera.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* Hook for camera permissions.
|
||||
* Supports both expo-camera (Expo Go) and react-native-vision-camera (production).
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Platform, Linking, Alert } from "react-native";
|
||||
import Constants from "expo-constants";
|
||||
|
||||
// Detect if running in Expo Go
|
||||
const isExpoGo = Constants.appOwnership === "expo";
|
||||
|
||||
export interface CameraPermissionState {
|
||||
hasPermission: boolean;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for camera permissions that works in both Expo Go and production builds.
|
||||
*/
|
||||
export function useCameraPermission(): CameraPermissionState & { requestPermission: () => Promise<void> } {
|
||||
const [hasPermission, setHasPermission] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
checkPermission();
|
||||
}, []);
|
||||
|
||||
const checkPermission = async () => {
|
||||
try {
|
||||
if (isExpoGo) {
|
||||
// Use expo-camera
|
||||
const { Camera } = await import("expo-camera");
|
||||
const { status } = await Camera.getCameraPermissionsAsync();
|
||||
setHasPermission(status === "granted");
|
||||
} else {
|
||||
// Use react-native-vision-camera
|
||||
const { Camera } = await import("react-native-vision-camera");
|
||||
const status = await Camera.getCameraPermissionStatus();
|
||||
setHasPermission(status === "granted");
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to check camera permission");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const requestPermission = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
if (isExpoGo) {
|
||||
// Use expo-camera
|
||||
const { Camera } = await import("expo-camera");
|
||||
const { status, canAskAgain } = await Camera.requestCameraPermissionsAsync();
|
||||
|
||||
if (status === "granted") {
|
||||
setHasPermission(true);
|
||||
} else {
|
||||
if (!canAskAgain) {
|
||||
showSettingsAlert();
|
||||
}
|
||||
setError("Camera permission denied");
|
||||
}
|
||||
} else {
|
||||
// Use react-native-vision-camera
|
||||
const { Camera } = await import("react-native-vision-camera");
|
||||
const status = await Camera.requestCameraPermission();
|
||||
|
||||
if (status === "granted") {
|
||||
setHasPermission(true);
|
||||
} else if (status === "denied") {
|
||||
showSettingsAlert();
|
||||
setError("Camera permission denied");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to request camera permission");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { hasPermission, isLoading, error, requestPermission };
|
||||
}
|
||||
|
||||
function showSettingsAlert() {
|
||||
Alert.alert(
|
||||
"Camera Permission Required",
|
||||
"Scry needs camera access to scan cards. Please enable it in Settings.",
|
||||
[
|
||||
{ text: "Cancel", style: "cancel" },
|
||||
{
|
||||
text: "Open Settings",
|
||||
onPress: () => {
|
||||
if (Platform.OS === "ios") {
|
||||
Linking.openURL("app-settings:");
|
||||
} else {
|
||||
Linking.openSettings();
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if using expo-camera (Expo Go), false for Vision Camera (production).
|
||||
*/
|
||||
export function useIsExpoGo(): boolean {
|
||||
return isExpoGo;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue