Merge pull request #9 from ThomasRubini/navigation

Cleared app.tsx and added navigation stack
This commit is contained in:
Djalim Simaila 2023-04-10 03:48:27 +02:00 committed by GitHub
commit 871fabefc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 474 additions and 191 deletions

2
.gitignore vendored
View File

@ -36,7 +36,7 @@ local.properties
# vscode
.vscode
.history
# node.js
#
node_modules/

View File

@ -10,13 +10,23 @@
"test": "jest"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.18.1",
"@react-navigation/material-bottom-tabs": "^6.2.15",
"@react-navigation/native": "^6.1.6",
"@react-navigation/native-stack": "^6.9.12",
"@reduxjs/toolkit": "^1.9.3",
"axios": "^1.3.4",
"expo": "^48.0.9",
"expo-cli": "^6.3.2",
"react": "18.2.0",
"react-native": "0.71.4",
"react-native-async-storage": "^0.0.1",
"react-native-paper": "^5.6.0",
"react-native-simple-toast": "^2.0.0",
"react-native-vector-icons": "^9.2.0"
"react-native-vector-icons": "^9.2.0",
"react-redux": "^8.0.5",
"redux": "^4.2.1",
"redux-persist": "^6.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
@ -26,6 +36,7 @@
"@tsconfig/react-native": "^2.0.2",
"@types/jest": "^29.2.1",
"@types/react": "^18.0.24",
"@types/react-native": "^0.71.5",
"@types/react-native-vector-icons": "^6.4.13",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1",

View File

@ -4,8 +4,8 @@
*
* @format
*/
import React from 'react';
import * as React from 'react';
import {NavigationContainer, StackActions} from '@react-navigation/native';
import type {PropsWithChildren} from 'react';
import {
SafeAreaView,
@ -17,120 +17,84 @@ import {
View,
} from 'react-native';
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
import ClipViewLocal from './clip/ClipViewLocal';
import ClipViewRemote from './clip/ClipViewRemote';
import {createMaterialBottomTabNavigator} from '@react-navigation/material-bottom-tabs';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import SignIn from './auth/SignIn';
import SignUp from './auth/SignUp';
import {Provider} from 'react-redux';
import {store, persistor} from './redux/store';
import {PersistGate} from 'redux-persist/integration/react';
type SectionProps = PropsWithChildren<{
title: string;
}>;
const Stack = createNativeStackNavigator();
const Tab = createMaterialBottomTabNavigator();
function Section({children, title}: SectionProps): JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
class App extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
token: '',
username: '',
};
store.subscribe(() => {
this.setState({token: store.getState().persistedUserReducer.token});
});
}
render(): React.ReactNode {
console.log('render app', store.getState());
console.log('app state', this.state);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor} />
<NavigationContainer>
<Tab.Navigator>
{
this.state.token === '' ? (
////////////////////////////////////////////////////////////
<>
<Tab.Screen
children={() => <SignIn store={store} />}
name="Login"
options={{title: 'Connexion'}}
/>
<Tab.Screen
children={() => <SignUp store={store} />}
name="Register"
options={{title: 'Créer un compte'}}
/>
</>
) : (
////////////////////////////////////////////////////////////
<>
<Tab.Screen name="Local" options={{title: 'local'}}>
{props => (
<ClipViewLocal
store={store}
navigation={props.navigation}
type={'local'}
/>
)}
</Tab.Screen>
<Tab.Screen name="Remote" options={{title: 'distant'}}>
{props => (
<ClipViewRemote
store={store}
navigation={props.navigation}
type={'remote'}
/>
)}
</Tab.Screen>
</>
)
////////////////////////////////////////////////////////////
}
</Tab.Navigator>
</NavigationContainer>
</Provider>
);
}
}
function App(): JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<Header />
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Section title="Step caca">
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
screen and then come back to see your edits.
</Section>
<Section title="Voir tes changements">
<ReloadInstructions />
</Section>
<Section title="Voir tes changements">
<ReloadInstructions />
</Section>
<Section title="Voir tes changements">
<ReloadInstructions />
</Section>
<Section title="Voir tes changements">
<ReloadInstructions />
</Section>
<Section title="Voir tes changements">
<ReloadInstructions />
</Section>
<Section title="Voir tes changements">
<ReloadInstructions />
</Section>
<Section title="Voir tes changements">
<ReloadInstructions />
</Section>
<Section title="Debug">
<DebugInstructions />
</Section>
<Section title="Learn More">
Read the docs to discover what to do next:
</Section>
<LearnMoreLinks />
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});
export default App;

