refactor to turbo
4
apps/expo/.expo-shared/assets.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
||||
}
|
47
apps/expo/app.config.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { ExpoConfig } from "expo/config";
|
||||
|
||||
const defineConfig = (): ExpoConfig => ({
|
||||
name: "movie-web",
|
||||
slug: "mw-mobile",
|
||||
scheme: "dev.movieweb.app",
|
||||
version: "1.0.0",
|
||||
orientation: "portrait",
|
||||
icon: "./assets/icon.png",
|
||||
userInterfaceStyle: "automatic",
|
||||
splash: {
|
||||
image: "./assets/images/splash.png",
|
||||
resizeMode: "contain",
|
||||
backgroundColor: "#ffffff",
|
||||
},
|
||||
updates: {
|
||||
fallbackToCacheTimeout: 0,
|
||||
},
|
||||
assetBundlePatterns: ["**/*"],
|
||||
ios: {
|
||||
bundleIdentifier: "dev.movieweb.app",
|
||||
supportsTablet: true,
|
||||
},
|
||||
android: {
|
||||
package: "dev.movieweb.app",
|
||||
adaptiveIcon: {
|
||||
foregroundImage: "./assets/images/adaptive-icon.png",
|
||||
backgroundColor: "#FFFFFF",
|
||||
},
|
||||
},
|
||||
web: {
|
||||
favicon: "./assets/images/favicon.png",
|
||||
bundler: "metro",
|
||||
},
|
||||
// extra: {
|
||||
// eas: {
|
||||
// projectId: "your-eas-project-id",
|
||||
// },
|
||||
// },
|
||||
experiments: {
|
||||
tsconfigPaths: true,
|
||||
typedRoutes: true,
|
||||
},
|
||||
plugins: ["expo-router"],
|
||||
});
|
||||
|
||||
export default defineConfig;
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
11
apps/expo/babel.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @type {import("@babel/core").ConfigFunction} */
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: [
|
||||
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
||||
"nativewind/babel",
|
||||
],
|
||||
plugins: ["react-native-reanimated/plugin"],
|
||||
};
|
||||
};
|
@@ -1,28 +1,31 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 4.1.2"
|
||||
},
|
||||
"build": {
|
||||
"production": {
|
||||
"android": {
|
||||
"buildType": "app-bundle"
|
||||
"base": {
|
||||
"node": "18.16.1",
|
||||
"ios": {
|
||||
"resourceClass": "m-medium"
|
||||
}
|
||||
},
|
||||
"development": {
|
||||
"extends": "base",
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"preview": {
|
||||
"extends": "base",
|
||||
"distribution": "internal",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
},
|
||||
"android": {
|
||||
"buildType": "apk"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"extends": "base"
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
},
|
||||
"cli": {
|
||||
"version": ">= 5.2.0"
|
||||
}
|
||||
}
|
60
apps/expo/metro.config.js
Normal file
@@ -0,0 +1,60 @@
|
||||
// Learn more: https://docs.expo.dev/guides/monorepos/
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const { FileStore } = require("metro-cache");
|
||||
const { withNativeWind } = require("nativewind/metro");
|
||||
|
||||
const path = require("path");
|
||||
|
||||
module.exports = withTurborepoManagedCache(
|
||||
withMonorepoPaths(
|
||||
withNativeWind(
|
||||
getDefaultConfig(__dirname, {
|
||||
isCSSEnabled: true,
|
||||
}),
|
||||
{
|
||||
input: "./src/app/styles/global.css",
|
||||
configPath: "./tailwind.config.ts",
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Add the monorepo paths to the Metro config.
|
||||
* This allows Metro to resolve modules from the monorepo.
|
||||
*
|
||||
* @see https://docs.expo.dev/guides/monorepos/#modify-the-metro-config
|
||||
* @param {import('expo/metro-config').MetroConfig} config
|
||||
* @returns {import('expo/metro-config').MetroConfig}
|
||||
*/
|
||||
function withMonorepoPaths(config) {
|
||||
const projectRoot = __dirname;
|
||||
const workspaceRoot = path.resolve(projectRoot, "../..");
|
||||
|
||||
// #1 - Watch all files in the monorepo
|
||||
config.watchFolders = [workspaceRoot];
|
||||
|
||||
// #2 - Resolve modules within the project's `node_modules` first, then all monorepo modules
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the Metro cache to the `node_modules/.cache/metro` folder.
|
||||
* This repository configured Turborepo to use this cache location as well.
|
||||
* If you have any environment variables, you can configure Turborepo to invalidate it when needed.
|
||||
*
|
||||
* @see https://turbo.build/repo/docs/reference/configuration#env
|
||||
* @param {import('expo/metro-config').MetroConfig} config
|
||||
* @returns {import('expo/metro-config').MetroConfig}
|
||||
*/
|
||||
function withTurborepoManagedCache(config) {
|
||||
config.cacheStores = [
|
||||
new FileStore({ root: path.join(__dirname, "node_modules/.cache/metro") }),
|
||||
];
|
||||
return config;
|
||||
}
|
66
apps/expo/package.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@movie-web/mobile",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "expo-router/entry",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .expo .turbo node_modules",
|
||||
"dev": "expo start -c --web",
|
||||
"dev:android": "expo start --android",
|
||||
"dev:ios": "expo start --ios",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/metro-config": "^0.17.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"expo": "~50.0.5",
|
||||
"expo-constants": "~15.4.5",
|
||||
"expo-linking": "~6.2.2",
|
||||
"expo-router": "~3.4.6",
|
||||
"expo-splash-screen": "~0.26.4",
|
||||
"expo-status-bar": "~1.11.1",
|
||||
"expo-web-browser": "^12.8.2",
|
||||
"nativewind": "~4.0.23",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.73.2",
|
||||
"react-native-css-interop": "~0.0.13",
|
||||
"react-native-gesture-handler": "~2.14.0",
|
||||
"react-native-reanimated": "~3.6.2",
|
||||
"react-native-safe-area-context": "~4.8.2",
|
||||
"react-native-screens": "~3.29.0",
|
||||
"react-native-web": "^0.19.10",
|
||||
"tailwind-merge": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@movie-web/eslint-config": "workspace:^0.2.0",
|
||||
"@movie-web/prettier-config": "workspace:^0.1.0",
|
||||
"@movie-web/tailwind-config": "workspace:^0.1.0",
|
||||
"@movie-web/tsconfig": "workspace:^0.1.0",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/react": "^18.2.48",
|
||||
"eslint": "^8.56.0",
|
||||
"prettier": "^3.1.1",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@movie-web/eslint-config/base",
|
||||
"@movie-web/eslint-config/react"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"expo-plugins/**"
|
||||
]
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config"
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
import { Tabs } from 'expo-router';
|
||||
import { Tabs } from "expo-router";
|
||||
|
||||
import TabBarIcon from '@/components/TabBarIcon';
|
||||
import Colors from '@/constants/Colors';
|
||||
import Colors from "@movie-web/tailwind-config/colors";
|
||||
|
||||
import TabBarIcon from "~/components/TabBarIcon";
|
||||
|
||||
export default function TabLayout() {
|
||||
return (
|
||||
@@ -14,7 +15,7 @@ export default function TabLayout() {
|
||||
tabBarActiveTintColor: Colors.primary[100],
|
||||
tabBarStyle: {
|
||||
backgroundColor: Colors.secondary[700],
|
||||
borderTopColor: 'transparent',
|
||||
borderTopColor: "transparent",
|
||||
borderTopRightRadius: 20,
|
||||
borderTopLeftRadius: 20,
|
||||
height: 80,
|
||||
@@ -33,7 +34,7 @@ export default function TabLayout() {
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: 'Home',
|
||||
title: "Home",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="home" focused={focused} />
|
||||
),
|
||||
@@ -42,7 +43,7 @@ export default function TabLayout() {
|
||||
<Tabs.Screen
|
||||
name="about"
|
||||
options={{
|
||||
title: 'About',
|
||||
title: "About",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="info-circle" focused={focused} />
|
||||
),
|
||||
@@ -51,8 +52,8 @@ export default function TabLayout() {
|
||||
<Tabs.Screen
|
||||
name="search"
|
||||
options={{
|
||||
title: 'Search',
|
||||
tabBarLabel: '',
|
||||
title: "Search",
|
||||
tabBarLabel: "",
|
||||
tabBarIcon: () => (
|
||||
<TabBarIcon
|
||||
className=" bg-primary-400 flex aspect-[1/1] h-14 items-center justify-center rounded-full text-center text-2xl text-white"
|
||||
@@ -65,7 +66,7 @@ export default function TabLayout() {
|
||||
<Tabs.Screen
|
||||
name="settings"
|
||||
options={{
|
||||
title: 'Settings',
|
||||
title: "Settings",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="cog" focused={focused} />
|
||||
),
|
||||
@@ -74,7 +75,7 @@ export default function TabLayout() {
|
||||
<Tabs.Screen
|
||||
name="account"
|
||||
options={{
|
||||
title: 'Account',
|
||||
title: "Account",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="user" focused={focused} />
|
||||
),
|
@@ -1,5 +1,5 @@
|
||||
import ScreenLayout from '@/components/layout/ScreenLayout';
|
||||
import { Text } from '@/components/ui/Text';
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function AboutScreen() {
|
||||
return (
|
@@ -1,13 +1,13 @@
|
||||
import ScreenLayout from '@/components/layout/ScreenLayout';
|
||||
import { Text } from '@/components/ui/Text';
|
||||
|
||||
export default function AccountScreen() {
|
||||
return (
|
||||
<ScreenLayout
|
||||
title="Account"
|
||||
subtitle="Manage your movie web account from here"
|
||||
>
|
||||
<Text>Hey Bro! what are you up to?</Text>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function AccountScreen() {
|
||||
return (
|
||||
<ScreenLayout
|
||||
title="Account"
|
||||
subtitle="Manage your movie web account from here"
|
||||
>
|
||||
<Text>Hey Bro! what are you up to?</Text>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import ScreenLayout from '@/components/layout/ScreenLayout';
|
||||
import { Text } from '@/components/ui/Text';
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
@@ -1,42 +1,41 @@
|
||||
import { FontAwesome5 } from '@expo/vector-icons';
|
||||
import { useFocusEffect } from 'expo-router';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { TextInput } from 'react-native-gesture-handler';
|
||||
|
||||
import Colors from '@/constants/Colors';
|
||||
|
||||
export default function Searchbar() {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
// When the screen is focused
|
||||
const focus = () => {
|
||||
setTimeout(() => {
|
||||
inputRef?.current?.focus();
|
||||
}, 20);
|
||||
};
|
||||
focus();
|
||||
return focus; // cleanup
|
||||
}, []),
|
||||
);
|
||||
|
||||
return (
|
||||
<View className="border-primary-400 focus-within:border-primary-300 mb-6 mt-4 flex-row items-center rounded-full border">
|
||||
<View className="ml-1 w-12 items-center justify-center">
|
||||
<FontAwesome5 name="search" size={18} color={Colors.secondary[200]} />
|
||||
</View>
|
||||
<TextInput
|
||||
value={keyword}
|
||||
autoFocus
|
||||
onChangeText={(text) => setKeyword(text)}
|
||||
ref={inputRef}
|
||||
placeholder="What are you looking for?"
|
||||
placeholderTextColor={Colors.secondary[200]}
|
||||
className="w-full rounded-3xl py-3 pr-5 text-white focus-visible:outline-none"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { TextInput } from "react-native-gesture-handler";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { FontAwesome5 } from "@expo/vector-icons";
|
||||
|
||||
import Colors from "@movie-web/tailwind-config/colors";
|
||||
|
||||
export default function Searchbar() {
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
// When the screen is focused
|
||||
const focus = () => {
|
||||
setTimeout(() => {
|
||||
inputRef?.current?.focus();
|
||||
}, 20);
|
||||
};
|
||||
focus();
|
||||
return focus; // cleanup
|
||||
}, []),
|
||||
);
|
||||
|
||||
return (
|
||||
<View className="border-primary-400 focus-within:border-primary-300 mb-6 mt-4 flex-row items-center rounded-full border">
|
||||
<View className="ml-1 w-12 items-center justify-center">
|
||||
<FontAwesome5 name="search" size={18} color={Colors.secondary[200]} />
|
||||
</View>
|
||||
<TextInput
|
||||
value={keyword}
|
||||
onChangeText={(text) => setKeyword(text)}
|
||||
ref={inputRef}
|
||||
placeholder="What are you looking for?"
|
||||
placeholderTextColor={Colors.secondary[200]}
|
||||
className="w-full rounded-3xl py-3 pr-5 text-white focus-visible:outline-none"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
@@ -1,35 +1,34 @@
|
||||
import { ScrollView, View } from 'react-native';
|
||||
|
||||
import Item from '@/components/item/item';
|
||||
import ScreenLayout from '@/components/layout/ScreenLayout';
|
||||
import { Text } from '@/components/ui/Text';
|
||||
|
||||
import Searchbar from './Searchbar';
|
||||
|
||||
export default function SearchScreen() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<ScreenLayout
|
||||
title={
|
||||
<View className="flex-row items-center">
|
||||
<Text className="text-2xl font-bold">Search</Text>
|
||||
</View>
|
||||
}
|
||||
subtitle="Looking for something?"
|
||||
>
|
||||
<Searchbar />
|
||||
<View className="flex w-full flex-1 flex-row flex-wrap justify-start">
|
||||
<View className="basis-1/2 px-3 pb-3">
|
||||
<Item />
|
||||
</View>
|
||||
<View className="basis-1/2 px-3 pb-3">
|
||||
<Item />
|
||||
</View>
|
||||
<View className="basis-1/2 px-3 pb-3">
|
||||
<Item />
|
||||
</View>
|
||||
</View>
|
||||
</ScreenLayout>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
import { ScrollView, View } from "react-native";
|
||||
|
||||
import Item from "~/components/item/item";
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
import Searchbar from "./Searchbar";
|
||||
|
||||
export default function SearchScreen() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<ScreenLayout
|
||||
title={
|
||||
<View className="flex-row items-center">
|
||||
<Text className="text-2xl font-bold">Search</Text>
|
||||
</View>
|
||||
}
|
||||
subtitle="Looking for something?"
|
||||
>
|
||||
<Searchbar />
|
||||
<View className="flex w-full flex-1 flex-row flex-wrap justify-start">
|
||||
<View className="basis-1/2 px-3 pb-3">
|
||||
<Item />
|
||||
</View>
|
||||
<View className="basis-1/2 px-3 pb-3">
|
||||
<Item />
|
||||
</View>
|
||||
<View className="basis-1/2 px-3 pb-3">
|
||||
<Item />
|
||||
</View>
|
||||
</View>
|
||||
</ScreenLayout>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
import ScreenLayout from '@/components/layout/ScreenLayout';
|
||||
import { Text } from '@/components/ui/Text';
|
||||
|
||||
export default function SettingsScreen() {
|
||||
return (
|
||||
<ScreenLayout title="Settings" subtitle="Need to change something?">
|
||||
<Text>Settings would be listed in here. Coming soon</Text>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function SettingsScreen() {
|
||||
return (
|
||||
<ScreenLayout title="Settings" subtitle="Need to change something?">
|
||||
<Text>Settings would be listed in here. Coming soon</Text>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { ScrollViewStyleReset } from 'expo-router/html';
|
||||
import { ScrollViewStyleReset } from "expo-router/html";
|
||||
|
||||
// This file is web-only and used to configure the root HTML for every
|
||||
// web page during static rendering.
|
@@ -1,12 +1,12 @@
|
||||
import { Link, Stack } from 'expo-router';
|
||||
import { View } from 'react-native';
|
||||
import { View } from "react-native";
|
||||
import { Link, Stack } from "expo-router";
|
||||
|
||||
import { Text } from '@/components/ui/Text';
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||
<Stack.Screen options={{ title: "Oops!" }} />
|
||||
<View className="flex-1 items-center justify-center p-5">
|
||||
<Text className="text-lg font-bold">
|
||||
This screen doesn't exist.
|
81
apps/expo/src/app/_layout.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { useEffect } from "react";
|
||||
import { useColorScheme } from "react-native";
|
||||
import { useFonts } from "expo-font";
|
||||
import { SplashScreen, Stack } from "expo-router";
|
||||
import FontAwesome from "@expo/vector-icons/FontAwesome";
|
||||
import {
|
||||
DarkTheme,
|
||||
DefaultTheme,
|
||||
ThemeProvider,
|
||||
} from "@react-navigation/native";
|
||||
|
||||
import Colors from "@movie-web/tailwind-config/colors";
|
||||
|
||||
import "./styles/global.css";
|
||||
|
||||
export {
|
||||
// Catch any errors thrown by the Layout component.
|
||||
ErrorBoundary,
|
||||
} from "expo-router";
|
||||
|
||||
export const unstable_settings = {
|
||||
// Ensure that reloading on `/modal` keeps a back button present.
|
||||
initialRouteName: "(tabs)/index",
|
||||
};
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync().catch(() => {
|
||||
/* reloading the app might trigger this, so it's safe to ignore */
|
||||
});
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded, error] = useFonts({
|
||||
OpenSansRegular: require("../../assets/fonts/OpenSans-Regular.ttf"),
|
||||
OpenSansLight: require("../../assets/fonts/OpenSans-Light.ttf"),
|
||||
OpenSansMedium: require("../../assets/fonts/OpenSans-Medium.ttf"),
|
||||
OpenSansBold: require("../../assets/fonts/OpenSans-Bold.ttf"),
|
||||
OpenSansSemiBold: require("../../assets/fonts/OpenSans-SemiBold.ttf"),
|
||||
OpenSansExtra: require("../../assets/fonts/OpenSans-ExtraBold.ttf"),
|
||||
...FontAwesome.font,
|
||||
});
|
||||
|
||||
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
|
||||
useEffect(() => {
|
||||
if (error) throw error;
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync().catch(() => {
|
||||
/* reloading the app might trigger this, so it's safe to ignore */
|
||||
});
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RootLayoutNav />;
|
||||
}
|
||||
|
||||
function RootLayoutNav() {
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
gestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor: Colors.background,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
30
apps/expo/src/app/components/ExternalLink.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { Link } from "expo-router";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
export function ExternalLink(
|
||||
props: Omit<React.ComponentProps<typeof Link>, "href"> & { href: string },
|
||||
) {
|
||||
return (
|
||||
<Link
|
||||
hrefAttrs={{
|
||||
// On web, launch the link in a new tab.
|
||||
target: "_blank",
|
||||
}}
|
||||
{...props}
|
||||
// @ts-expect-error href is not a valid prop for Link, but we're using it here to pass the URL to the web browser.
|
||||
href={props.href}
|
||||
onPress={(e) => {
|
||||
if (Platform.OS !== "web") {
|
||||
// Prevent the default behavior of linking to the default browser on native.
|
||||
e.preventDefault();
|
||||
// Open the link in an in-app browser.
|
||||
WebBrowser.openBrowserAsync(props.href).catch((error) => {
|
||||
console.error("Failed to open browser", error);
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import { FontAwesome } from '@expo/vector-icons';
|
||||
import { FontAwesome } from "@expo/vector-icons";
|
||||
|
||||
import Colors from '@/constants/Colors';
|
||||
import Colors from "@movie-web/tailwind-config/colors";
|
||||
|
||||
type Props = {
|
||||
focused?: boolean;
|
@@ -1,7 +1,7 @@
|
||||
import { Image, View } from 'react-native';
|
||||
import { Image, View } from "react-native";
|
||||
|
||||
import { Text } from '@/components/ui/Text';
|
||||
import { TMDB_POSTER_PATH } from '@/constants/General';
|
||||
import { TMDB_POSTER_PATH } from "~/app/constants/General";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function Item() {
|
||||
return (
|
@@ -1,20 +1,20 @@
|
||||
import { View } from 'react-native';
|
||||
import { View } from "react-native";
|
||||
|
||||
import { Text } from '@/components/ui/Text';
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
title?: React.ReactNode | string;
|
||||
subtitle?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
export default function ScreenLayout({ title, subtitle, children }: Props) {
|
||||
return (
|
||||
<View className="bg-shade-900 flex-1 p-12">
|
||||
{typeof title === 'string' && (
|
||||
{typeof title === "string" && (
|
||||
<Text className="text-2xl font-bold">{title}</Text>
|
||||
)}
|
||||
{typeof title !== 'string' && title}
|
||||
{typeof title !== "string" && title}
|
||||
<Text className="mt-1 text-sm font-bold">{subtitle}</Text>
|
||||
<View className="py-3">{children}</View>
|
||||
</View>
|
18
apps/expo/src/app/components/ui/Text.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { TextProps } from "react-native";
|
||||
import { Text as RNText } from "react-native";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "~/app/lib/utils";
|
||||
|
||||
const textVariants = cva("text-white");
|
||||
|
||||
export function Text({ className, ...props }: TextProps) {
|
||||
return (
|
||||
<RNText
|
||||
className={cn(className, textVariants(), {
|
||||
"font-sans": !className?.includes("font-"),
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
7
apps/expo/src/app/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ClassValue } from "clsx";
|
||||
import { clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
28
apps/expo/tailwind.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
// @ts-expect-error - no types
|
||||
import nativewind from "nativewind/preset";
|
||||
|
||||
import baseConfig from "@movie-web/tailwind-config/native";
|
||||
|
||||
export default {
|
||||
content: ["./src/**/*.{ts,tsx}"],
|
||||
presets: [
|
||||
baseConfig,
|
||||
nativewind,
|
||||
{
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ["OpenSansRegular"],
|
||||
thin: ["OpenSansLight"],
|
||||
normal: ["OpenSansRegular"],
|
||||
medium: ["OpenSansMedium"],
|
||||
semibold: ["OpenSansSemiBold"],
|
||||
bold: ["OpenSansBold"],
|
||||
extrabold: ["OpenSansExtra"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
} satisfies Config;
|
16
apps/expo/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": ["@movie-web/tsconfig/base.json"],
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"],
|
||||
"~/components/*": ["./src/app/components/*"]
|
||||
},
|
||||
"jsx": "react-native",
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||
"types": ["nativewind/types"]
|
||||
},
|
||||
"include": ["src", "*.ts", "*.js", ".expo/types/**/*.ts", "expo-env.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
},
|
||||
],
|
||||
],
|
||||
"plugins": [],
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"testRunner": {
|
||||
"args": {
|
||||
"$0": "jest",
|
||||
"config": "./jest.config.json"
|
||||
},
|
||||
"jest": {
|
||||
"setupTimeout": 120000
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"ios.debug": {
|
||||
"type": "ios.app",
|
||||
"build": "cd ../../apps/mobile/ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"binaryPath": "../../apps/mobile/ios/build/Build/Products/Debug-iphonesimulator/movieweb.app"
|
||||
},
|
||||
"ios.release": {
|
||||
"type": "ios.app",
|
||||
"build": "cd ../../apps/mobile/ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"binaryPath": "../../apps/mobile/ios/build/Build/Products/Release-iphonesimulator/movieweb.app"
|
||||
},
|
||||
|
||||
"ios.local": {
|
||||
"type": "ios.app",
|
||||
"build": "pnpm exec nx run mobile:build --platform ios --profile preview --wait --local --no-interactive --output=../../apps/mobile/dist/movieweb.tar.gz",
|
||||
"binaryPath": "../../apps/mobile/dist/movieweb.app"
|
||||
},
|
||||
|
||||
"android.debug": {
|
||||
"type": "android.apk",
|
||||
"build": "cd ../../apps/mobile/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug",
|
||||
"binaryPath": "../../apps/mobile/android/app/build/outputs/apk/debug/app-debug.apk"
|
||||
},
|
||||
"android.release": {
|
||||
"type": "android.apk",
|
||||
"build": "cd ../../apps/mobile/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release",
|
||||
"binaryPath": "../../apps/mobile/android/app/build/outputs/apk/release/app-release.apk"
|
||||
},
|
||||
|
||||
"android.local": {
|
||||
"type": "android.apk",
|
||||
"build": "pnpm exec nx run mobile:build --platform android --profile preview --wait --local --no-interactive --output=../../apps/mobile/dist/movieweb.apk",
|
||||
"binaryPath": "../../apps/mobile/dist/movieweb.apk"
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"simulator": {
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 14"
|
||||
}
|
||||
},
|
||||
"emulator": {
|
||||
"type": "android.emulator",
|
||||
"device": {
|
||||
"avdName": "Pixel_4a_API_30"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"ios.sim.release": {
|
||||
"device": "simulator",
|
||||
"app": "ios.release"
|
||||
},
|
||||
"ios.sim.debug": {
|
||||
"device": "simulator",
|
||||
"app": "ios.debug"
|
||||
},
|
||||
|
||||
"ios.sim.local": {
|
||||
"device": "simulator",
|
||||
"app": "ios.local"
|
||||
},
|
||||
|
||||
"android.emu.release": {
|
||||
"device": "emulator",
|
||||
"app": "android.release"
|
||||
},
|
||||
"android.emu.debug": {
|
||||
"device": "emulator",
|
||||
"app": "android.debug"
|
||||
},
|
||||
|
||||
"android.emu.local": {
|
||||
"device": "emulator",
|
||||
"app": "android.local"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.js"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"preset": "../../jest.preset",
|
||||
"rootDir": ".",
|
||||
"testMatch": [
|
||||
"<rootDir>/src/**/*.test.ts?(x)",
|
||||
"<rootDir>/src/**/*.spec.ts?(x)"
|
||||
],
|
||||
"testTimeout": 120000,
|
||||
"maxWorkers": 1,
|
||||
"globalSetup": "detox/runners/jest/globalSetup",
|
||||
"globalTeardown": "detox/runners/jest/globalTeardown",
|
||||
"reporters": ["detox/runners/jest/reporter"],
|
||||
"testEnvironment": "detox/runners/jest/testEnvironment",
|
||||
"verbose": true,
|
||||
"setupFilesAfterEnv": ["<rootDir>/test-setup.ts"],
|
||||
"transform": {
|
||||
"^.+\\.(ts|js|html)$": [
|
||||
"ts-jest",
|
||||
{ "tsconfig": "<rootDir>/tsconfig.e2e.json" }
|
||||
]
|
||||
}
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"name": "mobile-e2e",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "apps/mobile-e2e/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"build-ios": {
|
||||
"executor": "@nx/detox:build",
|
||||
"options": {
|
||||
"detoxConfiguration": "ios.sim.local"
|
||||
},
|
||||
"configurations": {
|
||||
"bare": {
|
||||
"detoxConfiguration": "ios.sim.debug"
|
||||
},
|
||||
"production": {
|
||||
"detoxConfiguration": "ios.sim.release"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test-ios": {
|
||||
"executor": "@nx/detox:test",
|
||||
"options": {
|
||||
"detoxConfiguration": "ios.sim.local",
|
||||
"buildTarget": "mobile-e2e:build-ios"
|
||||
},
|
||||
"configurations": {
|
||||
"bare": {
|
||||
"detoxConfiguration": "ios.sim.debug",
|
||||
"buildTarget": "mobile-e2e:build-ios:bare"
|
||||
},
|
||||
"production": {
|
||||
"detoxConfiguration": "ios.sim.release",
|
||||
"buildTarget": "mobile-e2e:build-ios:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-android": {
|
||||
"executor": "@nx/detox:build",
|
||||
"options": {
|
||||
"detoxConfiguration": "android.emu.local"
|
||||
},
|
||||
"configurations": {
|
||||
"bare": {
|
||||
"detoxConfiguration": "android.emu.debug"
|
||||
},
|
||||
"production": {
|
||||
"detoxConfiguration": "android.emu.release"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test-android": {
|
||||
"executor": "@nx/detox:test",
|
||||
"options": {
|
||||
"detoxConfiguration": "android.emu.local",
|
||||
"buildTarget": "mobile-e2e:build-android"
|
||||
},
|
||||
"configurations": {
|
||||
"bare": {
|
||||
"detoxConfiguration": "android.emu.debug",
|
||||
"buildTarget": "mobile-e2e:build-android:bare"
|
||||
},
|
||||
"production": {
|
||||
"detoxConfiguration": "android.emu.release",
|
||||
"buildTarget": "mobile-e2e:build-android:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"]
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"implicitDependencies": ["mobile"]
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
import { device, element, by, expect } from 'detox';
|
||||
|
||||
describe('movieweb', () => {
|
||||
beforeEach(async () => {
|
||||
await device.reloadReactNative();
|
||||
});
|
||||
|
||||
it('should display welcome message', async () => {
|
||||
await expect(element(by.id('heading'))).toHaveText('Welcome movie-web 👋');
|
||||
});
|
||||
});
|
@@ -1,5 +0,0 @@
|
||||
import { device } from 'detox';
|
||||
|
||||
beforeAll(async () => {
|
||||
await device.launchApp();
|
||||
});
|
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"sourceMap": false,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"allowJs": true,
|
||||
"types": ["node", "jest", "detox"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js"]
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.e2e.json"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['../../.eslintrc.js'],
|
||||
ignorePatterns: ['/*.js', '/*.d.ts'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
files: ['*.js', '*.jsx'],
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
'react/jsx-uses-react': 'off',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/require-default-props': 'off',
|
||||
'react/destructuring-assignment': 'off',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto',
|
||||
},
|
||||
],
|
||||
'react/jsx-filename-extension': [
|
||||
'error',
|
||||
{ extensions: ['.js', '.tsx', '.jsx'] },
|
||||
],
|
||||
'react/jsx-props-no-spreading': 'off',
|
||||
'react/no-unstable-nested-components': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
},
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.js"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
44
apps/mobile/.gitignore
vendored
@@ -1,44 +0,0 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
android/
|
||||
ios/
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
|
||||
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
|
||||
# The following patterns were generated by expo-cli
|
||||
|
||||
expo-env.d.ts
|
||||
# @end expo-cli
|
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "movie-web",
|
||||
"slug": "mw-mobile",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"scheme": "dev.movieweb.app",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"splash": {
|
||||
"image": "./assets/images/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"jsEngine": "jsc",
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "dev.movieweb.app"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#FFFFFF"
|
||||
},
|
||||
"package": "dev.movieweb.app"
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/images/favicon.png",
|
||||
"bundler": "metro"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
[
|
||||
"@config-plugins/detox",
|
||||
{
|
||||
"skipProguard": false,
|
||||
"subdomains": ["10.0.2.2", "localhost"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||
import {
|
||||
DarkTheme,
|
||||
DefaultTheme,
|
||||
ThemeProvider,
|
||||
} from '@react-navigation/native';
|
||||
import { useFonts } from 'expo-font';
|
||||
import { SplashScreen, Stack } from 'expo-router';
|
||||
import { useEffect } from 'react';
|
||||
import { useColorScheme } from 'react-native';
|
||||
|
||||
import Colors from '@/constants/Colors';
|
||||
|
||||
import './styles/global.css';
|
||||
|
||||
export {
|
||||
// Catch any errors thrown by the Layout component.
|
||||
ErrorBoundary,
|
||||
} from 'expo-router';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export const unstable_settings = {
|
||||
// Ensure that reloading on `/modal` keeps a back button present.
|
||||
initialRouteName: '(tabs)/index',
|
||||
};
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded, error] = useFonts({
|
||||
OpenSansRegular: require('../assets/fonts/OpenSans-Regular.ttf'),
|
||||
OpenSansLight: require('../assets/fonts/OpenSans-Light.ttf'),
|
||||
OpenSansMedium: require('../assets/fonts/OpenSans-Medium.ttf'),
|
||||
OpenSansBold: require('../assets/fonts/OpenSans-Bold.ttf'),
|
||||
OpenSansSemiBold: require('../assets/fonts/OpenSans-SemiBold.ttf'),
|
||||
OpenSansExtra: require('../assets/fonts/OpenSans-ExtraBold.ttf'),
|
||||
...FontAwesome.font,
|
||||
});
|
||||
|
||||
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
|
||||
useEffect(() => {
|
||||
if (error) throw error;
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RootLayoutNav />;
|
||||
}
|
||||
|
||||
function RootLayoutNav() {
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
gestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor: Colors.background,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
import { Link } from 'expo-router';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
export function ExternalLink(
|
||||
props: Omit<React.ComponentProps<typeof Link>, 'href'> & { href: string },
|
||||
) {
|
||||
return (
|
||||
<Link
|
||||
hrefAttrs={{
|
||||
// On web, launch the link in a new tab.
|
||||
target: '_blank',
|
||||
}}
|
||||
{...props}
|
||||
href={props.href}
|
||||
onPress={(e) => {
|
||||
if (Platform.OS !== 'web') {
|
||||
// Prevent the default behavior of linking to the default browser on native.
|
||||
e.preventDefault();
|
||||
// Open the link in an in-app browser.
|
||||
WebBrowser.openBrowserAsync(props.href as string);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { Text as RNText, TextProps } from 'react-native';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const textVariants = cva('text-white');
|
||||
|
||||
export function Text({ className, ...props }: TextProps) {
|
||||
return (
|
||||
<RNText
|
||||
className={cn(className, textVariants(), {
|
||||
'font-sans': !className?.includes('font-'),
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
export default {
|
||||
primary: {
|
||||
100: '#C082FF',
|
||||
300: '#8D44D6',
|
||||
400: '#7831BF',
|
||||
},
|
||||
secondary: {
|
||||
50: '#676790',
|
||||
200: '#3F3F60',
|
||||
300: '#32324F',
|
||||
700: '#131322',
|
||||
},
|
||||
background: '#0a0a12',
|
||||
};
|
@@ -1,6 +0,0 @@
|
||||
import { ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: [
|
||||
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
||||
'nativewind/babel',
|
||||
],
|
||||
plugins: [
|
||||
// Required for expo-router
|
||||
'expo-router/babel',
|
||||
'react-native-reanimated/plugin',
|
||||
],
|
||||
};
|
||||
};
|
@@ -1,13 +0,0 @@
|
||||
module.exports = {
|
||||
displayName: 'mobile',
|
||||
resolver: '@nx/jest/plugins/resolver',
|
||||
preset: 'jest-expo',
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)',
|
||||
],
|
||||
moduleFileExtensions: ['ts', 'js', 'html', 'tsx', 'jsx'],
|
||||
setupFilesAfterEnv: ['<rootDir>/test-setup.ts'],
|
||||
moduleNameMapper: {
|
||||
'\\.svg$': '@nx/expo/plugins/jest/svg-mock',
|
||||
},
|
||||
};
|
@@ -1,48 +0,0 @@
|
||||
const { withNxMetro } = require('@nx/expo');
|
||||
const { getDefaultConfig } = require('@expo/metro-config');
|
||||
const { mergeConfig } = require('metro-config');
|
||||
const { withNativeWind } = require('nativewind/metro');
|
||||
const exclusionList = require('metro-config/src/defaults/exclusionList');
|
||||
|
||||
const defaultConfig = getDefaultConfig(__dirname, {
|
||||
isCSSEnabled: true,
|
||||
});
|
||||
const { assetExts, sourceExts } = defaultConfig.resolver;
|
||||
|
||||
/**
|
||||
* Metro configuration
|
||||
* https://facebook.github.io/metro/docs/configuration
|
||||
*
|
||||
* @type {import('metro-config').MetroConfig}
|
||||
*/
|
||||
const customConfig = {
|
||||
transformer: {
|
||||
babelTransformerPath: require.resolve('react-native-svg-transformer'),
|
||||
},
|
||||
resolver: {
|
||||
assetExts: assetExts.filter((ext) => ext !== 'svg'),
|
||||
sourceExts: [...sourceExts, 'svg', 'mjs'],
|
||||
blockList: exclusionList([/^(?!.*node_modules).*\/dist\/.*/]),
|
||||
// unstable_enableSymlinks: true,
|
||||
// unstable_enablePackageExports: true,
|
||||
},
|
||||
};
|
||||
|
||||
const nativeWindConfig = withNativeWind(
|
||||
mergeConfig(defaultConfig, customConfig),
|
||||
{
|
||||
input: './app/styles/global.css',
|
||||
configPath: './tailwind.config.ts',
|
||||
},
|
||||
);
|
||||
|
||||
const nxConfig = withNxMetro(nativeWindConfig, {
|
||||
// Change this to true to see debugging info.
|
||||
// Useful if you have issues resolving modules
|
||||
debug: false,
|
||||
// all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx', 'json'
|
||||
extensions: [],
|
||||
// Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules)
|
||||
watchFolders: [],
|
||||
});
|
||||
module.exports = nxConfig;
|
@@ -1,77 +0,0 @@
|
||||
{
|
||||
"name": "mobile",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "*",
|
||||
"@expo/metro-config": "*",
|
||||
"@expo/vector-icons": "^13.0.0",
|
||||
"@nx/expo": "*",
|
||||
"@react-navigation/native": "^6.1.9",
|
||||
"@rnx-kit/metro-config": "*",
|
||||
"@rnx-kit/metro-resolver-symlinks": "*",
|
||||
"@testing-library/jest-native": "*",
|
||||
"@testing-library/react-native": "*",
|
||||
"@types/react": "*",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"eslint-plugin-react": "*",
|
||||
"eslint-plugin-react-hooks": "*",
|
||||
"expo": "*",
|
||||
"expo-font": "^11.4.0",
|
||||
"expo-linking": "^5.0.2",
|
||||
"expo-router": "^2.0.14",
|
||||
"expo-splash-screen": "~0.20.5",
|
||||
"expo-status-bar": "*",
|
||||
"expo-system-ui": "^2.6.0",
|
||||
"expo-web-browser": "^12.5.0",
|
||||
"jest": "*",
|
||||
"jest-expo": "*",
|
||||
"metro": "*",
|
||||
"metro-config": "*",
|
||||
"nativewind": "^4.0.23",
|
||||
"pod-install": "*",
|
||||
"react": "*",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "^2.14.1",
|
||||
"react-native-reanimated": "~3.3.0",
|
||||
"react-native-safe-area-context": "^4.8.2",
|
||||
"react-native-screens": "^3.29.0",
|
||||
"react-native-svg": "*",
|
||||
"react-native-svg-transformer": "*",
|
||||
"react-native-web": "^0.19.10",
|
||||
"react-test-renderer": "*",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "*",
|
||||
"prettier-plugin-tailwindcss": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"eas-build-pre-install": "cd ../../ && node tools/scripts/eas-build-pre-install.mjs . apps/mobile && cp pnpm-lock.yaml apps/mobile",
|
||||
"eas-build-post-install": "cd ../../ && node tools/scripts/eas-build-post-install.mjs . apps/mobile",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"test": "jest --watchAll",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@rnx-kit/metro-config": "^1.3.14",
|
||||
"@rnx-kit/metro-resolver-symlinks": "^0.1.34",
|
||||
"@types/react": "18.0.28",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"jest": "^29.4.1",
|
||||
"jest-expo": "~49.0.0",
|
||||
"pod-install": "^0.1.39",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "~5.2.2"
|
||||
}
|
||||
}
|
@@ -1,95 +0,0 @@
|
||||
{
|
||||
"name": "mobile",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "apps/mobile/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"start": {
|
||||
"executor": "@nx/expo:start",
|
||||
"dependsOn": ["ensure-symlink", "sync-deps"],
|
||||
"options": {
|
||||
"port": 8081,
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "nx start mobile"
|
||||
}
|
||||
},
|
||||
"run-ios": {
|
||||
"executor": "@nx/expo:run",
|
||||
"dependsOn": ["ensure-symlink", "sync-deps"],
|
||||
"options": {
|
||||
"platform": "ios"
|
||||
}
|
||||
},
|
||||
"run-android": {
|
||||
"executor": "@nx/expo:run",
|
||||
"dependsOn": ["ensure-symlink", "sync-deps"],
|
||||
"options": {
|
||||
"platform": "android"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "@nx/expo:build",
|
||||
"options": {}
|
||||
},
|
||||
"submit": {
|
||||
"executor": "@nx/expo:submit",
|
||||
"options": {}
|
||||
},
|
||||
"build-list": {
|
||||
"executor": "@nx/expo:build-list",
|
||||
"options": {}
|
||||
},
|
||||
"sync-deps": {
|
||||
"executor": "@nx/expo:sync-deps",
|
||||
"options": {}
|
||||
},
|
||||
"ensure-symlink": {
|
||||
"executor": "@nx/expo:ensure-symlink",
|
||||
"options": {}
|
||||
},
|
||||
"prebuild": {
|
||||
"executor": "@nx/expo:prebuild",
|
||||
"dependsOn": ["ensure-symlink", "sync-deps"],
|
||||
"options": {}
|
||||
},
|
||||
"install": {
|
||||
"executor": "@nx/expo:install",
|
||||
"options": {}
|
||||
},
|
||||
"update": {
|
||||
"executor": "@nx/expo:update",
|
||||
"options": {}
|
||||
},
|
||||
"export": {
|
||||
"executor": "@nx/expo:export",
|
||||
"dependsOn": ["ensure-symlink", "sync-deps"],
|
||||
"options": {
|
||||
"platform": "all",
|
||||
"outputDir": "../../dist/apps/mobile"
|
||||
}
|
||||
},
|
||||
"export-web": {
|
||||
"executor": "@nx/expo:export",
|
||||
"options": {
|
||||
"bundler": "metro"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"]
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "apps/mobile/jest.config.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
// @ts-expect-error - no types
|
||||
import nativewind from 'nativewind/preset';
|
||||
import type { Config } from 'tailwindcss';
|
||||
import colors from './app/constants/Colors';
|
||||
|
||||
export default {
|
||||
content: ['./app/**/*.{js,jsx,ts,tsx}'],
|
||||
presets: [
|
||||
nativewind,
|
||||
{
|
||||
theme: {
|
||||
extend: {
|
||||
colors,
|
||||
fontFamily: {
|
||||
sans: ['OpenSansRegular'],
|
||||
thin: ['OpenSansLight'],
|
||||
normal: ['OpenSansRegular'],
|
||||
medium: ['OpenSansMedium'],
|
||||
semibold: ['OpenSansSemiBold'],
|
||||
bold: ['OpenSansBold'],
|
||||
extrabold: ['OpenSansExtra'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
} satisfies Config;
|
@@ -1 +0,0 @@
|
||||
import '@testing-library/jest-native/extend-expect';
|
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"],
|
||||
},
|
||||
"files": ["../../node_modules/@nx/expo/typings/svg.d.ts"],
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.tsx",
|
||||
"test-setup.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"tailwind.config.ts"
|
||||
]
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "react-native",
|
||||
"lib": ["dom", "esnext"],
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"allowJs": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./app/*"
|
||||
],
|
||||
}
|
||||
},
|
||||
"files": [],
|
||||
"include": [".expo/types/**/*.ts", "expo-env.d.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json",
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json",
|
||||
},
|
||||
],
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|