Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#705 @taxi/app init #704

Merged
merged 12 commits into from
Jan 30, 2024
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node-linker=hoisted
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"web": "pnpm -F @taxi/web",
"app": "pnpm --filter @taxi/app",
"web": "pnpm --filter @taxi/web",
"start:app": "pnpm -r --stream start",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pnpm start:app을 할 경우 web과 app이 동시에 시작됩니다.

"start:app:prod": "NODE_ENV=production pnpm --filter @taxi/app start",
"start:web": "pnpm --filter @taxi/web... start",
"build:all": "pnpm -r build",
"build:web": "pnpm --filter @taxi/web... build",
"test": "pnpm -r test",
Expand Down
35 changes: 35 additions & 0 deletions packages/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/

# 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
40 changes: 40 additions & 0 deletions packages/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { ConfigContext, ExpoConfig } from "@expo/config";
import ip from "internal-ip";

const isDev = process.env.NODE_ENV === "development";

export default ({ config }: ConfigContext): Partial<ExpoConfig> => ({
...config,
name: "Taxi for KAIST",
slug: "Taxi-for-KAIST",
version: "1.0.0",
orientation: "portrait",
icon: "./assets/icon.png",
userInterfaceStyle: "light",
splash: {
image: "./assets/splash.png",
resizeMode: "contain",
backgroundColor: "#ffffff",
},
assetBundlePatterns: ["**/*"],
ios: {
supportsTablet: true,
},
android: {
adaptiveIcon: {
foregroundImage: "./assets/adaptive-icon.png",
backgroundColor: "#ffffff",
},
},
web: {
favicon: "./assets/favicon.png",
},
experiments: {
tsconfigPaths: true,
},
extra: {
SERVER_URL: isDev
? `http://${ip.v4.sync()}:3000`
: "https://taxi.sparcs.org",
Comment on lines +36 to +38
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개발 환경에서는 로컬 네트워크의 택시 웹이 뜨도록 하였습니다.
모바일 기기(expo go)를 이용하여 개발하는 경우 localhost:3000으로는 접속하지 못하기 때문에 local ip를 가져오도록 하였습니다.

},
});
Binary file added packages/app/assets/adaptive-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/app/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/app/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/app/assets/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/app/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
26 changes: 26 additions & 0 deletions packages/app/metro.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Learn more https://docs.expo.dev/guides/monorepos
const { getDefaultConfig } = require('expo/metro-config');
const { FileStore } = require('metro-cache');
const path = require('path');

const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '../..');

const config = getDefaultConfig(projectRoot);

// #1 - Watch all files in the monorepo
config.watchFolders = [workspaceRoot];
// #3 - Force resolving nested modules to the folders below
config.resolver.disableHierarchicalLookup = true;
// #2 - Try resolving with project modules first, then workspace modules
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
];

// Use turborepo to restore the cache when possible
config.cacheStores = [
new FileStore({ root: path.join(projectRoot, 'node_modules', '.cache', 'metro') }),
];

module.exports = config;
1 change: 1 addition & 0 deletions packages/app/node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@types/node" />
32 changes: 32 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@taxi/app",
"version": "1.0.0",
"main": "src/main.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/stack": "^6.3.20",
"expo": "~49.0.15",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-native": "0.72.6",
"react-native-webview": "^13.6.4",
"shallow-equal": "^3.1.0",
"url-pattern": "^1.0.3",
"uuid": "3.4.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/node": "^20.11.5",
"@types/react": "~18.2.14",
"internal-ip": "^6.2.0",
"typescript": "^5.1.3"
},
"private": true
}
15 changes: 15 additions & 0 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { RootStack } from "@/screens";
import { NavigationContainer } from "@react-navigation/native";
import { StatusBar } from "expo-status-bar";
import { View } from "react-native";

export function App() {
return (
<View style={{ flex: 1 }}>
<NavigationContainer>
<RootStack />
</NavigationContainer>
<StatusBar style="auto" />
</View>
);
}
44 changes: 44 additions & 0 deletions packages/app/src/components/TaxiWebView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { env } from "@/env";
import { mapWebRoutes } from "@/navigation/web";
import { isSameScreen } from "@/utils/navigation";
import { useIsFocused, useNavigation } from "@react-navigation/native";
import React, { useCallback, useMemo, useRef } from "react";
import { Platform } from "react-native";
import { WebView, type WebViewNavigation } from "react-native-webview";

