chore: move example folder to root

This commit is contained in:
Satyajit Sahoo
2019-12-09 15:20:28 +01:00
parent 448fa642ed
commit f951e98dc6
107 changed files with 17 additions and 13 deletions

View File

@@ -0,0 +1,2 @@
import { Linking } from 'expo';
export default [Linking.makeUrl('/')];

View File

@@ -0,0 +1 @@
export default [];

View File

@@ -0,0 +1,162 @@
import * as React from 'react';
import { View, TextInput, ActivityIndicator, StyleSheet } from 'react-native';
import { Title, Button } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/core';
import {
createStackNavigator,
HeaderBackButton,
StackNavigationProp,
} from '@react-navigation/stack';
type AuthStackParams = {
splash: undefined;
home: undefined;
'sign-in': undefined;
};
const SplashScreen = () => {
return (
<View style={styles.content}>
<ActivityIndicator />
</View>
);
};
const SignInScreen = ({ onSignIn }: { onSignIn: (token: string) => void }) => {
return (
<View style={styles.content}>
<TextInput placeholder="Username" style={styles.input} />
<TextInput placeholder="Password" secureTextEntry style={styles.input} />
<Button
mode="contained"
onPress={() => onSignIn('token')}
style={styles.button}
>
Sign in
</Button>
</View>
);
};
const HomeScreen = ({ onSignOut }: { onSignOut: () => void }) => {
return (
<View style={styles.content}>
<Title style={styles.text}>Signed in successfully 🎉</Title>
<Button onPress={onSignOut} style={styles.button}>
Sign out
</Button>
</View>
);
};
const SimpleStack = createStackNavigator<AuthStackParams>();
type Props = {
navigation: StackNavigationProp<ParamListBase>;
};
type State = {
isLoading: boolean;
userToken: undefined | string;
};
type Action =
| { type: 'RESTORE_TOKEN'; token: undefined | string }
| { type: 'SIGN_IN'; token: string }
| { type: 'SIGN_OUT' };
export default function SimpleStackScreen({ navigation }: Props) {
const [state, dispatch] = React.useReducer<React.Reducer<State, Action>>(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
userToken: action.token,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
userToken: action.token,
};
case 'SIGN_OUT':
return {
...prevState,
userToken: undefined,
};
}
},
{
isLoading: true,
userToken: undefined,
}
);
React.useEffect(() => {
const timer = setTimeout(() => {
dispatch({ type: 'RESTORE_TOKEN', token: undefined });
}, 1000);
return () => clearTimeout(timer);
}, []);
navigation.setOptions({
headerShown: false,
});
return (
<SimpleStack.Navigator
screenOptions={{
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
}}
>
{state.isLoading ? (
<SimpleStack.Screen
name="splash"
component={SplashScreen}
options={{ title: `Auth Flow` }}
/>
) : state.userToken === undefined ? (
<SimpleStack.Screen name="sign-in" options={{ title: `Sign in` }}>
{() => (
<SignInScreen
onSignIn={token => dispatch({ type: 'SIGN_IN', token })}
/>
)}
</SimpleStack.Screen>
) : (
<SimpleStack.Screen name="home" options={{ title: 'Home' }}>
{() => (
<HomeScreen onSignOut={() => dispatch({ type: 'SIGN_OUT' })} />
)}
</SimpleStack.Screen>
)}
</SimpleStack.Navigator>
);
}
const styles = StyleSheet.create({
content: {
flex: 1,
padding: 16,
justifyContent: 'center',
},
input: {
margin: 8,
padding: 10,
backgroundColor: 'white',
borderRadius: 3,
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'rgba(0, 0, 0, 0.08)',
},
button: {
margin: 8,
},
text: {
textAlign: 'center',
margin: 8,
},
});

View File

