--- /dev/null
+.expo
+node_modules
--- /dev/null
+import React from 'react';
+import { Platform, StatusBar, StyleSheet, View } from 'react-native';
+import { AppLoading, Asset, Font, Icon } from 'expo';
+import AppNavigator from './navigation/AppNavigator';
+
+export default class App extends React.Component {
+ state = {
+ isLoadingComplete: false,
+ };
+
+ render() {
+ if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
+ return (
+ <AppLoading
+ startAsync={this._loadResourcesAsync}
+ onError={this._handleLoadingError}
+ onFinish={this._handleFinishLoading}
+ />
+ );
+ } else {
+ return (
+ <View style={styles.container}>
+ {Platform.OS === 'ios' && <StatusBar barStyle="default" />}
+ <AppNavigator />
+ </View>
+ );
+ }
+ }
+
+ _loadResourcesAsync = async () => {
+ return Promise.all([
+ Asset.loadAsync([
+ require('./assets/images/robot-dev.png'),
+ require('./assets/images/robot-prod.png'),
+ ]),
+ Font.loadAsync({
+ // This is the font that we are using for our tab bar
+ ...Icon.Ionicons.font,
+ // We include SpaceMono because we use it in HomeScreen.js. Feel free
+ // to remove this if you are not using it in your app
+ 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
+ }),
+ ]);
+ };
+
+ _handleLoadingError = error => {
+ // In this case, you might want to report the error to your error
+ // reporting service, for example Sentry
+ console.warn(error);
+ };
+
+ _handleFinishLoading = () => {
+ this.setState({ isLoadingComplete: true });
+ };
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#fff',
+ },
+});
--- /dev/null
+import 'react-native';
+import React from 'react';
+import App from '../App';
+import renderer from 'react-test-renderer';
+import NavigationTestUtils from 'react-navigation/NavigationTestUtils';
+
+describe('App snapshot', () => {
+ jest.useFakeTimers();
+ beforeEach(() => {
+ NavigationTestUtils.resetInternalState();
+ });
+
+ it('renders the loading screen', async () => {
+ const tree = renderer.create(<App />).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('renders the root without loading screen', async () => {
+ const tree = renderer.create(<App skipLoadingScreen />).toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
--- /dev/null
+{
+ "expo": {
+ "name": "geoguessr",
+ "description": "A very interesting project.",
+ "slug": "geoguessr",
+ "privacy": "public",
+ "sdkVersion": "29.0.0",
+ "platforms": ["ios", "android"],
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/images/icon.png",
+ "splash": {
+ "image": "./assets/images/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "updates": {
+ "fallbackToCacheTimeout": 0
+ },
+ "assetBundlePatterns": [
+ "**/*"
+ ],
+ "ios": {
+ "supportsTablet": true
+ }
+ }
+}
--- /dev/null
+import React from 'react';
+import { Text } from 'react-native';
+
+export class MonoText extends React.Component {
+ render() {
+ return <Text {...this.props} style={[this.props.style, { fontFamily: 'space-mono' }]} />;
+ }
+}
--- /dev/null
+import React from 'react';
+import { Icon } from 'expo';
+
+import Colors from '../constants/Colors';
+
+export default class TabBarIcon extends React.Component {
+ render() {
+ return (
+ <Icon.Ionicons
+ name={this.props.name}
+ size={26}
+ style={{ marginBottom: -3 }}
+ color={this.props.focused ? Colors.tabIconSelected : Colors.tabIconDefault}
+ />
+ );
+ }
+}
\ No newline at end of file
--- /dev/null
+import 'react-native';
+import React from 'react';
+import { MonoText } from '../StyledText';
+import renderer from 'react-test-renderer';
+
+it('renders correctly', () => {
+ const tree = renderer.create(<MonoText>Snapshot test!</MonoText>).toJSON();
+
+ expect(tree).toMatchSnapshot();
+});
--- /dev/null
+const tintColor = '#2f95dc';
+
+export default {
+ tintColor,
+ tabIconDefault: '#ccc',
+ tabIconSelected: tintColor,
+ tabBar: '#fefefe',
+ errorBackground: 'red',
+ errorText: '#fff',
+ warningBackground: '#EAEB5E',
+ warningText: '#666804',
+ noticeBackground: tintColor,
+ noticeText: '#fff',
+};
--- /dev/null
+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,
+};
--- /dev/null
+import React from 'react';
+import { createSwitchNavigator } from 'react-navigation';
+
+import MainTabNavigator from './MainTabNavigator';
+
+export default createSwitchNavigator({
+ // You could add another route here for authentication.
+ // Read more at https://reactnavigation.org/docs/en/auth-flow.html
+ Main: MainTabNavigator,
+});
\ No newline at end of file
--- /dev/null
+import React from 'react';
+import { Platform } from 'react-native';
+import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
+
+import TabBarIcon from '../components/TabBarIcon';
+import HomeScreen from '../screens/HomeScreen';
+import LinksScreen from '../screens/LinksScreen';
+import SettingsScreen from '../screens/SettingsScreen';
+
+const HomeStack = createStackNavigator({
+ Home: HomeScreen,
+});
+
+HomeStack.navigationOptions = {
+ tabBarLabel: 'Home',
+ tabBarIcon: ({ focused }) => (
+ <TabBarIcon
+ focused={focused}
+ name={
+ Platform.OS === 'ios'
+ ? `ios-information-circle${focused ? '' : '-outline'}`
+ : 'md-information-circle'
+ }
+ />
+ ),
+};
+
+const LinksStack = createStackNavigator({
+ Links: LinksScreen,
+});
+
+LinksStack.navigationOptions = {
+ tabBarLabel: 'Links',
+ tabBarIcon: ({ focused }) => (
+ <TabBarIcon
+ focused={focused}
+ name={Platform.OS === 'ios' ? `ios-link${focused ? '' : '-outline'}` : 'md-link'}
+ />
+ ),
+};
+
+const SettingsStack = createStackNavigator({
+ Settings: SettingsScreen,
+});
+
+SettingsStack.navigationOptions = {
+ tabBarLabel: 'Settings',
+ tabBarIcon: ({ focused }) => (
+ <TabBarIcon
+ focused={focused}
+ name={Platform.OS === 'ios' ? `ios-options${focused ? '' : '-outline'}` : 'md-options'}
+ />
+ ),
+};
+
+export default createBottomTabNavigator({
+ HomeStack,
+ LinksStack,
+ SettingsStack,
+});
--- /dev/null
+{
+ "name": "my-new-project",
+ "main": "node_modules/expo/AppEntry.js",
+ "private": true,
+ "scripts": {
+ "start": "expo start",
+ "android": "expo start --android",
+ "ios": "expo start --ios",
+ "eject": "expo eject",
+ "test": "node ./node_modules/jest/bin/jest.js --watchAll"
+ },
+ "jest": {
+ "preset": "jest-expo"
+ },
+ "dependencies": {
+ "@expo/samples": "2.1.1",
+ "expo": "29.0.0",
+ "react": "16.3.1",
+ "react-native": "https://github.com/expo/react-native/archive/sdk-29.0.0.tar.gz",
+ "react-navigation": "^2.9.3"
+ },
+ "devDependencies": {
+ "jest-expo": "29.0.0"
+ }
+}
--- /dev/null
+import React from 'react';
+import {
+ Image,
+ Platform,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import { WebBrowser } from 'expo';
+
+import { MonoText } from '../components/StyledText';
+
+export default class HomeScreen extends React.Component {
+ static navigationOptions = {
+ header: null,
+ };
+
+ render() {
+ return (
+ <View style={styles.container}>
+ <ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
+ <View style={styles.welcomeContainer}>
+ <Image
+ source={
+ __DEV__
+ ? require('../assets/images/robot-dev.png')
+ : require('../assets/images/robot-prod.png')
+ }
+ style={styles.welcomeImage}
+ />
+ </View>
+
+ <View style={styles.getStartedContainer}>
+ {this._maybeRenderDevelopmentModeWarning()}
+
+ <Text style={styles.getStartedText}>Get started by opening</Text>
+
+ <View style={[styles.codeHighlightContainer, styles.homeScreenFilename]}>
+ <MonoText style={styles.codeHighlightText}>screens/HomeScreen.js</MonoText>
+ </View>
+
+ <Text style={styles.getStartedText}>
+ Change this text and your app will automatically reload.
+ </Text>
+ </View>
+
+ <View style={styles.helpContainer}>
+ <TouchableOpacity onPress={this._handleHelpPress} style={styles.helpLink}>
+ <Text style={styles.helpLinkText}>Help, it didn’t automatically reload!</Text>
+ </TouchableOpacity>
+ </View>
+ </ScrollView>
+
+ <View style={styles.tabBarInfoContainer}>
+ <Text style={styles.tabBarInfoText}>This is a tab bar. You can edit it in:</Text>
+
+ <View style={[styles.codeHighlightContainer, styles.navigationFilename]}>
+ <MonoText style={styles.codeHighlightText}>navigation/MainTabNavigator.js</MonoText>
+ </View>
+ </View>
+ </View>
+ );
+ }
+
+ _maybeRenderDevelopmentModeWarning() {
+ if (__DEV__) {
+ const learnMoreButton = (
+ <Text onPress={this._handleLearnMorePress} style={styles.helpLinkText}>
+ Learn more
+ </Text>
+ );
+
+ return (
+ <Text style={styles.developmentModeText}>
+ Development mode is enabled, your app will be slower but you can use useful development
+ tools. {learnMoreButton}
+ </Text>
+ );
+ } else {
+ return (
+ <Text style={styles.developmentModeText}>
+ You are not in development mode, your app will run at full speed.
+ </Text>
+ );
+ }
+ }
+
+ _handleLearnMorePress = () => {
+ WebBrowser.openBrowserAsync('https://docs.expo.io/versions/latest/guides/development-mode');
+ };
+
+ _handleHelpPress = () => {
+ WebBrowser.openBrowserAsync(
+ 'https://docs.expo.io/versions/latest/guides/up-and-running.html#can-t-see-your-changes'
+ );
+ };
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#fff',
+ },
+ developmentModeText: {
+ marginBottom: 20,
+ color: 'rgba(0,0,0,0.4)',
+ fontSize: 14,
+ lineHeight: 19,
+ textAlign: 'center',
+ },
+ contentContainer: {
+ paddingTop: 30,
+ },
+ welcomeContainer: {
+ alignItems: 'center',
+ marginTop: 10,
+ marginBottom: 20,
+ },
+ welcomeImage: {
+ width: 100,
+ height: 80,
+ resizeMode: 'contain',
+ marginTop: 3,
+ marginLeft: -10,
+ },
+ getStartedContainer: {
+ alignItems: 'center',
+ marginHorizontal: 50,
+ },
+ homeScreenFilename: {
+ marginVertical: 7,
+ },
+ codeHighlightText: {
+ color: 'rgba(96,100,109, 0.8)',
+ },
+ codeHighlightContainer: {
+ backgroundColor: 'rgba(0,0,0,0.05)',
+ borderRadius: 3,
+ paddingHorizontal: 4,
+ },
+ getStartedText: {
+ fontSize: 17,
+ color: 'rgba(96,100,109, 1)',
+ lineHeight: 24,
+ textAlign: 'center',
+ },
+ tabBarInfoContainer: {
+ position: 'absolute',
+ bottom: 0,
+ left: 0,
+ right: 0,
+ ...Platform.select({
+ ios: {
+ shadowColor: 'black',
+ shadowOffset: { height: -3 },
+ shadowOpacity: 0.1,
+ shadowRadius: 3,
+ },
+ android: {
+ elevation: 20,
+ },
+ }),
+ alignItems: 'center',
+ backgroundColor: '#fbfbfb',
+ paddingVertical: 20,
+ },
+ tabBarInfoText: {
+ fontSize: 17,
+ color: 'rgba(96,100,109, 1)',
+ textAlign: 'center',
+ },
+ navigationFilename: {
+ marginTop: 5,
+ },
+ helpContainer: {
+ marginTop: 15,
+ alignItems: 'center',
+ },
+ helpLink: {
+ paddingVertical: 15,
+ },
+ helpLinkText: {
+ fontSize: 14,
+ color: '#2e78b7',
+ },
+});
--- /dev/null
+import React from 'react';
+import { ScrollView, StyleSheet } from 'react-native';
+import { ExpoLinksView } from '@expo/samples';
+
+export default class LinksScreen extends React.Component {
+ static navigationOptions = {
+ title: 'Links',
+ };
+
+ render() {
+ return (
+ <ScrollView style={styles.container}>
+ {/* Go ahead and delete ExpoLinksView and replace it with your
+ * content, we just wanted to provide you with some helpful links */}
+ <ExpoLinksView />
+ </ScrollView>
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ paddingTop: 15,
+ backgroundColor: '#fff',
+ },
+});
--- /dev/null
+import React from 'react';
+import { ExpoConfigView } from '@expo/samples';
+
+export default class SettingsScreen extends React.Component {
+ static navigationOptions = {
+ title: 'app.json',
+ };
+
+ render() {
+ /* Go ahead and delete ExpoConfigView and replace it with your
+ * content, we just wanted to give you a quick view of your config */
+ return <ExpoConfigView />;
+ }
+}