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:
Chris Kruining 2026-02-09 16:16:34 +01:00
parent 56499d5af9
commit 83ab4df537
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
138 changed files with 19136 additions and 7681 deletions

View file

@ -0,0 +1,95 @@
/**
* Image loader utilities for React Native.
* Loads images from file paths and returns pixel data for recognition.
*/
import * as ImageManipulator from "expo-image-manipulator";
import { Platform } from "react-native";
export interface LoadedImage {
pixels: Uint8Array;
width: number;
height: number;
}
/**
* Load image from a file path and return RGBA pixel data.
* Uses expo-image-manipulator to resize the image to a manageable size first.
*
* Note: expo-image-manipulator doesn't directly provide pixel data.
* We need to use a canvas (web) or Skia (native) to decode pixels.
* For now, this provides the resized image URI for the Skia-based decoder.
*/
export async function loadImageForRecognition(
uri: string,
targetWidth: number = 480, // Reasonable size for processing
targetHeight: number = 640
): Promise<{ uri: string; width: number; height: number }> {
// Normalize the URI for the platform
const normalizedUri =
Platform.OS === "android" && !uri.startsWith("file://")
? `file://${uri}`
: uri;
// Resize the image for faster processing
const result = await ImageManipulator.manipulateAsync(
normalizedUri,
[
{
resize: {
width: targetWidth,
height: targetHeight,
},
},
],
{
compress: 1,
format: ImageManipulator.SaveFormat.PNG,
}
);
return {
uri: result.uri,
width: result.width,
height: result.height,
};
}
/**
* Load image and get base64 data.
* Useful for passing to native modules or Skia.
*/
export async function loadImageAsBase64(
uri: string,
targetWidth: number = 480,
targetHeight: number = 640
): Promise<{ base64: string; width: number; height: number }> {
// Normalize the URI
const normalizedUri =
Platform.OS === "android" && !uri.startsWith("file://")
? `file://${uri}`
: uri;
const result = await ImageManipulator.manipulateAsync(
normalizedUri,
[
{
resize: {
width: targetWidth,
height: targetHeight,
},
},
],
{
compress: 1,
format: ImageManipulator.SaveFormat.PNG,
base64: true,
}
);
return {
base64: result.base64 || "",
width: result.width,
height: result.height,
};
}