Skip to content

Commit

Permalink
feat: Example application in Expo
Browse files Browse the repository at this point in the history
Fixes #21
  • Loading branch information
rgommezz committed Feb 3, 2019
1 parent a7c7c4c commit 9a2ca4b
Show file tree
Hide file tree
Showing 37 changed files with 8,272 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -8,6 +8,9 @@ Handful of utilities you should keep in your toolbelt to handle offline/online c
## Important (Please read)
**This is the documentation for version 4.x.x. If you are migrating from v3 to v4, check the [release notes](https://github.com/rgommezz/react-native-offline/releases/tag/v4.0.0).**

## Example app
A comprehensive [example app](/example) is available within Expo to play with the library and better understand its different modules. [Go and check it out!](https://expo.io/@rgommezz/example)

## Contents

* [Motivation](#motivation)
Expand Down
7 changes: 7 additions & 0 deletions example/.gitignore
@@ -0,0 +1,7 @@
node_modules/**/*
.expo/*
npm-debug.*
*.jks
*.p12
*.key
*.mobileprovision
1 change: 1 addition & 0 deletions example/.watchmanconfig
@@ -0,0 +1 @@
{}
69 changes: 69 additions & 0 deletions example/App.js
@@ -0,0 +1,69 @@
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Font, Icon } from 'expo';
import AppNavigator from './navigation/AppNavigator';
import DummyNetworkContext from './DummyNetworkContext';

const onlineUrl = 'https://www.google.com/';
const offlineUrl = 'https://www.weifhweopfhwioehfiwoephfpweoifhewifhpewoif.com';

export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoadingComplete: false,
network: {
pingUrl: onlineUrl,
toggleConnection: this.toggleConnection,
},
};
}

toggleConnection = () => {
this.setState(prevState => ({
network: {
...prevState.network,
pingUrl:
prevState.network.pingUrl === onlineUrl ? offlineUrl : onlineUrl,
},
}));
};

loadResourcesAsync = async () =>
Font.loadAsync({
...Icon.Ionicons.font,
'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
});

handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};

render() {
const { isLoadingComplete, network } = this.state;
const { skipLoadingScreen } = this.props;
if (!isLoadingComplete && !skipLoadingScreen) {
return (
<AppLoading
startAsync={this.loadResourcesAsync}
onFinish={this.handleFinishLoading}
/>
);
}
return (
<DummyNetworkContext.Provider value={network}>
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</View>
</DummyNetworkContext.Provider>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
4 changes: 4 additions & 0 deletions example/DummyNetworkContext.js
@@ -0,0 +1,4 @@
import React from 'react';

const DummyNetworkContext = React.createContext();
export default DummyNetworkContext;
19 changes: 19 additions & 0 deletions example/README.md
@@ -0,0 +1,19 @@
## Example app

This application showcases component utilities, redux, sagas integration and the offline queue with different configurations. You can try it out in Expo by using [this link](https://expo.io/@rgommezz/example).

### Runing it locally
You need to have `expo-cli` installed.

```bash
cd example
yarn install
expo start
```

### Snapshots
<div>
<img align="left" src="https://user-images.githubusercontent.com/4982414/52172165-f3605b00-2761-11e9-87f3-bca71b0d3918.png" width="400">

<img align="right" src="https://user-images.githubusercontent.com/4982414/52172166-f52a1e80-2761-11e9-8d9b-aa5a7caa24e2.png" width="400">
</div>
22 changes: 22 additions & 0 deletions example/__tests__/App-test.js
@@ -0,0 +1,22 @@
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();
});
});
29 changes: 29 additions & 0 deletions example/app.json
@@ -0,0 +1,29 @@
{
"expo": {
"name": "example",
"slug": "example",
"privacy": "public",
"sdkVersion": "32.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
}
}
}
Binary file added example/assets/fonts/SpaceMono-Regular.ttf
Binary file not shown.
Binary file added example/assets/images/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/images/redux-saga.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/images/redux.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/images/robot-dev.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/images/robot-prod.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/images/splash.png
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 example/babel.config.js
@@ -0,0 +1,6 @@
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
136 changes: 136 additions & 0 deletions example/components/ActionButtons.js
@@ -0,0 +1,136 @@
import React from 'react';
import { connect } from 'react-redux';
import {
Alert,
View,
StyleSheet,
Text,
TouchableNativeFeedback,
TouchableOpacity,
Platform,
} from 'react-native';
import { addOne, subOne, other, cancelOther } from '../redux/actions';

const Touchable = Platform.select({
android: TouchableNativeFeedback,
ios: TouchableOpacity,
});

const showInfo = actionType => () => {
const message = (() => {
switch (actionType) {
case 'ADD_1':
return (
'This action adds 1 to the counter state. It is configured to be enqueued if offline ' +
'and uses a unique id per action, so that we can add to the queue as many as we want.'
);
case 'SUB_1':
return (
'This action subtracts 1 to the counter state. It is configured to be enqueued if offline ' +
'and uses a unique id per action, so that we can add to the queue as many as we want.'
);
case 'OTHER':
return (
"This action does not change the UI state. It's only purpose is to demonstrate that if we dispatch the same " +
'action to the queue several times, it will replace the existing one and a new instance to the end of the queue. ' +
"It's also dismissable, so that if an action with type CANCEL_OTHER is dispatched, it will be removed from the queue."
);
case 'CANCEL_OTHER':
return (
"This action does not change the UI state and it's NOT configured to be added to the offline queue." +
' If dispatched, it will dismiss actions from the queue with type OTHER.'
);
default:
return '';
}
})();
Alert.alert(actionType, message, [{ text: 'OK', onPress: () => ({}) }], {
cancelable: false,
});
};

function ActionButtons({
addOneAction,
subOneAction,
otherAction,
cancelOtherAction,
}) {
return (
<View style={styles.container}>
<Text style={styles.title}>Actions to dispatch</Text>
<Text style={styles.subtitle}>Long tap on each action for more info</Text>
<View style={styles.row}>
<Touchable
onPress={addOneAction}
onLongPress={showInfo('ADD_1')}
style={{ justifyContent: 'center' }}
>
<Text style={styles.button}>ADD_1</Text>
</Touchable>
<Touchable
onPress={subOneAction}
onLongPress={showInfo('SUB_1')}
style={{ justifyContent: 'center' }}
>
<Text style={styles.button}>SUB_1</Text>
</Touchable>
</View>
<View style={styles.row}>
<Touchable
onPress={otherAction}
onLongPress={showInfo('OTHER')}
style={{ justifyContent: 'center' }}
>
<Text style={styles.button}>OTHER</Text>
</Touchable>
<Touchable
onPress={cancelOtherAction}
onLongPress={showInfo('CANCEL_OTHER')}
style={{ justifyContent: 'center' }}
>
<Text style={styles.button}>CANCEL_OTHER</Text>
</Touchable>
</View>
</View>
);
}

export default connect(
null,
{
addOneAction: addOne,
subOneAction: subOne,
otherAction: other,
cancelOtherAction: cancelOther,
},
)(ActionButtons);

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
title: {
fontSize: 17,
color: 'rgba(96,100,109, 1)',
lineHeight: 24,
textAlign: 'center',
fontWeight: 'bold',
},
subtitle: {
fontSize: 14,
color: 'rgba(96,100,109, 1)',
textAlign: 'center',
fontStyle: 'italic',
},
row: {
justifyContent: 'center',
flexDirection: 'row',
marginVertical: 8,
},
button: {
color: '#388E3C',
marginHorizontal: 8,
fontSize: 18,
},
});
21 changes: 21 additions & 0 deletions example/components/ConnectionToggler.js
@@ -0,0 +1,21 @@
import React from 'react';
import { Button, View } from 'react-native';
import DummyNetworkContext from '../DummyNetworkContext';

function ConnectionToggler() {
return (
<DummyNetworkContext.Consumer>
{({ toggleConnection }) => (
<View style={{ marginBottom: 30 }}>
<Button
onPress={toggleConnection}
title="Toggle Internet connection"
color="#841584"
/>
</View>
)}
</DummyNetworkContext.Consumer>
);
}

export default ConnectionToggler;
43 changes: 43 additions & 0 deletions example/components/Counter.js
@@ -0,0 +1,43 @@
import React from 'react';
import { connect } from 'react-redux';
import { StyleSheet, Text, View } from 'react-native';
import { MonoText } from './StyledText';

function Counter({ counter }) {
return (
<View style={styles.container}>
<Text style={styles.title}>Counter state:</Text>
<View style={styles.value}>
<MonoText style={styles.highlightText}>{counter}</MonoText>
</View>
</View>
);
}

export default connect(state => ({
counter: state.counter,
}))(Counter);

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
},
title: {
fontSize: 17,
color: 'rgba(96,100,109, 1)',
lineHeight: 24,
textAlign: 'center',
fontWeight: 'bold',
marginRight: 8,
},
value: {
backgroundColor: 'rgba(0,0,0,0.05)',
borderRadius: 3,
paddingHorizontal: 4,
marginVertical: 7,
},
highlightText: {
color: 'blue',
},
});

0 comments on commit 9a2ca4b

Please sign in to comment.