53
src/auth/SignIn.tsx Normal file
View File

@ -0,0 +1,53 @@
import React from 'react';
import { View, Text, Button, TextInput } from 'react-native';
import { setUser } from '../redux/actions';
export default class SignIn extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
username: '',
password: ''
}
this.updateUsername = this.updateUsername.bind(this);
this.updatePassword = this.updatePassword.bind(this);
this.signInFunction = this.signInFunction.bind(this);
}
async signInFunction() {
const data = new FormData();
data.append("username", this.state.username);
data.append("password", this.state.password);
const signInResponse = await fetch(
'https://notifysync.simailadjalim.fr/user',
{
method: 'POST',
body: data
}
);
const signInJson = await signInResponse.json();
if (signInJson.status === "ok") {
this.props.store.dispatch(setUser(signInJson.token));
} else console.log(signInJson);
}
updateUsername(event: any) {
this.setState({ username: event.target.value });
}
updatePassword(event: any) {
this.setState({ password: event.target.value });
}
render(): React.ReactNode {
return (
<View>
<Text style={{ fontWeight: 'bold', fontSize: 30, margin: 20 }}>Connexion</Text>
<TextInput placeholder="Nom d'utilisateur" value={this.state.username} onChange={this.updateUsername} />
<TextInput placeholder="Mot de Passe" value={this.state.password} onChange={this.updatePassword} />
<Button title="Se connecter" onPress={this.signInFunction} />
</View>
);
}
}

77
src/auth/SignUp.tsx Normal file
View File

@ -0,0 +1,77 @@
import React from 'react';
import {View, Text, Button, TextInput} from 'react-native';
import {accessibilityProps} from 'react-native-paper/lib/typescript/src/components/MaterialCommunityIcon';
import {setUser} from '../redux/actions';
export default class SignUp extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
username: '',
password: '',
};
this.signUpFunction = this.signUpFunction.bind(this);
this.updateUsername = this.updateUsername.bind(this);
this.updatePassword = this.updatePassword.bind(this);
}
async signUpFunction() {
const data = new FormData();
data.append('username', this.state.username);
data.append('password', this.state.password);
const signUpResponse = await fetch(
'https://notifysync.simailadjalim.fr/user',
{
method: 'PUT',
body: data,
},
);
const signUpJson = await signUpResponse.json();
if (signUpJson.status === 'ok') {
const signInResponse = await fetch(
'https://notifysync.simailadjalim.fr/user',
{
method: 'POST',
body: data,
},
);
const signInJson = await signInResponse.json();
if (signInJson.status === 'ok') {
this.props.store.dispatch(setUser(signInJson.token));
} else {
console.log(signInJson);
}
} else {
console.log(signUpJson);
}
}
updateUsername(username: string) {
this.setState({username: username});
}
updatePassword(password: any) {
this.setState({password: password});
}
render(): React.ReactNode {
return (
<View>
<Text style={{fontWeight: 'bold', fontSize: 30, margin: 20}}>
Créer un compte
</Text>
<TextInput
placeholder="Nom d'utilisateur"
value={this.state.username}
onChangeText={this.updateUsername}
/>
<TextInput
placeholder="Mot de Passe"
value={this.state.password}
onChangeText={this.updatePassword}
/>
<Button title="S'enregistrer" onPress={this.signUpFunction} />
</View>
);
}
}

23
src/clip/AClipView.tsx Normal file
View File

@ -0,0 +1,23 @@
import React from 'react';
import { Button, Text } from 'react-native';
import { clearUser } from '../redux/actions';
export default abstract class AClipView extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
clips: []
}
}
getSignOutBtn() {
return <Button title="Sign out" onPress={() => {
this.props.store.dispatch(clearUser());
}} />
}
abstract getClips(): any;
abstract componentDidMount(): any;
}

View File