type TaxiWebViewProps = {
path: string;
};

export const TaxiWebView: React.FC<TaxiWebViewProps> = ({ path }) => {
const uri = useMemo(
() => (path.startsWith("/") ? env.SERVER_URL + path : path),
[path]
);
const ref = useRef<WebView>(null);
const currentScreen = useMemo(() => mapWebRoutes(uri), [uri]);
const isFocused = useIsFocused();
const navigation = useNavigation();

const onNavigationStateChange = useCallback(
(event: WebViewNavigation) => {
const screen = mapWebRoutes(event.url);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uri가 바뀔 때 그 uri가 매칭되는 스크린을 받아옵니다.

if (!isFocused || isSameScreen(currentScreen, screen)) return;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매칭된 스크린이 현재 스크린과 동일한 경우, 새로운 route navigation을 진행하지 않습니다.


navigation.navigate(...screen);
ref.current?.stopLoading();
ref.current?.goBack();
Comment on lines +28 to +30
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 스크린으로 이동하는 경우, 현재 WebView에서의 routing은 취소하고, native에서 해당하는 스크린으로 navigation합니다. (tab navigation, stack navigation 모두 가능)

},
[isFocused, currentScreen, navigation]
);
Comment on lines +23 to +33
Copy link
Member Author

@SnowSuno SnowSuno Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onNavigationStateChange 리스너를 이용하여 새로운 route로 이동하는 경우 그 페이지에 해당하는 native app에서의 navigating을 진행해 줍니다.

나중에 앱과 웹의 코드베이스가 공유되는 경우에는 다른 접근을 취하는게 좋을 것 같습니다.


return (
<WebView
cacheEnabled
ref={ref}
userAgent={`taxi-app-webview/${Platform.OS}`}
source={{ uri }}
onNavigationStateChange={onNavigationStateChange}
/>
);
};
6 changes: 6 additions & 0 deletions packages/app/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Constants from "expo-constants";

export const env = {
SERVER_URL: "https://taxi.sparcs.org",
...Constants.expoConfig?.extra,
};
4 changes: 4 additions & 0 deletions packages/app/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { registerRootComponent } from "expo";
import { App }from "./App";

registerRootComponent(App);
41 changes: 41 additions & 0 deletions packages/app/src/navigation/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Native tab navigation is disabled temporarily. (handled inside webview)
* Uncomment the commented code to enable native tab navigation.
*/
// import type { BottomTabScreenProps } from "@react-navigation/bottom-tabs";
// import type {
// CompositeScreenProps,
// NavigatorScreenParams,
// } from "@react-navigation/native";
import type { StackScreenProps } from "@react-navigation/stack";

// export type HomeTabParamList = {
// Home: undefined;
// Search: undefined;
// AddRoom: undefined;
// MyRoom: undefined;
// MyPage: undefined;
// };

export type RootStackParamList = {
// HomeTab: NavigatorScreenParams<HomeTabParamList>;
Home: undefined;
Event: { eventName: string };
Chatting: { roomId: string };
Web: { uri: string };
};

export type RootStackScreenProps<T extends keyof RootStackParamList> =
StackScreenProps<RootStackParamList, T>;

// export type HomeTabScreenProps<T extends keyof HomeTabParamList> =
// CompositeScreenProps<
// BottomTabScreenProps<HomeTabParamList, T>,
// RootStackScreenProps<keyof RootStackParamList>
// >;

declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
13 changes: 13 additions & 0 deletions packages/app/src/navigation/web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { route, routes, screen } from "@/utils/navigation";

export const mapWebRoutes = routes([
route("/", () => screen("Home")),
route("/home(/:roomId)", () => screen("Home")),
route("/event/:eventName", ({ eventName }) => screen("Event", { eventName })),
route("/search", () => screen("Home")),
route("/addroom", () => screen("Home")),
route("/myroom", () => screen("Home")),
route("/myroom/:roomId", ({ roomId }) => screen("Chatting", { roomId })),
route("/mypage", () => screen("Home")),
route("/chatting/:roomId", ({ roomId }) => screen("Chatting", { roomId })),
]);
17 changes: 17 additions & 0 deletions packages/app/src/screens/Chatting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { RootStackScreenProps } from "@/navigation/types";
import React from "react";
import { KeyboardAvoidingView, Platform } from "react-native";

