first commit
This commit is contained in:
6
app/.expo-shared/assets.json
Normal file
6
app/.expo-shared/assets.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"e997a5256149a4b76e6bfd6cbf519c5e5a0f1d278a3d8fa1253022b03c90473b": true,
|
||||
"af683c96e0ffd2cf81287651c9433fa44debc1220ca7cb431fe482747f34a505": true,
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
||||
}
|
13
app/.gitignore
vendored
Normal file
13
app/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
node_modules/
|
||||
.expo/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
24
app/App.tsx
Normal file
24
app/App.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import "react-native-gesture-handler";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import React from "react";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
|
||||
import useCachedResources from "./hooks/useCachedResources";
|
||||
import useColorScheme from "./hooks/useColorScheme";
|
||||
import Navigation from "./navigation";
|
||||
|
||||
export default function App() {
|
||||
const isLoadingComplete = useCachedResources();
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
if (!isLoadingComplete) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<Navigation colorScheme={colorScheme} />
|
||||
<StatusBar />
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
}
|
34
app/app.json
Normal file
34
app/app.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "virtual-phone",
|
||||
"slug": "virtual-phone",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "myapp",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"splash": {
|
||||
"image": "./assets/images/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
BIN
app/assets/fonts/SpaceMono-Regular.ttf
Executable file
BIN
app/assets/fonts/SpaceMono-Regular.ttf
Executable file
Binary file not shown.
BIN
app/assets/images/adaptive-icon.png
Normal file
BIN
app/assets/images/adaptive-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
app/assets/images/favicon.png
Normal file
BIN
app/assets/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
app/assets/images/icon.png
Normal file
BIN
app/assets/images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
app/assets/images/splash.png
Normal file
BIN
app/assets/images/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
7
app/babel.config.js
Normal file
7
app/babel.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: ["react-native-reanimated/plugin"],
|
||||
};
|
||||
};
|
80
app/components/EditScreenInfo.tsx
Normal file
80
app/components/EditScreenInfo.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import React from "react";
|
||||
import { StyleSheet, TouchableOpacity } from "react-native";
|
||||
|
||||
import Colors from "../constants/Colors";
|
||||
import { MonoText } from "./StyledText";
|
||||
import { Text, View } from "./Themed";
|
||||
|
||||
export default function EditScreenInfo({ path }: { path: string }) {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.getStartedContainer}>
|
||||
<Text
|
||||
style={styles.getStartedText}
|
||||
lightColor="rgba(0,0,0,0.8)"
|
||||
darkColor="rgba(255,255,255,0.8)">
|
||||
Open up the code for this screen:
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
|
||||
darkColor="rgba(255,255,255,0.05)"
|
||||
lightColor="rgba(0,0,0,0.05)">
|
||||
<MonoText>{path}</MonoText>
|
||||
</View>
|
||||
|
||||
<Text
|
||||
style={styles.getStartedText}
|
||||
lightColor="rgba(0,0,0,0.8)"
|
||||
darkColor="rgba(255,255,255,0.8)">
|
||||
Change any of the text, save the file, and your app will automatically update.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.helpContainer}>
|
||||
<TouchableOpacity onPress={handleHelpPress} style={styles.helpLink}>
|
||||
<Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
|
||||
Tap here if your app doesn't automatically update after making changes
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function handleHelpPress() {
|
||||
WebBrowser.openBrowserAsync(
|
||||
"https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet",
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
getStartedContainer: {
|
||||
alignItems: "center",
|
||||
marginHorizontal: 50,
|
||||
},
|
||||
homeScreenFilename: {
|
||||
marginVertical: 7,
|
||||
},
|
||||
codeHighlightContainer: {
|
||||
borderRadius: 3,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
getStartedText: {
|
||||
fontSize: 17,
|
||||
lineHeight: 24,
|
||||
textAlign: "center",
|
||||
},
|
||||
helpContainer: {
|
||||
marginTop: 15,
|
||||
marginHorizontal: 20,
|
||||
alignItems: "center",
|
||||
},
|
||||
helpLink: {
|
||||
paddingVertical: 15,
|
||||
},
|
||||
helpLinkText: {
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
7
app/components/StyledText.tsx
Normal file
7
app/components/StyledText.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Text, TextProps } from "./Themed";
|
||||
|
||||
export function MonoText(props: TextProps) {
|
||||
return <Text {...props} style={[props.style, { fontFamily: "space-mono" }]} />;
|
||||
}
|
46
app/components/Themed.tsx
Normal file
46
app/components/Themed.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Learn more about Light and Dark modes:
|
||||
* https://docs.expo.io/guides/color-schemes/
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { Text as DefaultText, View as DefaultView } from "react-native";
|
||||
|
||||
import Colors from "../constants/Colors";
|
||||
import useColorScheme from "../hooks/useColorScheme";
|
||||
|
||||
export function useThemeColor(
|
||||
props: { light?: string; dark?: string },
|
||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
|
||||
) {
|
||||
const theme = useColorScheme();
|
||||
const colorFromProps = props[theme];
|
||||
|
||||
if (colorFromProps) {
|
||||
return colorFromProps;
|
||||
} else {
|
||||
return Colors[theme][colorName];
|
||||
}
|
||||
}
|
||||
|
||||
type ThemeProps = {
|
||||
lightColor?: string;
|
||||
darkColor?: string;
|
||||
};
|
||||
|
||||
export type TextProps = ThemeProps & DefaultText["props"];
|
||||
export type ViewProps = ThemeProps & DefaultView["props"];
|
||||
|
||||
export function Text(props: TextProps) {
|
||||
const { style, lightColor, darkColor, ...otherProps } = props;
|
||||
const color = useThemeColor({ light: lightColor, dark: darkColor }, "text");
|
||||
|
||||
return <DefaultText style={[{ color }, style]} {...otherProps} />;
|
||||
}
|
||||
|
||||
export function View(props: ViewProps) {
|
||||
const { style, lightColor, darkColor, ...otherProps } = props;
|
||||
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, "background");
|
||||
|
||||
return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
|
||||
}
|
10
app/components/__tests__/StyledText-test.js
Normal file
10
app/components/__tests__/StyledText-test.js
Normal file
@ -0,0 +1,10 @@
|
||||
import * as React from "react";
|
||||
import renderer from "react-test-renderer";
|
||||
|
||||
import { MonoText } from "../StyledText";
|
||||
|
||||
it(`renders correctly`, () => {
|
||||
const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
19
app/constants/Colors.ts
Normal file
19
app/constants/Colors.ts
Normal file
@ -0,0 +1,19 @@
|
||||
const tintColorLight = "#2f95dc";
|
||||
const tintColorDark = "#fff";
|
||||
|
||||
export default {
|
||||
light: {
|
||||
text: "#000",
|
||||
background: "#fff",
|
||||
tint: tintColorLight,
|
||||
tabIconDefault: "#ccc",
|
||||
tabIconSelected: tintColorLight,
|
||||
},
|
||||
dark: {
|
||||
text: "#fff",
|
||||
background: "#000",
|
||||
tint: tintColorDark,
|
||||
tabIconDefault: "#ccc",
|
||||
tabIconSelected: tintColorDark,
|
||||
},
|
||||
};
|
12
app/constants/Layout.ts
Normal file
12
app/constants/Layout.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Dimensions } from "react-native";
|
||||
|
||||
const width = Dimensions.get("window").width;
|
||||
const height = Dimensions.get("window").height;
|
||||
|
||||
export default {
|
||||
window: {
|
||||
width,
|
||||
height,
|
||||
},
|
||||
isSmallDevice: width < 375,
|
||||
};
|
33
app/hooks/useCachedResources.ts
Normal file
33
app/hooks/useCachedResources.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import * as Font from "expo-font";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import * as React from "react";
|
||||
|
||||
export default function useCachedResources() {
|
||||
const [isLoadingComplete, setLoadingComplete] = React.useState(false);
|
||||
|
||||
// Load any resources or data that we need prior to rendering the app
|
||||
React.useEffect(() => {
|
||||
async function loadResourcesAndDataAsync() {
|
||||
try {
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
// Load fonts
|
||||
await Font.loadAsync({
|
||||
...Ionicons.font,
|
||||
"space-mono": require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
} catch (e) {
|
||||
// We might want to provide this error information to an error reporting service
|
||||
console.warn(e);
|
||||
} finally {
|
||||
setLoadingComplete(true);
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}
|
||||
|
||||
loadResourcesAndDataAsync();
|
||||
}, []);
|
||||
|
||||
return isLoadingComplete;
|
||||
}
|
8
app/hooks/useColorScheme.ts
Normal file
8
app/hooks/useColorScheme.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { ColorSchemeName, useColorScheme as _useColorScheme } from "react-native";
|
||||
|
||||
// The useColorScheme value is always either light or dark, but the built-in
|
||||
// type suggests that it can be null. This will not happen in practice, so this
|
||||
// makes it a bit easier to work with.
|
||||
export default function useColorScheme(): NonNullable<ColorSchemeName> {
|
||||
return _useColorScheme() as NonNullable<ColorSchemeName>;
|
||||
}
|
78
app/navigation/BottomTabNavigator.tsx
Normal file
78
app/navigation/BottomTabNavigator.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Learn more about createBottomTabNavigator:
|
||||
* https://reactnavigation.org/docs/bottom-tab-navigator
|
||||
*/
|
||||
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
import * as React from "react";
|
||||
|
||||
import Colors from "../constants/Colors";
|
||||
import useColorScheme from "../hooks/useColorScheme";
|
||||
import TabOneScreen from "../screens/TabOneScreen";
|
||||
import TabTwoScreen from "../screens/TabTwoScreen";
|
||||
import { BottomTabParamList, TabOneParamList, TabTwoParamList } from "../types";
|
||||
|
||||
const BottomTab = createBottomTabNavigator<BottomTabParamList>();
|
||||
|
||||
export default function BottomTabNavigator() {
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<BottomTab.Navigator
|
||||
initialRouteName="TabOne"
|
||||
tabBarOptions={{ activeTintColor: Colors[colorScheme].tint }}>
|
||||
<BottomTab.Screen
|
||||
name="TabOne"
|
||||
component={TabOneNavigator}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
|
||||
}}
|
||||
/>
|
||||
<BottomTab.Screen
|
||||
name="TabTwo"
|
||||
component={TabTwoNavigator}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
|
||||
}}
|
||||
/>
|
||||
</BottomTab.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
// You can explore the built-in icon families and icons on the web at:
|
||||
// https://icons.expo.fyi/
|
||||
function TabBarIcon(props: { name: React.ComponentProps<typeof Ionicons>["name"]; color: string }) {
|
||||
return <Ionicons size={30} style={{ marginBottom: -3 }} {...props} />;
|
||||
}
|
||||
|
||||
// Each tab has its own navigation stack, you can read more about this pattern here:
|
||||
// https://reactnavigation.org/docs/tab-based-navigation#a-stack-navigator-for-each-tab
|
||||
const TabOneStack = createStackNavigator<TabOneParamList>();
|
||||
|
||||
function TabOneNavigator() {
|
||||
return (
|
||||
<TabOneStack.Navigator>
|
||||
<TabOneStack.Screen
|
||||
name="TabOneScreen"
|
||||
component={TabOneScreen}
|
||||
options={{ headerTitle: "Tab One Title" }}
|
||||
/>
|
||||
</TabOneStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
const TabTwoStack = createStackNavigator<TabTwoParamList>();
|
||||
|
||||
function TabTwoNavigator() {
|
||||
return (
|
||||
<TabTwoStack.Navigator>
|
||||
<TabTwoStack.Screen
|
||||
name="TabTwoScreen"
|
||||
component={TabTwoScreen}
|
||||
options={{ headerTitle: "Tab Two Title" }}
|
||||
/>
|
||||
</TabTwoStack.Navigator>
|
||||
);
|
||||
}
|
30
app/navigation/LinkingConfiguration.ts
Normal file
30
app/navigation/LinkingConfiguration.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Learn more about deep linking with React Navigation
|
||||
* https://reactnavigation.org/docs/deep-linking
|
||||
* https://reactnavigation.org/docs/configuring-links
|
||||
*/
|
||||
|
||||
import * as Linking from "expo-linking";
|
||||
|
||||
export default {
|
||||
prefixes: [Linking.makeUrl("/")],
|
||||
config: {
|
||||
screens: {
|
||||
Root: {
|
||||
screens: {
|
||||
TabOne: {
|
||||
screens: {
|
||||
TabOneScreen: "one",
|
||||
},
|
||||
},
|
||||
TabTwo: {
|
||||
screens: {
|
||||
TabTwoScreen: "two",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NotFound: "*",
|
||||
},
|
||||
},
|
||||
};
|
37
app/navigation/index.tsx
Normal file
37
app/navigation/index.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* If you are not familiar with React Navigation, check out the "Fundamentals" guide:
|
||||
* https://reactnavigation.org/docs/getting-started
|
||||
*
|
||||
*/
|
||||
import { NavigationContainer, DefaultTheme, DarkTheme } from "@react-navigation/native";
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
import * as React from "react";
|
||||
import { ColorSchemeName } from "react-native";
|
||||
|
||||
import NotFoundScreen from "../screens/NotFoundScreen";
|
||||
import { RootStackParamList } from "../types";
|
||||
import BottomTabNavigator from "./BottomTabNavigator";
|
||||
import LinkingConfiguration from "./LinkingConfiguration";
|
||||
|
||||
export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeName }) {
|
||||
return (
|
||||
<NavigationContainer
|
||||
linking={LinkingConfiguration}
|
||||
theme={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
||||
<RootNavigator />
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
|
||||
// A root stack navigator is often used for displaying modals on top of all other content
|
||||
// Read more here: https://reactnavigation.org/docs/modal
|
||||
const Stack = createStackNavigator<RootStackParamList>();
|
||||
|
||||
function RootNavigator() {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="Root" component={BottomTabNavigator} />
|
||||
<Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title: "Oops!" }} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
15458
app/package-lock.json
generated
Normal file
15458
app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
app/package.json
Normal file
45
app/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject",
|
||||
"test": "jest --watchAll"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "12.0.5",
|
||||
"@react-native-community/masked-view": "0.1.11",
|
||||
"@react-navigation/bottom-tabs": "5.11.11",
|
||||
"@react-navigation/native": "5.9.4",
|
||||
"@react-navigation/stack": "5.14.5",
|
||||
"expo": "41.0.1",
|
||||
"expo-asset": "8.3.2",
|
||||
"expo-constants": "10.1.3",
|
||||
"expo-font": "9.1.0",
|
||||
"expo-linking": "2.2.3",
|
||||
"expo-splash-screen": "0.10.2",
|
||||
"expo-status-bar": "1.0.4",
|
||||
"expo-web-browser": "9.1.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
|
||||
"react-native-reanimated": "2.2.0",
|
||||
"react-native-gesture-handler": "1.10.3",
|
||||
"react-native-safe-area-context": "3.2.0",
|
||||
"react-native-screens": "3.3.0",
|
||||
"react-native-web": "0.16.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.14.3",
|
||||
"@types/react": "17.0.8",
|
||||
"@types/react-native": "0.64.8",
|
||||
"jest-expo": "41.0.0",
|
||||
"typescript": "4.3.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
40
app/screens/NotFoundScreen.tsx
Normal file
40
app/screens/NotFoundScreen.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { StackScreenProps } from "@react-navigation/stack";
|
||||
import * as React from "react";
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||
|
||||
import { RootStackParamList } from "../types";
|
||||
|
||||
export default function NotFoundScreen({
|
||||
navigation,
|
||||
}: StackScreenProps<RootStackParamList, "NotFound">) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>This screen doesn't exist.</Text>
|
||||
<TouchableOpacity onPress={() => navigation.replace("Root")} style={styles.link}>
|
||||
<Text style={styles.linkText}>Go to home screen!</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
link: {
|
||||
marginTop: 15,
|
||||
paddingVertical: 15,
|
||||
},
|
||||
linkText: {
|
||||
fontSize: 14,
|
||||
color: "#2e78b7",
|
||||
},
|
||||
});
|
32
app/screens/TabOneScreen.tsx
Normal file
32
app/screens/TabOneScreen.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import * as React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
import EditScreenInfo from "../components/EditScreenInfo";
|
||||
import { Text, View } from "../components/Themed";
|
||||
|
||||
export default function TabOneScreen() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Tab One</Text>
|
||||
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
|
||||
<EditScreenInfo path="/screens/TabOneScreen.tsx" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
separator: {
|
||||
marginVertical: 30,
|
||||
height: 1,
|
||||
width: "80%",
|
||||
},
|
||||
});
|
57
app/screens/TabTwoScreen.tsx
Normal file
57
app/screens/TabTwoScreen.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import * as React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
import EditScreenInfo from "../components/EditScreenInfo";
|
||||
import { Text, View } from "../components/Themed";
|
||||
import type { Conversation } from "../../api/src/controller/sms";
|
||||
|
||||
export default function TabTwoScreen() {
|
||||
const [conversations, setConversations] = React.useState<Conversation>({});
|
||||
const conversationsEntries = Object.entries(conversations);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetch("http://192.168.1.40:3000/conversations")
|
||||
.then(response => response.json())
|
||||
.then(conversations => setConversations(conversations))
|
||||
.catch(error => console.error("error", error));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.title}>Tab Two</Text>
|
||||
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
|
||||
{conversationsEntries.map(([recipient, messages], index) => {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
return (
|
||||
<>
|
||||
<View>
|
||||
<Text>{recipient}</Text>
|
||||
<Text>{lastMessage.content}</Text>
|
||||
<Text>{new Date(lastMessage.sentAt).toDateString()}</Text>
|
||||
</View>
|
||||
{index + 1 < messages.length && (
|
||||
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
separator: {
|
||||
marginVertical: 30,
|
||||
height: 1,
|
||||
width: "80%",
|
||||
},
|
||||
});
|
6
app/tsconfig.json
Normal file
6
app/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
22
app/types.tsx
Normal file
22
app/types.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Learn more about using TypeScript with React Navigation:
|
||||
* https://reactnavigation.org/docs/typescript/
|
||||
*/
|
||||
|
||||
export type RootStackParamList = {
|
||||
Root: undefined;
|
||||
NotFound: undefined;
|
||||
};
|
||||
|
||||
export type BottomTabParamList = {
|
||||
TabOne: undefined;
|
||||
TabTwo: undefined;
|
||||
};
|
||||
|
||||
export type TabOneParamList = {
|
||||
TabOneScreen: undefined;
|
||||
};
|
||||
|
||||
export type TabTwoParamList = {
|
||||
TabTwoScreen: undefined;
|
||||
};
|
Reference in New Issue
Block a user