@ -1,17 +1,46 @@
import { View, Text } from 'react-native';
import {View, Text, Button} from 'react-native';
import IconVector from 'react-native-vector-icons/FontAwesome5';
import AClipElement from './AClipElement';
import Toast from 'react-native-simple-toast';
export default class ClipElementLocal extends AClipElement {
constructor(props: any) {
super(props);
}
constructor(props: any) {
super(props);
}
async sendToRemote() {
const data = new FormData();
data.append(
'token',
this.props.store.getState().persistedUserReducer.token,
);
data.append('content', this.props.content);
data.append('deviceName', 'TODOChangeThisMobileDevice');
const sendToRemoteResponse = await fetch(
'https://notifysync.simailadjalim.fr/clipboard',
{
method: 'PUT',
body: data,
},
);
const response = await sendToRemoteResponse.json();
Toast.show(this.props.content + 'was sent to the server', Toast.SHORT);
}
render(): JSX.Element {
return <View style={{flex:1,margin:10,flexDirection:'row',justifyContent:'space-between',alignItems:'center'}}>
<Text style={{fontSize:20,}}>{this.props.content}</Text>
<IconVector name="clipboard" size={40} onPress={() => this.onCopy()} />
</View>;
}
}
render(): JSX.Element {
return (
<View
style={{
flex: 1,
margin: 10,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<Text style={{fontSize: 20}}>{this.props.content.length >28 ?this.props.content.slice(0,24)+"...":this.props.content }</Text>
<IconVector name="sendTo" size={40} onPress={() => this.sendToRemote()} />
<IconVector name="clipboard" size={40} onPress={() => this.onCopy()} />
</View>
);
}
}

View File

@ -9,11 +9,13 @@ export default class ClipElementRemote extends AClipElement {
}
render(): JSX.Element {
const date= new Date(this.props.timestamp*1000);
console.log(this.props.timestamp*1000);
return <View style={{flex:1,margin:10,flexDirection:'row',justifyContent:'space-between',alignItems:'center'}}>
<View style={{flex:1,margin:10,flexDirection:'column'}}>
<Text style={{fontSize:20,}}>{this.props.content}</Text>
<Text style={{fontSize:20,}}>{this.props.content.length >28 ?this.props.content.slice(0,24)+"...":this.props.content}</Text>
<Text style={{fontSize:10,}}>{this.props.deviceName}</Text>
<Text style={{fontSize:10,}}>{this.props.timestamp}</Text>
<Text style={{fontSize:10,}}>{date.getHours() + ":" + date.getMinutes() + ", "+ date.toDateString()}</Text>
</View>
<IconVector name="clipboard" size={40} onPress={() => this.onCopy()} />
</View>;

View File

@ -1,26 +1,47 @@
import React from 'react';
import { ScrollView } from 'react-native';
import {ScrollView} from 'react-native';
import ClipElementLocal from './ClipElementLocal';
import ClipElementRemote from './ClipElementRemote';
export default class ClipList extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
constructor(props: any) {
super(props);
}
createClipElementLocal(content: string): JSX.Element {
return <ClipElementLocal store={this.props.store} content={content} />;
}
createClipElementLocal(content: string): JSX.Element {
return <ClipElementLocal content={content} />;
}
createClipElementRemote(
content: string,
deviceName: string,
timestamp: number,
): JSX.Element {
return (
<ClipElementRemote
store={this.props.store}
content={content}
deviceName={deviceName}
timestamp={timestamp}
/>
);
}
createClipElementRemote(content: string, deviceName: string, timestamp: number): JSX.Element {
return <ClipElementRemote content={content} deviceName={deviceName} timestamp={timestamp} />;
render(): JSX.Element {
let clips;
if (this.props.type === 'local') {
clips = this.props.clips.map((entry: any) =>
this.createClipElementLocal(entry.content),
);
} else {
clips = this.props.clips.map((entry: any) =>
this.createClipElementRemote(
entry.content,
entry.deviceName,
entry.timestamp,
),
);
}
render(): JSX.Element {
let clips;
if (this.props.type === "local") clips = this.props.clips.map((entry: any) => this.createClipElementLocal(entry.content));
else clips = this.props.clips.map((entry: any) => this.createClipElementRemote(entry.content, entry.deviceName, entry.timestamp));
return <ScrollView>{clips}</ScrollView>;
}
}
return <ScrollView>{clips}</ScrollView>;
}
}

View File

@ -1,45 +0,0 @@
import axios from 'axios';
import React from 'react';
import { ScrollView, Text } from 'react-native';
import ClipList from './ClipList';
type Clip = {
content: string;
token: string;
deviceName: string;
id: number;
timestamp: number;
};
async function getLocalClips() {
return [{ content: "test" }, { content: "test2" }];
}
async function getRemoteClips() {
const { data, status } = await axios.get("http://notifysync.simailadjalim.fr/clipboard?token=FFmkeNAxguFM5My52PhhzlOB_1ZwDr0ureD2kzuewMlhmJ6Ia6YkhcdZd1Nw4SXdLu9Ji0gzVYCfGCcgB8v8zQ");
return Object.values(data['clipboard']);
}
export default class ClipView extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
clips: []
}
}
async componentDidMount() {
let clips;
if (this.props.type === "local") clips = await getLocalClips();
else clips = await getRemoteClips();
this.setState({clips: clips});
}
render(): JSX.Element {
return <ScrollView>
<Text style={{ fontWeight: 'bold', fontSize: 30, margin: 20 }}>{this.props.type[0].toUpperCase() + this.props.type.slice(1) + " Clipboard"}</Text>
<ClipList type={this.props.type} clips={this.state.clips} />
</ScrollView>;
}
}