@@ -0,0 +1,69 @@
import * as React from 'react';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import TouchableBounce from '../Shared/TouchableBounce';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack';
const getTabBarIcon = (name: string) => ({
color,
size,
}: {
color: string;
size: number;
}) => <MaterialCommunityIcons name={name} color={color} size={size} />;
type BottomTabParams = {
article: undefined;
albums: undefined;
contacts: undefined;
chat: undefined;
};
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
export default function BottomTabsScreen() {
return (
<BottomTabs.Navigator
screenOptions={{
tabBarButton: props => <TouchableBounce {...props} />,
}}
>
<BottomTabs.Screen
name="article"
options={{
title: 'Article',
tabBarIcon: getTabBarIcon('file-document-box'),
}}
>
{props => <SimpleStackScreen {...props} headerMode="none" />}
</BottomTabs.Screen>
<BottomTabs.Screen
name="chat"
component={Chat}
options={{
tabBarLabel: 'Chat',
tabBarIcon: getTabBarIcon('message-reply'),
}}
/>
<BottomTabs.Screen
name="contacts"
component={Contacts}
options={{
title: 'Contacts',
tabBarIcon: getTabBarIcon('contacts'),
}}
/>
<BottomTabs.Screen
name="albums"
component={Albums}
options={{
title: 'Albums',
tabBarIcon: getTabBarIcon('image-album'),
}}
/>
</BottomTabs.Navigator>
);
}

View File

