minor improvement

This commit is contained in:
Djalim Simaila 2023-04-10 01:30:59 +02:00
parent d1d749290a
commit b713911e6b
11 changed files with 316 additions and 228 deletions

2
.gitignore vendored
View File

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

View File

@ -10,15 +10,23 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "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", "axios": "^1.3.4",
"expo": "^48.0.9", "expo": "^48.0.9",
"expo-cli": "^6.3.2", "expo-cli": "^6.3.2",
"@react-navigation/native": "^6.1.6",
"@react-navigation/native-stack": "^6.9.12",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.71.4", "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-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": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
@ -28,6 +36,7 @@
"@tsconfig/react-native": "^2.0.2", "@tsconfig/react-native": "^2.0.2",
"@types/jest": "^29.2.1", "@types/jest": "^29.2.1",
"@types/react": "^18.0.24", "@types/react": "^18.0.24",
"@types/react-native": "^0.71.5",
"@types/react-native-vector-icons": "^6.4.13", "@types/react-native-vector-icons": "^6.4.13",
"@types/react-test-renderer": "^18.0.0", "@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1", "babel-jest": "^29.2.1",

View File

@ -5,76 +5,96 @@
* @format * @format
*/ */
import * as React from 'react'; import * as React from 'react';
import { NavigationContainer, StackActions } from '@react-navigation/native'; import {NavigationContainer, StackActions} from '@react-navigation/native';
import type { PropsWithChildren } from 'react'; import type {PropsWithChildren} from 'react';
import { import {
SafeAreaView, SafeAreaView,
ScrollView, ScrollView,
StatusBar, StatusBar,
StyleSheet, StyleSheet,
Text, Text,
useColorScheme, useColorScheme,
View, View,
} from 'react-native'; } from 'react-native';
import ClipViewLocal from './clip/ClipViewLocal'; import ClipViewLocal from './clip/ClipViewLocal';
import ClipViewRemote from './clip/ClipViewRemote'; import ClipViewRemote from './clip/ClipViewRemote';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs'; import {createMaterialBottomTabNavigator} from '@react-navigation/material-bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import {createNativeStackNavigator} from '@react-navigation/native-stack';
import SignIn from './auth/SignIn'; import SignIn from './auth/SignIn';
import SignUp from './auth/SignUp'; import SignUp from './auth/SignUp';
import { Provider } from 'react-redux'; import {Provider} from 'react-redux';
import { store, persistor } from './redux/store'; import {store, persistor} from './redux/store';
import { PersistGate } from 'redux-persist/integration/react'; import {PersistGate} from 'redux-persist/integration/react';
const Stack = createNativeStackNavigator(); const Stack = createNativeStackNavigator();
const Tab = createMaterialBottomTabNavigator(); const Tab = createMaterialBottomTabNavigator();
class App extends React.Component<any, any> { class App extends React.Component<any, any> {
constructor(props: any) {
constructor(props: any) { super(props);
super(props); this.state = {
this.state = { token: '',
token: "", username: '',
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}></PersistGate>
<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>
}; };
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>
);
}
} }
export default App; export default App;

View File

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

View File

@ -1,34 +1,46 @@
import { View, Text, Button } from 'react-native'; import {View, Text, Button} from 'react-native';
import IconVector from 'react-native-vector-icons/FontAwesome5'; import IconVector from 'react-native-vector-icons/FontAwesome5';
import AClipElement from './AClipElement'; import AClipElement from './AClipElement';
import Toast from 'react-native-simple-toast';
export default class ClipElementLocal extends AClipElement { export default class ClipElementLocal extends AClipElement {
constructor(props: any) {
super(props);
}
constructor(props: any) { async sendToRemote() {
super(props); 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);
}
async sendToRemote() { render(): JSX.Element {
const data = new FormData(); return (
data.append("token", this.props.store.getState().persistedUserReducer.token); <View
data.append("content", this.props.content); style={{
data.append("deviceName", "TODOChangeThisMobileDevice"); flex: 1,
const sendToRemoteResponse = await fetch( margin: 10,
'https://notifysync.simailadjalim.fr/clipboard', flexDirection: 'row',
{ justifyContent: 'space-between',
method: 'PUT', alignItems: 'center',
body: data }}>
} <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()} />
const response = await sendToRemoteResponse.json(); <IconVector name="clipboard" size={40} onPress={() => this.onCopy()} />
console.log(response); </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}</Text>
<Button title="Send to remote" 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 { 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'}}> return <View style={{flex:1,margin:10,flexDirection:'row',justifyContent:'space-between',alignItems:'center'}}>
<View style={{flex:1,margin:10,flexDirection:'column'}}> <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.deviceName}</Text>
<Text style={{fontSize:10,}}>{this.props.timestamp}</Text> <Text style={{fontSize:10,}}>{date.getHours() + ":" + date.getMinutes() + ", "+ date.toDateString()}</Text>
</View> </View>
<IconVector name="clipboard" size={40} onPress={() => this.onCopy()} /> <IconVector name="clipboard" size={40} onPress={() => this.onCopy()} />
</View>; </View>;

View File

@ -1,26 +1,47 @@
import React from 'react'; import React from 'react';
import { ScrollView } from 'react-native'; import {ScrollView} from 'react-native';
import ClipElementLocal from './ClipElementLocal'; import ClipElementLocal from './ClipElementLocal';
import ClipElementRemote from './ClipElementRemote'; import ClipElementRemote from './ClipElementRemote';
export default class ClipList extends React.Component<any, any> { export default class ClipList extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
constructor(props: any) { createClipElementLocal(content: string): JSX.Element {
super(props); return <ClipElementLocal store={this.props.store} content={content} />;
} }
createClipElementLocal(content: string): JSX.Element { createClipElementRemote(
return <ClipElementLocal store={this.props.store} content={content} />; 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 { render(): JSX.Element {
return <ClipElementRemote store={this.props.store} content={content} deviceName={deviceName} timestamp={timestamp} />; 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>;
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>;
}
}

View File

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

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

View File

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

View File

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