initial
This commit is contained in:
@@ -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`.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }} />;
|
||||
}
|
||||
@@ -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' },
|
||||
});
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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' },
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
# apps/mobile/assets
|
||||
|
||||
Drop `icon.png` (1024x1024), `splash.png`, `adaptive-icon.png` here.
|
||||
Paths are referenced from `app.json`.
|
||||
@@ -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',
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -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`.
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user