View File

@ -0,0 +1,60 @@
import axios from 'axios';
import React from 'react';
import {ScrollView, Text, Button, Clipboard} from 'react-native';
import ClipList from './ClipList';
import AClipView from './AClipView';
import {addToLocal as addToLocalStorage} from '../redux/actions';
export default class ClipViewLocal extends AClipView {
constructor(props: any) {
super(props);
this.state = {
clips: [],
};
this.getClips();
this.props.store.subscribe(() => {
this.setState({
clips: this.props.store.getState().persistedUserReducer.clips,
});
});
}
getClips() {
const clips = this.props.store.getState().persistedUserReducer.clips;
this.setState({clips: clips});
}
async addToLocal() {
console.log('should add clip');
this.props.store.dispatch(addToLocalStorage(await Clipboard.getString()));
}
componentDidMount() {
this.getClips();
}
render(): JSX.Element {
let title = 'Local Clipboard';
let notTitle = 'Remote Clipboard';
return (
<ScrollView>
<Text style={{fontWeight: 'bold', fontSize: 30, margin: 20}}>
{title}
</Text>
<Button
title="Coller depuis le presse papier"
onPress={() => {
this.addToLocal();
}}
/>
<ClipList
store={this.props.store}
type={this.props.type}
clips={this.state.clips}
/>
{this.getSignOutBtn()}
</ScrollView>
);
}
}

View File

@ -0,0 +1,32 @@
import axios from 'axios';
import React from 'react';
import { ScrollView, Text, Button } from 'react-native';
import ClipList from './ClipList';
import AClipView from './AClipView';
export default class ClipViewRemote extends AClipView {
constructor(props: any) {
super(props);
}
async getClips() {
const { data, status } = await axios.get("http://notifysync.simailadjalim.fr/clipboard?token=" + this.props.store.getState().persistedUserReducer.token);
return Object.values(data['clipboard']);
}
async componentDidMount() {
let clips;
clips = await this.getClips();
this.setState({clips: clips});
}
render(): JSX.Element {
let title = "Remote Clipboard";
return <ScrollView>
<Text style={{ fontWeight: 'bold', fontSize: 30, margin: 20 }}>{title}</Text>
<ClipList type={this.props.type} clips={this.state.clips} />
<Button title="Refresh" onPress={() => this.componentDidMount()} />
{this.getSignOutBtn()}
</ScrollView>;
}
}

19
src/redux/actions.tsx Normal file
View File

@ -0,0 +1,19 @@
export function setUser(token: string) {
return {
type: 'auth/connect',
payload: {token},
};
}
export function clearUser() {
return {
type: 'auth/disconnect',
};
}
export function addToLocal(content: string) {
return {
type: 'local/add',
payload: {content},
};
}

11
src/redux/persistance.tsx Normal file
View File

@ -0,0 +1,11 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import {persistReducer} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {userReducer} from './reducers';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
};
export const persistedUserReducer = persistReducer(persistConfig, userReducer);

14
src/redux/reducers.tsx Normal file
View File

@ -0,0 +1,14 @@
const initialState = { token: "", clips: [] }
export function userReducer(state = initialState, action: any) {
switch (action.type) {
case "auth/connect":
return { ...state, token: action.payload.token };
case "auth/disconnect":
return { ...state, token: "" };
case "local/add":
return { ...state, clips: [...state.clips, { content: action.payload.content }] };
default:
return state;
}
}

12
src/redux/store.tsx Normal file
View File

@ -0,0 +1,12 @@
import {createStore, combineReducers} from 'redux';
import {userReducer} from './reducers';
import {persistedUserReducer} from './persistance';
import {persistStore} from 'redux-persist';
export const store = createStore(
combineReducers({
persistedUserReducer,
}),
);
export const persistor = persistStore(store);