import { TaxiWebView } from "@/components/TaxiWebView";

export const Chatting: React.FC<RootStackScreenProps<"Chatting">> = ({
route: { params },
}) => (
<KeyboardAvoidingView
behavior={Platform.select({ ios: "padding", android: "height" })}
style={{ flex: 1 }}
// @TODO: Remove keyboard avoiding in web
>
<TaxiWebView path={`/chatting/${params.roomId}`} />
</KeyboardAvoidingView>
);
8 changes: 8 additions & 0 deletions packages/app/src/screens/Event.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RootStackScreenProps } from "@/navigation/types";
import React from "react";

import { TaxiWebView } from "@/components/TaxiWebView";

export const Event: React.FC<RootStackScreenProps<"Event">> = ({
route: { params },
}) => <TaxiWebView path={`/event/${params.eventName}`} />;
8 changes: 8 additions & 0 deletions packages/app/src/screens/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RootStackScreenProps } from "@/navigation/types";
import React from "react";

import { TaxiWebView } from "@/components/TaxiWebView";

export const Home: React.FC<RootStackScreenProps<"Home">> = () => (
<TaxiWebView path="/home" />
);
8 changes: 8 additions & 0 deletions packages/app/src/screens/HomeTab/AddRoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HomeTabScreenProps } from "@/navigation/types";
import React from "react";

import { TaxiWebView } from "@/components/TaxiWebView";

export const AddRoom: React.FC<HomeTabScreenProps<"AddRoom">> = () => (
<TaxiWebView path="/addroom" />
);
8 changes: 8 additions & 0 deletions packages/app/src/screens/HomeTab/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HomeTabScreenProps } from "@/navigation/types";
import React from "react";

import { TaxiWebView } from "@/components/TaxiWebView";

export const Home: React.FC<HomeTabScreenProps<"Home">> = () => (
<TaxiWebView path="/home" />
);
8 changes: 8 additions & 0 deletions packages/app/src/screens/HomeTab/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HomeTabScreenProps } from "@/navigation/types";
import React from "react";

import { TaxiWebView } from "@/components/TaxiWebView";

export const MyPage: React.FC<HomeTabScreenProps<"MyPage">> = () => (
<TaxiWebView path="/mypage" />
);
8 changes: 8 additions & 0 deletions packages/app/src/screens/HomeTab/MyRoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HomeTabScreenProps } from "@/navigation/types";
import React from "react";

import { TaxiWebView } from "@/components/TaxiWebView";

export const MyRoom: React.FC<HomeTabScreenProps<"MyRoom">> = () => (
<TaxiWebView path="/myroom" />
);
8 changes: 8 additions & 0 deletions packages/app/src/screens/HomeTab/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HomeTabScreenProps } from "@/navigation/types";
import React from "react";

import { TaxiWebView } from "@/components/TaxiWebView";

export const Search: React.FC<HomeTabScreenProps<"Search">> = () => (
<TaxiWebView path="/search" />
);
25 changes: 25 additions & 0 deletions packages/app/src/screens/HomeTab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { HomeTabParamList } from "@/navigation/types";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import React from "react";

import { AddRoom } from "./AddRoom";
import { Home } from "./Home";
import { MyPage } from "./MyPage";
import { MyRoom } from "./MyRoom";
import { Search } from "./Search";

const Tab = createBottomTabNavigator<HomeTabParamList>();

export const HomeTab: React.FC = () => (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="Home" options={{ title: "홈" }} component={Home} />
<Tab.Screen name="Search" options={{ title: "검색" }} component={Search} />
<Tab.Screen
name="AddRoom"
options={{ title: "개설" }}
component={AddRoom}
/>
<Tab.Screen name="MyRoom" options={{ title: "내 방" }} component={MyRoom} />
<Tab.Screen name="MyPage" options={{ title: "마이" }} component={MyPage} />
</Tab.Navigator>
);
Loading
Loading