Spaces:
Runtime error
Runtime error
Iliya Zhechev
commited on
Commit
·
0924f9b
1
Parent(s):
4960000
App initial commit with bare bones app
Browse files- app/.expo-shared/assets.json +6 -0
- app/.gitignore +14 -0
- app/App.tsx +22 -0
- app/app.json +34 -0
- app/assets/fonts/SpaceMono-Regular.ttf +0 -0
- app/assets/images/adaptive-icon.png +0 -0
- app/assets/images/favicon.png +0 -0
- app/assets/images/icon.png +0 -0
- app/assets/images/splash.png +0 -0
- app/babel.config.js +6 -0
- app/components/EditScreenInfo.tsx +79 -0
- app/components/StyledText.tsx +5 -0
- app/components/Themed.tsx +45 -0
- app/components/__tests__/StyledText-test.js +10 -0
- app/constants/Colors.ts +19 -0
- app/constants/Layout.ts +12 -0
- app/hooks/useCachedResources.ts +33 -0
- app/hooks/useColorScheme.ts +8 -0
- app/navigation/LinkingConfiguration.ts +36 -0
- app/navigation/index.tsx +107 -0
- app/package-lock.json +0 -0
- app/package.json +47 -0
- app/screens/ModalScreen.tsx +35 -0
- app/screens/NotFoundScreen.tsx +36 -0
- app/screens/TabOneScreen.tsx +106 -0
- app/screens/TabTwoScreen.tsx +31 -0
- app/tsconfig.json +6 -0
- app/types.tsx +35 -0
app/.expo-shared/assets.json
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"e997a5256149a4b76e6bfd6cbf519c5e5a0f1d278a3d8fa1253022b03c90473b": true,
|
3 |
+
"af683c96e0ffd2cf81287651c9433fa44debc1220ca7cb431fe482747f34a505": true,
|
4 |
+
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
5 |
+
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
6 |
+
}
|
app/.gitignore
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
node_modules/
|
2 |
+
.expo/
|
3 |
+
dist/
|
4 |
+
npm-debug.*
|
5 |
+
*.jks
|
6 |
+
*.p8
|
7 |
+
*.p12
|
8 |
+
*.key
|
9 |
+
*.mobileprovision
|
10 |
+
*.orig.*
|
11 |
+
web-build/
|
12 |
+
|
13 |
+
# macOS
|
14 |
+
.DS_Store
|
app/App.tsx
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StatusBar } from 'expo-status-bar';
|
2 |
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
3 |
+
|
4 |
+
import useCachedResources from './hooks/useCachedResources';
|
5 |
+
import useColorScheme from './hooks/useColorScheme';
|
6 |
+
import Navigation from './navigation';
|
7 |
+
|
8 |
+
export default function App() {
|
9 |
+
const isLoadingComplete = useCachedResources();
|
10 |
+
const colorScheme = useColorScheme();
|
11 |
+
|
12 |
+
if (!isLoadingComplete) {
|
13 |
+
return null;
|
14 |
+
} else {
|
15 |
+
return (
|
16 |
+
<SafeAreaProvider>
|
17 |
+
<Navigation colorScheme={colorScheme} />
|
18 |
+
<StatusBar />
|
19 |
+
</SafeAreaProvider>
|
20 |
+
);
|
21 |
+
}
|
22 |
+
}
|
app/app.json
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"expo": {
|
3 |
+
"name": "text-brush",
|
4 |
+
"slug": "text-brush",
|
5 |
+
"version": "1.0.0",
|
6 |
+
"orientation": "portrait",
|
7 |
+
"icon": "./assets/images/icon.png",
|
8 |
+
"scheme": "myapp",
|
9 |
+
"userInterfaceStyle": "automatic",
|
10 |
+
"splash": {
|
11 |
+
"image": "./assets/images/splash.png",
|
12 |
+
"resizeMode": "contain",
|
13 |
+
"backgroundColor": "#ffffff"
|
14 |
+
},
|
15 |
+
"updates": {
|
16 |
+
"fallbackToCacheTimeout": 0
|
17 |
+
},
|
18 |
+
"assetBundlePatterns": [
|
19 |
+
"**/*"
|
20 |
+
],
|
21 |
+
"ios": {
|
22 |
+
"supportsTablet": true
|
23 |
+
},
|
24 |
+
"android": {
|
25 |
+
"adaptiveIcon": {
|
26 |
+
"foregroundImage": "./assets/images/adaptive-icon.png",
|
27 |
+
"backgroundColor": "#ffffff"
|
28 |
+
}
|
29 |
+
},
|
30 |
+
"web": {
|
31 |
+
"favicon": "./assets/images/favicon.png"
|
32 |
+
}
|
33 |
+
}
|
34 |
+
}
|
app/assets/fonts/SpaceMono-Regular.ttf
ADDED
Binary file (93.3 kB). View file
|
|
app/assets/images/adaptive-icon.png
ADDED
![]() |
app/assets/images/favicon.png
ADDED
![]() |
app/assets/images/icon.png
ADDED
![]() |
app/assets/images/splash.png
ADDED
![]() |
app/babel.config.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = function(api) {
|
2 |
+
api.cache(true);
|
3 |
+
return {
|
4 |
+
presets: ['babel-preset-expo']
|
5 |
+
};
|
6 |
+
};
|
app/components/EditScreenInfo.tsx
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as WebBrowser from 'expo-web-browser';
|
2 |
+
import { StyleSheet, TouchableOpacity } from 'react-native';
|
3 |
+
|
4 |
+
import Colors from '../constants/Colors';
|
5 |
+
import { MonoText } from './StyledText';
|
6 |
+
import { Text, View } from './Themed';
|
7 |
+
|
8 |
+
export default function EditScreenInfo({ path }: { path: string }) {
|
9 |
+
return (
|
10 |
+
<View>
|
11 |
+
<View style={styles.getStartedContainer}>
|
12 |
+
<Text
|
13 |
+
style={styles.getStartedText}
|
14 |
+
lightColor="rgba(0,0,0,0.8)"
|
15 |
+
darkColor="rgba(255,255,255,0.8)">
|
16 |
+
Open up the code for this screen:
|
17 |
+
</Text>
|
18 |
+
|
19 |
+
<View
|
20 |
+
style={[styles.codeHighlightContainer, styles.homeScreenFilename]}
|
21 |
+
darkColor="rgba(255,255,255,0.05)"
|
22 |
+
lightColor="rgba(0,0,0,0.05)">
|
23 |
+
<MonoText>{path}</MonoText>
|
24 |
+
</View>
|
25 |
+
|
26 |
+
<Text
|
27 |
+
style={styles.getStartedText}
|
28 |
+
lightColor="rgba(0,0,0,0.8)"
|
29 |
+
darkColor="rgba(255,255,255,0.8)">
|
30 |
+
Change any of the text, save the file, and your app will automatically update.
|
31 |
+
</Text>
|
32 |
+
</View>
|
33 |
+
|
34 |
+
<View style={styles.helpContainer}>
|
35 |
+
<TouchableOpacity onPress={handleHelpPress} style={styles.helpLink}>
|
36 |
+
<Text style={styles.helpLinkText} lightColor={Colors.light.tint}>
|
37 |
+
Tap here if your app doesn't automatically update after making changes
|
38 |
+
</Text>
|
39 |
+
</TouchableOpacity>
|
40 |
+
</View>
|
41 |
+
</View>
|
42 |
+
);
|
43 |
+
}
|
44 |
+
|
45 |
+
function handleHelpPress() {
|
46 |
+
WebBrowser.openBrowserAsync(
|
47 |
+
'https://docs.expo.io/get-started/create-a-new-app/#opening-the-app-on-your-phonetablet'
|
48 |
+
);
|
49 |
+
}
|
50 |
+
|
51 |
+
const styles = StyleSheet.create({
|
52 |
+
getStartedContainer: {
|
53 |
+
alignItems: 'center',
|
54 |
+
marginHorizontal: 50,
|
55 |
+
},
|
56 |
+
homeScreenFilename: {
|
57 |
+
marginVertical: 7,
|
58 |
+
},
|
59 |
+
codeHighlightContainer: {
|
60 |
+
borderRadius: 3,
|
61 |
+
paddingHorizontal: 4,
|
62 |
+
},
|
63 |
+
getStartedText: {
|
64 |
+
fontSize: 17,
|
65 |
+
lineHeight: 24,
|
66 |
+
textAlign: 'center',
|
67 |
+
},
|
68 |
+
helpContainer: {
|
69 |
+
marginTop: 15,
|
70 |
+
marginHorizontal: 20,
|
71 |
+
alignItems: 'center',
|
72 |
+
},
|
73 |
+
helpLink: {
|
74 |
+
paddingVertical: 15,
|
75 |
+
},
|
76 |
+
helpLinkText: {
|
77 |
+
textAlign: 'center',
|
78 |
+
},
|
79 |
+
});
|
app/components/StyledText.tsx
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Text, TextProps } from './Themed';
|
2 |
+
|
3 |
+
export function MonoText(props: TextProps) {
|
4 |
+
return <Text {...props} style={[props.style, { fontFamily: 'space-mono' }]} />;
|
5 |
+
}
|
app/components/Themed.tsx
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Learn more about Light and Dark modes:
|
3 |
+
* https://docs.expo.io/guides/color-schemes/
|
4 |
+
*/
|
5 |
+
|
6 |
+
import { Text as DefaultText, View as DefaultView } from 'react-native';
|
7 |
+
|
8 |
+
import Colors from '../constants/Colors';
|
9 |
+
import useColorScheme from '../hooks/useColorScheme';
|
10 |
+
|
11 |
+
export function useThemeColor(
|
12 |
+
props: { light?: string; dark?: string },
|
13 |
+
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
|
14 |
+
) {
|
15 |
+
const theme = useColorScheme();
|
16 |
+
const colorFromProps = props[theme];
|
17 |
+
|
18 |
+
if (colorFromProps) {
|
19 |
+
return colorFromProps;
|
20 |
+
} else {
|
21 |
+
return Colors[theme][colorName];
|
22 |
+
}
|
23 |
+
}
|
24 |
+
|
25 |
+
type ThemeProps = {
|
26 |
+
lightColor?: string;
|
27 |
+
darkColor?: string;
|
28 |
+
};
|
29 |
+
|
30 |
+
export type TextProps = ThemeProps & DefaultText['props'];
|
31 |
+
export type ViewProps = ThemeProps & DefaultView['props'];
|
32 |
+
|
33 |
+
export function Text(props: TextProps) {
|
34 |
+
const { style, lightColor, darkColor, ...otherProps } = props;
|
35 |
+
const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
|
36 |
+
|
37 |
+
return <DefaultText style={[{ color }, style]} {...otherProps} />;
|
38 |
+
}
|
39 |
+
|
40 |
+
export function View(props: ViewProps) {
|
41 |
+
const { style, lightColor, darkColor, ...otherProps } = props;
|
42 |
+
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
|
43 |
+
|
44 |
+
return <DefaultView style={[{ backgroundColor }, style]} {...otherProps} />;
|
45 |
+
}
|
app/components/__tests__/StyledText-test.js
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react';
|
2 |
+
import renderer from 'react-test-renderer';
|
3 |
+
|
4 |
+
import { MonoText } from '../StyledText';
|
5 |
+
|
6 |
+
it(`renders correctly`, () => {
|
7 |
+
const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
|
8 |
+
|
9 |
+
expect(tree).toMatchSnapshot();
|
10 |
+
});
|
app/constants/Colors.ts
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const tintColorLight = '#2f95dc';
|
2 |
+
const tintColorDark = '#fff';
|
3 |
+
|
4 |
+
export default {
|
5 |
+
light: {
|
6 |
+
text: '#000',
|
7 |
+
background: '#fff',
|
8 |
+
tint: tintColorLight,
|
9 |
+
tabIconDefault: '#ccc',
|
10 |
+
tabIconSelected: tintColorLight,
|
11 |
+
},
|
12 |
+
dark: {
|
13 |
+
text: '#fff',
|
14 |
+
background: '#000',
|
15 |
+
tint: tintColorDark,
|
16 |
+
tabIconDefault: '#ccc',
|
17 |
+
tabIconSelected: tintColorDark,
|
18 |
+
},
|
19 |
+
};
|
app/constants/Layout.ts
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Dimensions } from 'react-native';
|
2 |
+
|
3 |
+
const width = Dimensions.get('window').width;
|
4 |
+
const height = Dimensions.get('window').height;
|
5 |
+
|
6 |
+
export default {
|
7 |
+
window: {
|
8 |
+
width,
|
9 |
+
height,
|
10 |
+
},
|
11 |
+
isSmallDevice: width < 375,
|
12 |
+
};
|
app/hooks/useCachedResources.ts
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { FontAwesome } from '@expo/vector-icons';
|
2 |
+
import * as Font from 'expo-font';
|
3 |
+
import * as SplashScreen from 'expo-splash-screen';
|
4 |
+
import { useEffect, useState } from 'react';
|
5 |
+
|
6 |
+
export default function useCachedResources() {
|
7 |
+
const [isLoadingComplete, setLoadingComplete] = useState(false);
|
8 |
+
|
9 |
+
// Load any resources or data that we need prior to rendering the app
|
10 |
+
useEffect(() => {
|
11 |
+
async function loadResourcesAndDataAsync() {
|
12 |
+
try {
|
13 |
+
SplashScreen.preventAutoHideAsync();
|
14 |
+
|
15 |
+
// Load fonts
|
16 |
+
await Font.loadAsync({
|
17 |
+
...FontAwesome.font,
|
18 |
+
'space-mono': require('../assets/fonts/SpaceMono-Regular.ttf'),
|
19 |
+
});
|
20 |
+
} catch (e) {
|
21 |
+
// We might want to provide this error information to an error reporting service
|
22 |
+
console.warn(e);
|
23 |
+
} finally {
|
24 |
+
setLoadingComplete(true);
|
25 |
+
SplashScreen.hideAsync();
|
26 |
+
}
|
27 |
+
}
|
28 |
+
|
29 |
+
loadResourcesAndDataAsync();
|
30 |
+
}, []);
|
31 |
+
|
32 |
+
return isLoadingComplete;
|
33 |
+
}
|
app/hooks/useColorScheme.ts
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ColorSchemeName, useColorScheme as _useColorScheme } from 'react-native';
|
2 |
+
|
3 |
+
// The useColorScheme value is always either light or dark, but the built-in
|
4 |
+
// type suggests that it can be null. This will not happen in practice, so this
|
5 |
+
// makes it a bit easier to work with.
|
6 |
+
export default function useColorScheme(): NonNullable<ColorSchemeName> {
|
7 |
+
return _useColorScheme() as NonNullable<ColorSchemeName>;
|
8 |
+
}
|
app/navigation/LinkingConfiguration.ts
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Learn more about deep linking with React Navigation
|
3 |
+
* https://reactnavigation.org/docs/deep-linking
|
4 |
+
* https://reactnavigation.org/docs/configuring-links
|
5 |
+
*/
|
6 |
+
|
7 |
+
import { LinkingOptions } from '@react-navigation/native';
|
8 |
+
import * as Linking from 'expo-linking';
|
9 |
+
|
10 |
+
import { RootStackParamList } from '../types';
|
11 |
+
|
12 |
+
const linking: LinkingOptions<RootStackParamList> = {
|
13 |
+
prefixes: [Linking.createURL('/')],
|
14 |
+
config: {
|
15 |
+
screens: {
|
16 |
+
Root: {
|
17 |
+
screens: {
|
18 |
+
TabOne: {
|
19 |
+
screens: {
|
20 |
+
TabOneScreen: 'one',
|
21 |
+
},
|
22 |
+
},
|
23 |
+
TabTwo: {
|
24 |
+
screens: {
|
25 |
+
TabTwoScreen: 'two',
|
26 |
+
},
|
27 |
+
},
|
28 |
+
},
|
29 |
+
},
|
30 |
+
Modal: 'modal',
|
31 |
+
NotFound: '*',
|
32 |
+
},
|
33 |
+
},
|
34 |
+
};
|
35 |
+
|
36 |
+
export default linking;
|
app/navigation/index.tsx
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* If you are not familiar with React Navigation, refer to the "Fundamentals" guide:
|
3 |
+
* https://reactnavigation.org/docs/getting-started
|
4 |
+
*
|
5 |
+
*/
|
6 |
+
import { FontAwesome } from '@expo/vector-icons';
|
7 |
+
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
8 |
+
import { NavigationContainer, DefaultTheme, DarkTheme } from '@react-navigation/native';
|
9 |
+
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
10 |
+
import * as React from 'react';
|
11 |
+
import { ColorSchemeName, Pressable } from 'react-native';
|
12 |
+
|
13 |
+
import Colors from '../constants/Colors';
|
14 |
+
import useColorScheme from '../hooks/useColorScheme';
|
15 |
+
import ModalScreen from '../screens/ModalScreen';
|
16 |
+
import NotFoundScreen from '../screens/NotFoundScreen';
|
17 |
+
import TabOneScreen from '../screens/TabOneScreen';
|
18 |
+
import TabTwoScreen from '../screens/TabTwoScreen';
|
19 |
+
import { RootStackParamList, RootTabParamList, RootTabScreenProps } from '../types';
|
20 |
+
import LinkingConfiguration from './LinkingConfiguration';
|
21 |
+
|
22 |
+
export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeName }) {
|
23 |
+
return (
|
24 |
+
<NavigationContainer
|
25 |
+
linking={LinkingConfiguration}
|
26 |
+
theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
27 |
+
<RootNavigator />
|
28 |
+
</NavigationContainer>
|
29 |
+
);
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* A root stack navigator is often used for displaying modals on top of all other content.
|
34 |
+
* https://reactnavigation.org/docs/modal
|
35 |
+
*/
|
36 |
+
const Stack = createNativeStackNavigator<RootStackParamList>();
|
37 |
+
|
38 |
+
function RootNavigator() {
|
39 |
+
return (
|
40 |
+
<Stack.Navigator>
|
41 |
+
<Stack.Screen name="Root" component={BottomTabNavigator} options={{ headerShown: false }} />
|
42 |
+
<Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title: 'Oops!' }} />
|
43 |
+
<Stack.Group screenOptions={{ presentation: 'modal' }}>
|
44 |
+
<Stack.Screen name="Modal" component={ModalScreen} />
|
45 |
+
</Stack.Group>
|
46 |
+
</Stack.Navigator>
|
47 |
+
);
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* A bottom tab navigator displays tab buttons on the bottom of the display to switch screens.
|
52 |
+
* https://reactnavigation.org/docs/bottom-tab-navigator
|
53 |
+
*/
|
54 |
+
const BottomTab = createBottomTabNavigator<RootTabParamList>();
|
55 |
+
|
56 |
+
function BottomTabNavigator() {
|
57 |
+
const colorScheme = useColorScheme();
|
58 |
+
|
59 |
+
return (
|
60 |
+
<BottomTab.Navigator
|
61 |
+
initialRouteName="TabOne"
|
62 |
+
screenOptions={{
|
63 |
+
tabBarActiveTintColor: Colors[colorScheme].tint,
|
64 |
+
}}>
|
65 |
+
<BottomTab.Screen
|
66 |
+
name="TabOne"
|
67 |
+
component={TabOneScreen}
|
68 |
+
options={({ navigation }: RootTabScreenProps<'TabOne'>) => ({
|
69 |
+
title: 'Tab One',
|
70 |
+
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
71 |
+
headerRight: () => (
|
72 |
+
<Pressable
|
73 |
+
onPress={() => navigation.navigate('Modal')}
|
74 |
+
style={({ pressed }) => ({
|
75 |
+
opacity: pressed ? 0.5 : 1,
|
76 |
+
})}>
|
77 |
+
<FontAwesome
|
78 |
+
name="info-circle"
|
79 |
+
size={25}
|
80 |
+
color={Colors[colorScheme].text}
|
81 |
+
style={{ marginRight: 15 }}
|
82 |
+
/>
|
83 |
+
</Pressable>
|
84 |
+
),
|
85 |
+
})}
|
86 |
+
/>
|
87 |
+
<BottomTab.Screen
|
88 |
+
name="TabTwo"
|
89 |
+
component={TabTwoScreen}
|
90 |
+
options={{
|
91 |
+
title: 'Tab Two',
|
92 |
+
tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
|
93 |
+
}}
|
94 |
+
/>
|
95 |
+
</BottomTab.Navigator>
|
96 |
+
);
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
|
101 |
+
*/
|
102 |
+
function TabBarIcon(props: {
|
103 |
+
name: React.ComponentProps<typeof FontAwesome>['name'];
|
104 |
+
color: string;
|
105 |
+
}) {
|
106 |
+
return <FontAwesome size={30} style={{ marginBottom: -3 }} {...props} />;
|
107 |
+
}
|
app/package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
app/package.json
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "text-brush",
|
3 |
+
"version": "1.0.0",
|
4 |
+
"main": "node_modules/expo/AppEntry.js",
|
5 |
+
"scripts": {
|
6 |
+
"start": "expo start",
|
7 |
+
"android": "expo start --android",
|
8 |
+
"ios": "expo start --ios",
|
9 |
+
"web": "expo start --web",
|
10 |
+
"test": "jest --watchAll"
|
11 |
+
},
|
12 |
+
"jest": {
|
13 |
+
"preset": "jest-expo"
|
14 |
+
},
|
15 |
+
"dependencies": {
|
16 |
+
"@expo/vector-icons": "^13.0.0",
|
17 |
+
"@expo/webpack-config": "^0.17.0",
|
18 |
+
"@react-navigation/bottom-tabs": "^6.0.5",
|
19 |
+
"@react-navigation/native": "^6.0.2",
|
20 |
+
"@react-navigation/native-stack": "^6.1.0",
|
21 |
+
"expo": "~46.0.13",
|
22 |
+
"expo-asset": "~8.6.1",
|
23 |
+
"expo-constants": "~13.2.4",
|
24 |
+
"expo-font": "~10.2.1",
|
25 |
+
"expo-linking": "~3.2.2",
|
26 |
+
"expo-splash-screen": "~0.16.2",
|
27 |
+
"expo-status-bar": "~1.4.0",
|
28 |
+
"expo-system-ui": "~1.3.0",
|
29 |
+
"expo-web-browser": "~11.0.0",
|
30 |
+
"react": "18.0.0",
|
31 |
+
"react-dom": "18.0.0",
|
32 |
+
"react-native": "0.69.6",
|
33 |
+
"react-native-safe-area-context": "4.3.1",
|
34 |
+
"react-native-screens": "~3.15.0",
|
35 |
+
"react-native-web": "~0.18.7"
|
36 |
+
},
|
37 |
+
"devDependencies": {
|
38 |
+
"@babel/core": "^7.12.9",
|
39 |
+
"@types/react": "~18.0.14",
|
40 |
+
"@types/react-native": "~0.69.1",
|
41 |
+
"jest": "^26.6.3",
|
42 |
+
"jest-expo": "~44.0.1",
|
43 |
+
"react-test-renderer": "18.0.0",
|
44 |
+
"typescript": "~4.3.5"
|
45 |
+
},
|
46 |
+
"private": true
|
47 |
+
}
|
app/screens/ModalScreen.tsx
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StatusBar } from 'expo-status-bar';
|
2 |
+
import { Platform, StyleSheet } from 'react-native';
|
3 |
+
|
4 |
+
import EditScreenInfo from '../components/EditScreenInfo';
|
5 |
+
import { Text, View } from '../components/Themed';
|
6 |
+
|
7 |
+
export default function ModalScreen() {
|
8 |
+
return (
|
9 |
+
<View style={styles.container}>
|
10 |
+
<Text style={styles.title}>Modal</Text>
|
11 |
+
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
|
12 |
+
<EditScreenInfo path="/screens/ModalScreen.tsx" />
|
13 |
+
|
14 |
+
{/* Use a light status bar on iOS to account for the black space above the modal */}
|
15 |
+
<StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} />
|
16 |
+
</View>
|
17 |
+
);
|
18 |
+
}
|
19 |
+
|
20 |
+
const styles = StyleSheet.create({
|
21 |
+
container: {
|
22 |
+
flex: 1,
|
23 |
+
alignItems: 'center',
|
24 |
+
justifyContent: 'center',
|
25 |
+
},
|
26 |
+
title: {
|
27 |
+
fontSize: 20,
|
28 |
+
fontWeight: 'bold',
|
29 |
+
},
|
30 |
+
separator: {
|
31 |
+
marginVertical: 30,
|
32 |
+
height: 1,
|
33 |
+
width: '80%',
|
34 |
+
},
|
35 |
+
});
|
app/screens/NotFoundScreen.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StyleSheet, TouchableOpacity } from 'react-native';
|
2 |
+
|
3 |
+
import { Text, View } from '../components/Themed';
|
4 |
+
import { RootStackScreenProps } from '../types';
|
5 |
+
|
6 |
+
export default function NotFoundScreen({ navigation }: RootStackScreenProps<'NotFound'>) {
|
7 |
+
return (
|
8 |
+
<View style={styles.container}>
|
9 |
+
<Text style={styles.title}>This screen doesn't exist.</Text>
|
10 |
+
<TouchableOpacity onPress={() => navigation.replace('Root')} style={styles.link}>
|
11 |
+
<Text style={styles.linkText}>Go to home screen!</Text>
|
12 |
+
</TouchableOpacity>
|
13 |
+
</View>
|
14 |
+
);
|
15 |
+
}
|
16 |
+
|
17 |
+
const styles = StyleSheet.create({
|
18 |
+
container: {
|
19 |
+
flex: 1,
|
20 |
+
alignItems: 'center',
|
21 |
+
justifyContent: 'center',
|
22 |
+
padding: 20,
|
23 |
+
},
|
24 |
+
title: {
|
25 |
+
fontSize: 20,
|
26 |
+
fontWeight: 'bold',
|
27 |
+
},
|
28 |
+
link: {
|
29 |
+
marginTop: 15,
|
30 |
+
paddingVertical: 15,
|
31 |
+
},
|
32 |
+
linkText: {
|
33 |
+
fontSize: 14,
|
34 |
+
color: '#2e78b7',
|
35 |
+
},
|
36 |
+
});
|
app/screens/TabOneScreen.tsx
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from 'react';
|
2 |
+
import { ActivityIndicator, Button, Image, StyleSheet, TextInput } from 'react-native';
|
3 |
+
|
4 |
+
import { Text, View } from '../components/Themed';
|
5 |
+
import { RootTabScreenProps } from '../types';
|
6 |
+
|
7 |
+
function blobToBase64(blob: Blob): Promise<string | ArayBuffer> {
|
8 |
+
return new Promise((resolve, reject) => {
|
9 |
+
const reader = new FileReader();
|
10 |
+
reader.onloadend = () => resolve(reader.result);
|
11 |
+
reader.readAsDataURL(blob);
|
12 |
+
reader.onerror = error => reject(error)
|
13 |
+
});
|
14 |
+
}
|
15 |
+
export default function TabOneScreen({ navigation }: RootTabScreenProps<'TabOne'>) {
|
16 |
+
const [text, setText] = useState('');
|
17 |
+
const [imageData, setImageData] = useState<string | null>(null);
|
18 |
+
const [isLoading, setIsLoading] = useState<bool>(false);
|
19 |
+
|
20 |
+
const onPress = () => {
|
21 |
+
const prompt = text;
|
22 |
+
const seed = 1337;
|
23 |
+
const steps = 30;
|
24 |
+
const url = `http://78.83.125.177:5000/generate-image?seed=${seed}&steps=${steps}&prompt=${prompt}`;
|
25 |
+
// const url = 'http://localhost:3000/ava.jpeg'
|
26 |
+
console.log('fetching');
|
27 |
+
setIsLoading(true);
|
28 |
+
fetch(url)
|
29 |
+
.then(async response => {
|
30 |
+
// debugger;
|
31 |
+
const blob = await response.blob();
|
32 |
+
const base64Blob = await blobToBase64(blob)
|
33 |
+
const validBlob = "data:image/png;base64," + base64Blob.substr(base64Blob.indexOf(',') + 1);
|
34 |
+
setImageData(validBlob);
|
35 |
+
console.log('done!');
|
36 |
+
setIsLoading(false);
|
37 |
+
})
|
38 |
+
.catch(error => {
|
39 |
+
console.error(error);
|
40 |
+
setIsLoading(false);
|
41 |
+
});
|
42 |
+
}
|
43 |
+
|
44 |
+
return (
|
45 |
+
<View style={styles.container}>
|
46 |
+
<Text style={styles.title}>Generate AI Image</Text>
|
47 |
+
<TextInput
|
48 |
+
style={styles.input}
|
49 |
+
placeholder="Write a prompt! (e.g. cute cat on mars)"
|
50 |
+
onChangeText={newText => setText(newText)}
|
51 |
+
onSubmitEditing={_ => onPress()}
|
52 |
+
defaultValue={text}
|
53 |
+
/>
|
54 |
+
<Button title="Generate" onPress={onPress}></Button>
|
55 |
+
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
|
56 |
+
{imageData && <Image source={{uri: imageData, scale: 1}} style={styles.image} />}
|
57 |
+
|
58 |
+
{isLoading && (
|
59 |
+
<View style={styles.loading}>
|
60 |
+
<ActivityIndicator size='large' />
|
61 |
+
</View>
|
62 |
+
)}
|
63 |
+
</View>
|
64 |
+
);
|
65 |
+
}
|
66 |
+
|
67 |
+
const styles = StyleSheet.create({
|
68 |
+
image: {
|
69 |
+
width: 512,
|
70 |
+
height: 512,
|
71 |
+
maxWidth: '100%',
|
72 |
+
resizeMode: 'contain',
|
73 |
+
},
|
74 |
+
input: {
|
75 |
+
borderWidth: 3,
|
76 |
+
borderRadius: 3,
|
77 |
+
padding: '10px',
|
78 |
+
width: '100%',
|
79 |
+
margin: '10px',
|
80 |
+
},
|
81 |
+
container: {
|
82 |
+
padding: 10,
|
83 |
+
flex: 1,
|
84 |
+
alignItems: 'center',
|
85 |
+
justifyContent: 'center',
|
86 |
+
},
|
87 |
+
title: {
|
88 |
+
fontSize: 20,
|
89 |
+
fontWeight: 'bold',
|
90 |
+
},
|
91 |
+
separator: {
|
92 |
+
marginVertical: 30,
|
93 |
+
height: 1,
|
94 |
+
width: '80%',
|
95 |
+
},
|
96 |
+
loading: {
|
97 |
+
backgroundColor: 'rgba(255, 255, 255, 0.5)',
|
98 |
+
position: 'absolute',
|
99 |
+
left: 0,
|
100 |
+
right: 0,
|
101 |
+
top: 0,
|
102 |
+
bottom: 0,
|
103 |
+
alignItems: 'center',
|
104 |
+
justifyContent: 'center'
|
105 |
+
},
|
106 |
+
});
|
app/screens/TabTwoScreen.tsx
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StyleSheet } from 'react-native';
|
2 |
+
|
3 |
+
import EditScreenInfo from '../components/EditScreenInfo';
|
4 |
+
import { Text, View } from '../components/Themed';
|
5 |
+
|
6 |
+
export default function TabTwoScreen() {
|
7 |
+
return (
|
8 |
+
<View style={styles.container}>
|
9 |
+
<Text style={styles.title}>Tab Two</Text>
|
10 |
+
<View style={styles.separator} lightColor="#eee" darkColor="rgba(255,255,255,0.1)" />
|
11 |
+
<EditScreenInfo path="/screens/TabTwoScreen.tsx" />
|
12 |
+
</View>
|
13 |
+
);
|
14 |
+
}
|
15 |
+
|
16 |
+
const styles = StyleSheet.create({
|
17 |
+
container: {
|
18 |
+
flex: 1,
|
19 |
+
alignItems: 'center',
|
20 |
+
justifyContent: 'center',
|
21 |
+
},
|
22 |
+
title: {
|
23 |
+
fontSize: 20,
|
24 |
+
fontWeight: 'bold',
|
25 |
+
},
|
26 |
+
separator: {
|
27 |
+
marginVertical: 30,
|
28 |
+
height: 1,
|
29 |
+
width: '80%',
|
30 |
+
},
|
31 |
+
});
|
app/tsconfig.json
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"extends": "expo/tsconfig.base",
|
3 |
+
"compilerOptions": {
|
4 |
+
"strict": true
|
5 |
+
}
|
6 |
+
}
|
app/types.tsx
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Learn more about using TypeScript with React Navigation:
|
3 |
+
* https://reactnavigation.org/docs/typescript/
|
4 |
+
*/
|
5 |
+
|
6 |
+
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
|
7 |
+
import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native';
|
8 |
+
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
9 |
+
|
10 |
+
declare global {
|
11 |
+
namespace ReactNavigation {
|
12 |
+
interface RootParamList extends RootStackParamList {}
|
13 |
+
}
|
14 |
+
}
|
15 |
+
|
16 |
+
export type RootStackParamList = {
|
17 |
+
Root: NavigatorScreenParams<RootTabParamList> | undefined;
|
18 |
+
Modal: undefined;
|
19 |
+
NotFound: undefined;
|
20 |
+
};
|
21 |
+
|
22 |
+
export type RootStackScreenProps<Screen extends keyof RootStackParamList> = NativeStackScreenProps<
|
23 |
+
RootStackParamList,
|
24 |
+
Screen
|
25 |
+
>;
|
26 |
+
|
27 |
+
export type RootTabParamList = {
|
28 |
+
TabOne: undefined;
|
29 |
+
TabTwo: undefined;
|
30 |
+
};
|
31 |
+
|
32 |
+
export type RootTabScreenProps<Screen extends keyof RootTabParamList> = CompositeScreenProps<
|
33 |
+
BottomTabScreenProps<RootTabParamList, Screen>,
|
34 |
+
NativeStackScreenProps<RootStackParamList>
|
35 |
+
>;
|