This commit is contained in:
2026-04-18 23:11:35 +02:00
commit f7cfd2a86e
196 changed files with 35538 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# @chat-app/mobile
Expo + React Native client for iOS and Android.
## Prerequisites
- Node 22+, pnpm 9+
- Xcode (iOS) / Android Studio (Android)
- Optional: `npm i -g eas-cli` for cloud builds
## Dev
```bash
# from repo root
pnpm install
pnpm mobile:dev # expo start
pnpm mobile:ios # native iOS run
pnpm mobile:android # native Android run
```
## Notes
- Uses Expo Router (file-based). Screens live under `app/`.
- Secrets stored via `expo-secure-store` (Keychain / Keystore).
- Local encrypted history via `expo-sqlite`.
- Crypto via `react-native-libsodium`.
- Shared business logic lives in `@chat-app/shared`.
+51
View File
@@ -0,0 +1,51 @@
{
"expo": {
"name": "ChatApp",
"slug": "chat-app",
"version": "0.1.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"scheme": "chatapp",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#0b0b0f"
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.meinname.chatapp",
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false
}
},
"android": {
"package": "com.meinname.chatapp",
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#0b0b0f"
}
},
"plugins": [
"expo-router",
"expo-secure-store",
"expo-sqlite",
[
"expo-notifications",
{
"color": "#0b0b0f"
}
]
],
"experiments": {
"typedRoutes": true
},
"extra": {
"eas": {
"projectId": "REPLACE_WITH_EAS_PROJECT_ID"
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
// Authenticated app group layout.
// Gate on session: if no session, redirect to `/`.
// Add tab bar / drawer nav here once we have more than one screen.
import { Stack } from 'expo-router';
export default function AppLayout() {
return <Stack screenOptions={{ headerShown: true }} />;
}
+18
View File
@@ -0,0 +1,18 @@
// Chats list placeholder.
// Milestone 1 TODO: render list of conversations, decrypt last-message preview,
// navigate to a conversation screen on tap.
import { StyleSheet, Text, View } from 'react-native';
export default function Chats() {
return (
<View style={styles.container}>
<Text style={styles.text}>Chats placeholder</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#0b0b0f' },
text: { color: '#fff' },
});
+18
View File
@@ -0,0 +1,18 @@
// Root layout for Expo Router.
// Wraps every screen. Place global providers here (Theme, Supabase/Auth context,
// SafeAreaProvider, GestureHandlerRootView, etc.) once they exist.
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
export default function RootLayout() {
return (
<>
<StatusBar style="auto" />
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
<Stack.Screen name="(app)" />
</Stack>
</>
);
}
+29
View File
@@ -0,0 +1,29 @@
// Landing / Login screen.
//
// Milestone 1 TODO:
// - Show app logo + "Enter your email" field.
// - On submit, call shared/auth requestMagicLink(email).
// - Handle deep link return in app/_layout.tsx (or a dedicated auth callback route).
import { StyleSheet, Text, View } from 'react-native';
export default function Landing() {
return (
<View style={styles.container}>
<Text style={styles.title}>ChatApp</Text>
<Text style={styles.subtitle}>Login placeholder magic link flow goes here.</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#0b0b0f',
padding: 24,
},
title: { color: '#fff', fontSize: 28, fontWeight: '600' },
subtitle: { color: '#9ca3af', marginTop: 8, textAlign: 'center' },
});
+4
View File
@@ -0,0 +1,4 @@
# apps/mobile/assets
Drop `icon.png` (1024x1024), `splash.png`, `adaptive-icon.png` here.
Paths are referenced from `app.json`.
+10
View File
@@ -0,0 +1,10 @@
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
// expo-router requires this plugin
'expo-router/babel',
],
};
};
View File
+10
View File
@@ -0,0 +1,10 @@
# apps/mobile/lib
App-local helpers that depend on Expo / React Native APIs and therefore cannot live in `packages/shared`.
Expected contents:
- `secureStorage.ts` — thin adapter over `expo-secure-store`, exposes get/set/delete that `@chat-app/shared/auth` consumes.
- `sqlite.ts` — opens the encrypted local message DB via `expo-sqlite`, exposes a migration runner.
- `push.ts` — wires `expo-notifications` + registers FCM/APNs token with Supabase.
- `sodium.ts` — binds `react-native-libsodium` to the backend interface expected by `@chat-app/shared/crypto`.
+22
View File
@@ -0,0 +1,22 @@
// Metro config for Expo inside a pnpm monorepo.
// Key points:
// - Watch the repo root so workspace packages (@chat-app/shared, db-types) are seen.
// - Disable hierarchical lookup so Metro uses only the hoisted/workspace node_modules.
// - Add nodeModulesPaths so symlinked workspace deps resolve.
const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '../..');
const config = getDefaultConfig(projectRoot);
config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
];
config.resolver.disableHierarchicalLookup = true;
module.exports = config;
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@chat-app/mobile",
"version": "0.1.0",
"private": true,
"description": "Expo + React Native mobile client (iOS / Android)",
"main": "expo-router/entry",
"scripts": {
"dev": "expo start",
"start": "expo start",
"ios": "expo run:ios",
"android": "expo run:android",
"web": "expo start --web",
"build": "echo \"Use EAS Build: eas build --platform all\" && exit 0",
"lint": "eslint . --ext .ts,.tsx",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"clean": "rm -rf .expo node_modules/.cache .turbo *.tsbuildinfo"
},
"dependencies": {
"@chat-app/shared": "workspace:*",
"@chat-app/db-types": "workspace:*",
"@supabase/supabase-js": "^2.46.0",
"expo": "^52.0.0",
"expo-constants": "~17.0.0",
"expo-linking": "~7.0.0",
"expo-notifications": "~0.29.0",
"expo-router": "~4.0.0",
"expo-secure-store": "~14.0.0",
"expo-sqlite": "~15.0.0",
"expo-status-bar": "~2.0.0",
"react": "18.3.1",
"react-native": "0.76.0",
"react-native-libsodium": "^1.3.0",
"react-native-safe-area-context": "~4.12.0",
"react-native-screens": "~4.1.0"
},
"devDependencies": {
"@babel/core": "^7.25.0",
"@types/react": "~18.3.12"
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM"],
"types": ["expo/types"],
"composite": false,
"noEmit": true,
"allowJs": true,
"paths": {
"@/*": ["./*"],
"@shared/*": ["../../packages/shared/src/*"],
"@db-types/*": ["../../packages/db-types/src/*"]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts"
],
"exclude": ["node_modules", ".expo", "dist", "build"]
}