@@ -0,0 +1,114 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import {
createCompatNavigatorFactory,
CompatScreenType,
} from '@react-navigation/compat';
import {
createStackNavigator,
StackNavigationProp,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type CompatStackParams = {
Article: { author: string };
Album: undefined;
};
const ArticleScreen: CompatScreenType<StackNavigationProp<
CompatStackParams,
'Article'
>> = ({ navigation }) => {
return (
<React.Fragment>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article author={{ name: navigation.getParam('author') }} />
</React.Fragment>
);
};
ArticleScreen.navigationOptions = ({ navigation }) => ({
title: `Article by ${navigation.getParam('author')}`,
});
const AlbumsScreen: CompatScreenType<StackNavigationProp<
CompatStackParams
>> = ({ navigation }) => {
return (
<React.Fragment>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums />
</React.Fragment>
);
};
const CompatStack = createCompatNavigatorFactory(createStackNavigator)<
StackNavigationProp<CompatStackParams>
>(
{
Article: {
screen: ArticleScreen,
params: {
author: 'Gandalf',
},
},
Album: AlbumsScreen,
},
{
mode: 'modal',
}
);
export default function CompatStackScreen({
navigation,
}: {
navigation: StackNavigationProp<{}>;
}) {
navigation.setOptions({
headerShown: false,
});
return <CompatStack />;
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -0,0 +1,69 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack';
type MaterialBottomTabParams = {
article: undefined;
albums: undefined;
contacts: undefined;
chat: undefined;
};
const MaterialBottomTabs = createMaterialBottomTabNavigator<
MaterialBottomTabParams
>();
export default function MaterialBottomTabsScreen() {
return (
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
<MaterialBottomTabs.Screen
name="article"
options={{
tabBarLabel: 'Article',
tabBarIcon: 'file-document-box',
tabBarColor: '#C9E7F8',
}}
>
{props => <SimpleStackScreen {...props} headerMode="none" />}
</MaterialBottomTabs.Screen>
<MaterialBottomTabs.Screen
name="chat"
component={Chat}
options={{
tabBarLabel: 'Chat',
tabBarIcon: 'message-reply',
tabBarColor: '#9FD5C9',
tabBarBadge: true,
}}
/>
<MaterialBottomTabs.Screen
name="contacts"
component={Contacts}
options={{
tabBarLabel: 'Contacts',
tabBarIcon: 'contacts',
tabBarColor: '#F7EAA2',
}}
/>
<MaterialBottomTabs.Screen
name="albums"
component={Albums}
options={{
tabBarLabel: 'Albums',
tabBarIcon: 'image-album',
tabBarColor: '#FAD4D6',
}}
/>
</MaterialBottomTabs.Navigator>
);
}
const styles = StyleSheet.create({
tabBar: {
backgroundColor: 'white',
},
});

View File

@@ -0,0 +1,54 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
type MaterialTopTabParams = {
albums: undefined;
contacts: undefined;
chat: undefined;
};
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
export default function MaterialTopTabsScreen() {
return (
<MaterialTopTabs.Navigator
tabBarOptions={{
style: styles.tabBar,
labelStyle: styles.tabLabel,
indicatorStyle: styles.tabIndicator,
}}
>
<MaterialTopTabs.Screen
name="chat"
component={Chat}
options={{ title: 'Chat' }}
/>
<MaterialTopTabs.Screen
name="contacts"
component={Contacts}
options={{ title: 'Contacts' }}
/>
<MaterialTopTabs.Screen
name="albums"
component={Albums}
options={{ title: 'Albums' }}
/>
</MaterialTopTabs.Navigator>
);
}
const styles = StyleSheet.create({
tabBar: {
backgroundColor: 'white',
},
tabLabel: {
color: 'black',
},
tabIndicator: {
backgroundColor: 'tomato',
},
});

View File

@@ -0,0 +1,131 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import { useSafeArea } from 'react-native-safe-area-context';
import { RouteProp, ParamListBase } from '@react-navigation/core';
import {
createStackNavigator,
StackNavigationProp,
TransitionPresets,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type SimpleStackParams = {
article: { author: string };
album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const ArticleScreen = ({
navigation,
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>;
}) => {
const insets = useSafeArea();
return (
<React.Fragment>
<View style={[styles.buttons, { marginTop: insets.top }]}>
<Button
mode="contained"
onPress={() => navigation.push('album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} />
</React.Fragment>
);
};
const AlbumsScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
const insets = useSafeArea();
return (
<React.Fragment>
<View style={[styles.buttons, { marginTop: insets.top }]}>
<Button
mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums />
</React.Fragment>
);
};
const ModalPresentationStack = createStackNavigator<SimpleStackParams>();
type Props = {
options?: React.ComponentProps<typeof ModalPresentationStack.Navigator>;
navigation: StackNavigationProp<ParamListBase>;
};
export default function SimpleStackScreen({ navigation, options }: Props) {
navigation.setOptions({
headerShown: false,
});
return (
<ModalPresentationStack.Navigator
mode="modal"
headerMode="none"
screenOptions={{
...TransitionPresets.ModalPresentationIOS,
cardOverlayEnabled: true,
gestureEnabled: true,
}}
{...options}
>
<ModalPresentationStack.Screen
name="article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<ModalPresentationStack.Screen
name="album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</ModalPresentationStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default function NativeStack() {
return (
<View style={styles.container}>
<Text style={styles.text}>Currently not supported on Expo :(</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#eceff1',
},
text: {
fontSize: 16,
color: '#999',
},
});

View File

@@ -0,0 +1,214 @@
import * as React from 'react';
import { View, Text, ScrollView, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
// eslint-disable-next-line import/no-unresolved
import { enableScreens } from 'react-native-screens';
import {
RouteProp,
ParamListBase,
useFocusEffect,
} from '@react-navigation/core';
import { DrawerNavigationProp } from '@react-navigation/drawer';
import { StackNavigationProp } from '@react-navigation/stack';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import Albums from '../Shared/Albums';
type NativeStackParams = {
article: { author: string };
album: undefined;
};
type NativeStackNavigation = NativeStackNavigationProp<NativeStackParams>;
const ArticleScreen = ({
navigation,
}: {
navigation: NativeStackNavigation;
route: RouteProp<NativeStackParams, 'article'>;
}) => (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Text style={styles.title}>What is Lorem Ipsum?</Text>
<Text style={styles.paragraph}>
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry&apos;s standard dummy text ever since
the 1500s, when an unknown printer took a galley of type and scrambled it
to make a type specimen book. It has survived not only five centuries, but
also the leap into electronic typesetting, remaining essentially
unchanged. It was popularised in the 1960s with the release of Letraset
sheets containing Lorem Ipsum passages, and more recently with desktop
publishing software like Aldus PageMaker including versions of Lorem
Ipsum.
</Text>
<Text style={styles.title}>Where does it come from?</Text>
<Text style={styles.paragraph}>
Contrary to popular belief, Lorem Ipsum is not simply random text. It has
roots in a piece of classical Latin literature from 45 BC, making it over
2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney
College in Virginia, looked up one of the more obscure Latin words,
consectetur, from a Lorem Ipsum passage, and going through the cites of
the word in classical literature, discovered the undoubtable source. Lorem
Ipsum comes from sections 1.10.32 and 1.10.33 of &quot;de Finibus Bonorum
et Malorum&quot; (The Extremes of Good and Evil) by Cicero, written in 45
BC. This book is a treatise on the theory of ethics, very popular during
the Renaissance. The first line of Lorem Ipsum, &quot;Lorem ipsum dolor
sit amet..&quot;, comes from a line in section 1.10.32.
</Text>
<Text style={styles.paragraph}>
The standard chunk of Lorem Ipsum used since the 1500s is reproduced below
for those interested. Sections 1.10.32 and 1.10.33 from &quot;de Finibus
Bonorum et Malorum&quot; by Cicero are also reproduced in their exact
original form, accompanied by English versions from the 1914 translation
by H. Rackham.
</Text>
<Text style={styles.title}>Why do we use it?</Text>
<Text style={styles.paragraph}>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout. The point of using
Lorem Ipsum is that it has a more-or-less normal distribution of letters,
as opposed to using &quot;Content here, content here&quot;, making it look
like readable English. Many desktop publishing packages and web page
editors now use Lorem Ipsum as their default model text, and a search for
&quot;lorem ipsum&quot; will uncover many web sites still in their
infancy. Various versions have evolved over the years, sometimes by
accident, sometimes on purpose (injected humour and the like).
</Text>
<Text style={styles.title}>Where can I get some?</Text>
<Text style={styles.paragraph}>
There are many variations of passages of Lorem Ipsum available, but the
majority have suffered alteration in some form, by injected humour, or
randomised words which don&apos;t look even slightly believable. If you
are going to use a passage of Lorem Ipsum, you need to be sure there
isn&apos;t anything embarrassing hidden in the middle of text. All the
Lorem Ipsum generators on the Internet tend to repeat predefined chunks as
necessary, making this the first true generator on the Internet. It uses a
dictionary of over 200 Latin words, combined with a handful of model
sentence structures, to generate Lorem Ipsum which looks reasonable. The
generated Lorem Ipsum is therefore always free from repetition, injected
humour, or non-characteristic words etc.
</Text>
</ScrollView>
);
const AlbumsScreen = ({
navigation,
}: {
navigation: NativeStackNavigation;
}) => (
<React.Fragment>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums />
</React.Fragment>
);
const NativeStack = createNativeStackNavigator<NativeStackParams>();
type Props = {
navigation: StackNavigationProp<ParamListBase>;
};
export default function NativeStackScreen({ navigation }: Props) {
navigation.setOptions({
headerShown: false,
});
useFocusEffect(
React.useCallback(() => {
const drawer = navigation.dangerouslyGetParent() as DrawerNavigationProp<
ParamListBase
>;
navigation.setOptions({ gestureEnabled: false });
drawer.setOptions({ gestureEnabled: false });
return () => {
navigation.setOptions({ gestureEnabled: true });
drawer.setOptions({ gestureEnabled: true });
};
}, [navigation])
);
return (
<NativeStack.Navigator>
<NativeStack.Screen
name="article"
component={ArticleScreen}
options={{
title: 'Lorem Ipsum',
headerLargeTitle: true,
headerHideShadow: true,
}}
/>
<NativeStack.Screen
name="album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</NativeStack.Navigator>
);
}
enableScreens(true);
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
container: {
backgroundColor: 'white',
},
content: {
paddingVertical: 16,
},
title: {
color: '#000',
fontWeight: 'bold',
fontSize: 24,
marginVertical: 8,
marginHorizontal: 16,
},
paragraph: {
color: '#000',
fontSize: 16,
lineHeight: 24,
marginVertical: 8,
marginHorizontal: 16,
},
});

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default function NativeStack() {
return (
<View style={styles.container}>
<Text style={styles.text}>Not supported on Web :(</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#eceff1',
},
text: {
fontSize: 16,
color: '#999',
},
});

View File

@@ -0,0 +1,115 @@
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/core';
import {
createStackNavigator,
StackNavigationProp,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
type SimpleStackParams = {
article: { author: string };
album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const ArticleScreen = ({
navigation,
route,
}: {
navigation: SimpleStackNavigation;
route: RouteProp<SimpleStackParams, 'article'>;
}) => {
return (
<React.Fragment>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('album')}
style={styles.button}
>
Push album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Article author={{ name: route.params.author }} />
</React.Fragment>
);
};
const AlbumsScreen = ({
navigation,
}: {
navigation: SimpleStackNavigation;
}) => {
return (
<React.Fragment>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<Albums />
</React.Fragment>
);
};
const SimpleStack = createStackNavigator<SimpleStackParams>();
type Props = Partial<React.ComponentProps<typeof SimpleStack.Navigator>> & {
navigation: StackNavigationProp<ParamListBase>;
};
export default function SimpleStackScreen({ navigation, ...rest }: Props) {
navigation.setOptions({
headerShown: false,
});
return (
<SimpleStack.Navigator {...rest}>
<SimpleStack.Screen
name="article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</SimpleStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -0,0 +1,51 @@
import * as React from 'react';
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
const COVERS = [
require('../../assets/album-art-1.jpg'),
require('../../assets/album-art-2.jpg'),
require('../../assets/album-art-3.jpg'),
require('../../assets/album-art-4.jpg'),
require('../../assets/album-art-5.jpg'),
require('../../assets/album-art-6.jpg'),
require('../../assets/album-art-7.jpg'),
require('../../assets/album-art-8.jpg'),
];
export default function Albums() {
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
return (
<ScrollView
ref={ref}
style={styles.container}
contentContainerStyle={styles.content}
>
{COVERS.map((source, i) => (
// eslint-disable-next-line react/no-array-index-key
<Image key={i} source={source} style={styles.cover} />
))}
{COVERS.map((source, i) => (
// eslint-disable-next-line react/no-array-index-key
<Image key={i + 'F'} source={source} style={styles.cover} />
))}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#343C46',
},
content: {
flexDirection: 'row',
flexWrap: 'wrap',
},
cover: {
width: '50%',
height: Dimensions.get('window').width / 2,
},
});

View File

@@ -0,0 +1,115 @@
import * as React from 'react';
import { View, Text, Image, ScrollView, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
type Props = {
date?: string;
author?: {
name: string;
};
};
export default function Article({
date = '1st Jan 2025',
author = {
name: 'Knowledge Bot',
},
}: Props) {
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
return (
<ScrollView
ref={ref}
style={styles.container}
contentContainerStyle={styles.content}
>
<View style={styles.author}>
<Image
style={styles.avatar}
source={require('../../assets/avatar-1.png')}
/>
<View style={styles.meta}>
<Text style={styles.name}>{author.name}</Text>
<Text style={styles.timestamp}>{date}</Text>
</View>
</View>
<Text style={styles.title}>Lorem Ipsum</Text>
<Text style={styles.paragraph}>
Contrary to popular belief, Lorem Ipsum is not simply random text. It
has roots in a piece of classical Latin literature from 45 BC, making it
over 2000 years old.
</Text>
<Image style={styles.image} source={require('../../assets/book.jpg')} />
<Text style={styles.paragraph}>
Richard McClintock, a Latin professor at Hampden-Sydney College in
Virginia, looked up one of the more obscure Latin words, consectetur,
from a Lorem Ipsum passage, and going through the cites of the word in
classical literature, discovered the undoubtable source.
</Text>
<Text style={styles.paragraph}>
Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of &quot;de Finibus
Bonorum et Malorum&quot; (The Extremes of Good and Evil) by Cicero,
written in 45 BC. This book is a treatise on the theory of ethics, very
popular during the Renaissance. The first line of Lorem Ipsum,
&quot;Lorem ipsum dolor sit amet..&quot;, comes from a line in section
1.10.32.
</Text>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
},
content: {
paddingVertical: 16,
},
author: {
flexDirection: 'row',
marginVertical: 8,
marginHorizontal: 16,
},
meta: {
marginHorizontal: 8,
justifyContent: 'center',
},
name: {
color: '#000',
fontWeight: 'bold',
fontSize: 16,
lineHeight: 24,
},
timestamp: {
color: '#999',
fontSize: 14,
lineHeight: 21,
},
avatar: {
height: 48,
width: 48,
borderRadius: 24,
},
title: {
color: '#000',
fontWeight: 'bold',
fontSize: 36,
marginVertical: 8,
marginHorizontal: 16,
},
paragraph: {
color: '#000',
fontSize: 16,
lineHeight: 24,
marginVertical: 8,
marginHorizontal: 16,
},
image: {
width: '100%',
height: 200,
resizeMode: 'cover',
marginVertical: 8,
},
});

118
example/src/Shared/Chat.tsx Normal file
View File

@@ -0,0 +1,118 @@
import * as React from 'react';
import {
View,
Image,
Text,
TextInput,
ScrollView,
StyleSheet,
} from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
const MESSAGES = [
'okay',
'sudo make me a sandwich',
'what? make it yourself',
'make me a sandwich',
];
export default function Chat() {
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
return (
<View style={styles.container}>
<ScrollView
style={styles.inverted}
contentContainerStyle={styles.content}
>
{MESSAGES.map((text, i) => {
const odd = i % 2;
return (
<View
// eslint-disable-next-line react/no-array-index-key
key={i}
style={[odd ? styles.odd : styles.even, styles.inverted]}
>
<Image
style={styles.avatar}
source={
odd
? require('../../assets/avatar-2.png')
: require('../../assets/avatar-1.png')
}
/>
<View
style={[styles.bubble, odd ? styles.received : styles.sent]}
>
<Text style={odd ? styles.receivedText : styles.sentText}>
{text}
</Text>
</View>
</View>
);
})}
</ScrollView>
<TextInput
style={styles.input}
placeholder="Write a message"
underlineColorAndroid="transparent"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#eceff1',
},
inverted: {
transform: [{ scaleY: -1 }],
},
content: {
padding: 16,
},
even: {
flexDirection: 'row',
},
odd: {
flexDirection: 'row-reverse',
},
avatar: {
marginVertical: 8,
marginHorizontal: 6,
height: 40,
width: 40,
borderRadius: 20,
borderColor: 'rgba(0, 0, 0, .16)',
borderWidth: StyleSheet.hairlineWidth,
},
bubble: {
marginVertical: 8,
marginHorizontal: 6,
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 20,
},
sent: {
backgroundColor: '#cfd8dc',
},
received: {
backgroundColor: '#2196F3',
},
sentText: {
color: 'black',
},
receivedText: {
color: 'white',
},
input: {
height: 48,
paddingVertical: 12,
paddingHorizontal: 24,
backgroundColor: 'white',
},
});

View File

@@ -0,0 +1,137 @@
import * as React from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
type Item = { name: string; number: number };
const CONTACTS: Item[] = [
{ name: 'Marissa Castillo', number: 7766398169 },
{ name: 'Denzel Curry', number: 9394378449 },
{ name: 'Miles Ferguson', number: 8966872888 },
{ name: 'Desiree Webster', number: 6818656371 },
{ name: 'Samantha Young', number: 6538288534 },
{ name: 'Irene Hunter', number: 2932176249 },
{ name: 'Annie Ryan', number: 4718456627 },
{ name: 'Sasha Oliver', number: 9743195919 },
{ name: 'Jarrod Avila', number: 8339212305 },
{ name: 'Griffin Weaver', number: 6059349721 },
{ name: 'Emilee Moss', number: 7382905180 },
{ name: 'Angelique Oliver', number: 9689298436 },
{ name: 'Emanuel Little', number: 6673376805 },
{ name: 'Wayne Day', number: 6918839582 },
{ name: 'Lauren Reese', number: 4652613201 },
{ name: 'Kailey Ward', number: 2232609512 },
{ name: 'Gabrielle Newman', number: 2837997127 },
{ name: 'Luke Strickland', number: 8404732322 },
{ name: 'Payton Garza', number: 7916140875 },
{ name: 'Anna Moss', number: 3504954657 },
{ name: 'Kailey Vazquez', number: 3002136330 },
{ name: 'Jennifer Coleman', number: 5469629753 },
{ name: 'Cindy Casey', number: 8446175026 },
{ name: 'Dillon Doyle', number: 5614510703 },
{ name: 'Savannah Garcia', number: 5634775094 },
{ name: 'Kailey Hudson', number: 3289239675 },
{ name: 'Ariel Green', number: 2103492196 },
{ name: 'Weston Perez', number: 2984221823 },
{ name: 'Kari Juarez', number: 9502125065 },
{ name: 'Sara Sanders', number: 7696668206 },
{ name: 'Griffin Le', number: 3396937040 },
{ name: 'Fernando Valdez', number: 9124257306 },
{ name: 'Taylor Marshall', number: 9656072372 },
{ name: 'Elias Dunn', number: 9738536473 },
{ name: 'Diane Barrett', number: 6886824829 },
{ name: 'Samuel Freeman', number: 5523948094 },
{ name: 'Irene Garza', number: 2077694008 },
{ name: 'Devante Alvarez', number: 9897002645 },
{ name: 'Sydney Floyd', number: 6462897254 },
{ name: 'Toni Dixon', number: 3775448213 },
{ name: 'Anastasia Spencer', number: 4548212752 },
{ name: 'Reid Cortez', number: 6668056507 },
{ name: 'Ramon Duncan', number: 8889157751 },
{ name: 'Kenny Moreno', number: 5748219540 },
{ name: 'Shelby Craig', number: 9473708675 },
{ name: 'Jordyn Brewer', number: 7552277991 },
{ name: 'Tanya Walker', number: 4308189657 },
{ name: 'Nolan Figueroa', number: 9173443776 },
{ name: 'Sophia Gibbs', number: 6435942770 },
{ name: 'Vincent Sandoval', number: 2606111495 },
];
class ContactItem extends React.PureComponent<{
item: { name: string; number: number };
}> {
render() {
const { item } = this.props;
return (
<View style={styles.item}>
<View style={styles.avatar}>
<Text style={styles.letter}>
{item.name.slice(0, 1).toUpperCase()}
</Text>
</View>
<View style={styles.details}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.number}>{item.number}</Text>
</View>
</View>
);
}
}
export default function Contacts() {
const ref = React.useRef<FlatList<Item>>(null);
useScrollToTop(ref);
const renderItem = ({ item }: { item: Item }) => <ContactItem item={item} />;
const ItemSeparator = () => <View style={styles.separator} />;
return (
<FlatList
ref={ref}
data={CONTACTS}
keyExtractor={(_, i) => String(i)}
renderItem={renderItem}
ItemSeparatorComponent={ItemSeparator}
/>
);
}
const styles = StyleSheet.create({
item: {
backgroundColor: 'white',
flexDirection: 'row',
alignItems: 'center',
padding: 8,
},
avatar: {
height: 36,
width: 36,
borderRadius: 18,
backgroundColor: '#e91e63',
alignItems: 'center',
justifyContent: 'center',
},
letter: {
color: 'white',
fontWeight: 'bold',
},
details: {
margin: 8,
},
name: {
fontWeight: 'bold',
fontSize: 14,
color: 'black',
},
number: {
fontSize: 12,
color: '#999',
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: 'rgba(0, 0, 0, .08)',
},
});

View File

@@ -0,0 +1,4 @@
// @ts-ignore
import TouchableBounce from 'react-native/Libraries/Components/Touchable/TouchableBounce';
export default TouchableBounce;

View File

@@ -0,0 +1,3 @@
import { TouchableOpacity } from 'react-native';
export default TouchableOpacity;

207
example/src/index.tsx Normal file
View File

@@ -0,0 +1,207 @@
import * as React from 'react';
import { ScrollView, AsyncStorage, YellowBox } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { Appbar, List } from 'react-native-paper';
import { Asset } from 'expo-asset';
import {
InitialState,
getStateFromPath,
NavigationContainerRef,
} from '@react-navigation/core';
import {
useLinking,
NavigationNativeContainer,
} from '@react-navigation/native';
import {
createDrawerNavigator,
DrawerNavigationProp,
} from '@react-navigation/drawer';
import {
createStackNavigator,
Assets as StackAssets,
StackNavigationProp,
} from '@react-navigation/stack';
import LinkingPrefixes from './LinkingPrefixes';
import SimpleStack from './Screens/SimpleStack';
import NativeStack from './Screens/NativeStack';
import ModalPresentationStack from './Screens/ModalPresentationStack';
import BottomTabs from './Screens/BottomTabs';
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
import AuthFlow from './Screens/AuthFlow';
import CompatAPI from './Screens/CompatAPI';
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
type RootDrawerParamList = {
Root: undefined;
Another: undefined;
};
type RootStackParamList = {
Home: undefined;
} & {
[P in keyof typeof SCREENS]: undefined;
};
const SCREENS = {
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
NativeStack: { title: 'Native Stack', component: NativeStack },
ModalPresentationStack: {
title: 'Modal Presentation Stack',
component: ModalPresentationStack,
},
BottomTabs: { title: 'Bottom Tabs', component: BottomTabs },
MaterialTopTabs: {
title: 'Material Top Tabs',
component: MaterialTopTabsScreen,
},
MaterialBottomTabs: {
title: 'Material Bottom Tabs',
component: MaterialBottomTabs,
},
AuthFlow: {
title: 'Auth Flow',
component: AuthFlow,
},
CompatAPI: {
title: 'Compat Layer',
component: CompatAPI,
},
};
const Drawer = createDrawerNavigator<RootDrawerParamList>();
const Stack = createStackNavigator<RootStackParamList>();
const PERSISTENCE_KEY = 'NAVIGATION_STATE';
Asset.loadAsync(StackAssets);
export default function App() {
const containerRef = React.useRef<NavigationContainerRef>();
// To test deep linking on, run the following in the Terminal:
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
const { getInitialState } = useLinking(containerRef, {
prefixes: LinkingPrefixes,
getStateFromPath: path => {
const state = getStateFromPath(path);
return {
routes: [
{
name: 'root',
state: {
...state,
routes: [{ name: 'home' }, ...(state ? state.routes : [])],
},
},
],
};
},
});
const [isReady, setIsReady] = React.useState(false);
const [initialState, setInitialState] = React.useState<
InitialState | undefined
>();
React.useEffect(() => {
const restoreState = async () => {
try {
let state = await getInitialState();
if (state === undefined) {
const savedState = await AsyncStorage.getItem(PERSISTENCE_KEY);
state = savedState ? JSON.parse(savedState) : undefined;
}
if (state !== undefined) {
setInitialState(state);
}
} finally {
setIsReady(true);
}
};
restoreState();
}, [getInitialState]);
if (!isReady) {
return null;
}
return (
<NavigationNativeContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))
}
>
<Drawer.Navigator>
<Drawer.Screen
name="Root"
options={{
title: 'Examples',
drawerIcon: ({ size, color }) => (
<MaterialIcons size={size} color={color} name="folder" />
),
}}
>
{({
navigation,
}: {
navigation: DrawerNavigationProp<RootDrawerParamList>;
}) => (
<Stack.Navigator>
<Stack.Screen
name="Home"
options={{
title: 'Examples',
headerLeft: () => (
<Appbar.Action
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
}}
>
{({
navigation,
}: {
navigation: StackNavigationProp<RootStackParamList>;
}) => (
<ScrollView>
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
name => (
<List.Item
key={name}
title={SCREENS[name].title}
onPress={() => navigation.push(name)}
/>
)
)}
</ScrollView>
)}
</Stack.Screen>
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
name => (
<Stack.Screen
key={name}
name={name}
component={SCREENS[name].component}
options={{ title: SCREENS[name].title }}
/>
)
)}
</Stack.Navigator>
)}
</Drawer.Screen>
</Drawer.Navigator>
</NavigationNativeContainer>
);
}