🔧 refactor(gitignore): add android/ to .gitignore to prevent tracking of Android build files
🔧 refactor(App.tsx): import AppRegistry, Platform from react-native for better platform detection 🔧 refactor(App.tsx): import persistor from redux store to persist redux state 🔧 refactor(App.tsx): wrap app in PersistGate to persist and rehydrate a redux store 🔧 refactor(App.tsx): change initial token state to use token from redux store 🔧 refactor(app.json): add package name to app.json for unique app identification 🔧 refactor(package.json): change android and ios scripts to use expo run:android and expo run:ios 🔧 refactor(package.json): add react-native-android-notification-listener and react-native-pager-view dependencies 🔧 refactor(package.json): add redux-persist-expo-filesystem for redux state persistence 🔧 refactor(ClipElementLocal.tsx): remove unnecessary whitespace 🔧 refactor(ClipList.tsx): replace ScrollView with FlatList for better performance 🔧 refactor(ClipList.tsx): use nanoid for unique key generation in FlatList 🔧 refactor(ClipView.tsx): add Button for compact layout 🔧 refactor(ClipView.tsx): adjust Searchbar style for better layout 🔧 refactor(ClipViewLocal.tsx): use localClipAddToList action for adding to local clip list 🔧 refactor(ClipViewLocal.tsx): set initial clips state to use localClip from redux store 🔧 refactor(ClipViewRemote.tsx): use remoteClipAddToList action for adding to remote clip list 🔧 refactor(ClipViewRemote.tsx): set initial clips state to use remoteClip from redux store 🔧 refactor(Clips.tsx): add signout function to clear all redux states on signout 🔧 refactor(Clips.tsx): add Notifications to Drawer 🔧 refactor(Clips.tsx): replace ClipView with NotifView for notif active state 🔧 refactor(reducers.tsx): rename localAddToList to localClipAddToList for better semantics 🔧 refactor(reducers.tsx): rename remoteAddToList to remoteClipAddToList for better semantics 🔧 refactor(reducers.tsx): add localNotifsSlice and remoteNotifsSlice for handling local and remote notifications
This commit is contained in:
parent
504b83386d
commit
94df141d06
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,6 +24,7 @@ ios/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
android/
|
||||
build/
|
||||
.idea
|
||||
.gradle
|
||||
|
15
App.tsx
15
App.tsx
@ -1,11 +1,14 @@
|
||||
import 'react-native-gesture-handler';
|
||||
// ^ le bouge pas ca casse tout ^
|
||||
import { AppRegistry, Platform } from 'react-native'
|
||||
|
||||
|
||||
import { useState } from "react";
|
||||
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './src/redux/store';
|
||||
import { MD3DarkTheme, MD3LightTheme, PaperProvider, Portal } from 'react-native-paper';
|
||||
import { store,persistor } from './src/redux/store';
|
||||
import { PaperProvider } from 'react-native-paper';
|
||||
import { ToastProvider } from 'react-native-toast-notifications'
|
||||
import AuthPage from './src/pages/Auth';
|
||||
import ClipPage from './src/pages/Clips';
|
||||
@ -15,11 +18,11 @@ import { Material3Dracula, ReactNavigationDracula } from './src/themes';
|
||||
import { PixelRatio, useWindowDimensions } from 'react-native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
||||
|
||||
function App(){
|
||||
const [token,setToken] = useState("");
|
||||
const [token,setToken] = useState(store.getState().user.token);
|
||||
const [username,setUsername] = useState("");
|
||||
|
||||
const {height,width} = useWindowDimensions();
|
||||
@ -30,11 +33,12 @@ function App(){
|
||||
let newToken = store.getState().user.token;
|
||||
setToken(newToken);
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<PaperProvider theme={Material3Dracula}>
|
||||
<ToastProvider>
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<StatusBar style="light"/>
|
||||
<SafeAreaProvider>
|
||||
<NavigationContainer theme={ReactNavigationDracula}>
|
||||
@ -54,6 +58,7 @@ function App(){
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
</SafeAreaProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
</ToastProvider>
|
||||
</PaperProvider>
|
||||
|
3
app.json
3
app.json
@ -21,7 +21,8 @@
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"package": "fr.simailadjalim.ClipSync"
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
|
@ -4,8 +4,8 @@
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -22,9 +22,11 @@
|
||||
"expo-status-bar": "~1.11.1",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.73.6",
|
||||
"react-native-android-notification-listener": "^5.0.1",
|
||||
"react-native-async-storage": "^0.0.1",
|
||||
"react-native-drawer-layout": "^3.3.0",
|
||||
"react-native-gesture-handler": "~2.14.0",
|
||||
"react-native-pager-view": "^6.3.0",
|
||||
"react-native-paper": "^5.12.3",
|
||||
"react-native-reanimated": "~3.6.2",
|
||||
"react-native-safe-area-context": "^4.9.0",
|
||||
@ -38,6 +40,7 @@
|
||||
"react-redux": "^9.1.0",
|
||||
"redux": "^5.0.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-persist-expo-filesystem": "^2.0.1",
|
||||
"redux-thunk": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -8,7 +8,7 @@ import { store } from '../../redux/store';
|
||||
import { Button } from 'react-native-paper';
|
||||
|
||||
export default function ClipElementLocal({content}:{content: string}){
|
||||
|
||||
|
||||
const toast = useToast();
|
||||
function onCopy() {
|
||||
Clipboard.setStringAsync(content)
|
||||
|
@ -1,46 +1,31 @@
|
||||
import React from 'react';
|
||||
import {ScrollView} from 'react-native';
|
||||
import {FlatList, ScrollView} from 'react-native';
|
||||
import ClipElementLocal from './ClipElementLocal';
|
||||
import ClipElementRemote from './ClipElementRemote';
|
||||
import { nanoid } from '@reduxjs/toolkit';
|
||||
|
||||
export default class ClipList extends React.Component<any, any> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
createClipElementLocal(content: string): JSX.Element {
|
||||
return <ClipElementLocal content={content} />;
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
return <FlatList
|
||||
data={this.props.clips}
|
||||
renderItem={({item}) => <ClipElementLocal content={item.content} />}
|
||||
keyExtractor={item => nanoid()}
|
||||
inverted={true}
|
||||
/>
|
||||
} else {
|
||||
clips = this.props.clips.map((entry: any) =>
|
||||
this.createClipElementRemote(
|
||||
entry.content,
|
||||
entry.device_name,
|
||||
entry.timestamp,
|
||||
),
|
||||
);
|
||||
return <FlatList
|
||||
data={this.props.clips}
|
||||
renderItem={({item}) => <ClipElementRemote content={item.content} deviceName={item.device_name} timestamp={item.timestamp}/>}
|
||||
keyExtractor={item => nanoid()}
|
||||
inverted={true}
|
||||
/>
|
||||
;
|
||||
}
|
||||
return <ScrollView>{clips}</ScrollView>;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { useWindowDimensions, ScrollView } from "react-native";
|
||||
import { Searchbar } from "react-native-paper"
|
||||
import { useWindowDimensions, ScrollView, View } from "react-native";
|
||||
import { Searchbar, Button } from "react-native-paper"
|
||||
import ClipViewLocal from "./ClipViewLocal";
|
||||
import ClipViewRemote from "./ClipViewRemote";
|
||||
import { ps } from "../../utils";
|
||||
@ -18,12 +18,19 @@ export default function ClipView(){
|
||||
else if (width < 1200 ) layout = "medium";
|
||||
else layout = "expanded";
|
||||
return ( <>
|
||||
|
||||
<Searchbar
|
||||
<View style={{flexDirection:"row",justifyContent:"center",alignItems:"center", gap:5, margin:ps(4)}}>
|
||||
{layout == "compact" ?
|
||||
<Button children={<></>} icon="dots-vertical" mode="contained" style={{width:"auto"}} onPress={() => console.log('Pressed')}/>
|
||||
: <></>}
|
||||
<Searchbar
|
||||
placeholder="Clipboards"
|
||||
onChangeText={setSearchQuery}
|
||||
value={searchQuery}
|
||||
style={{
|
||||
flex:1
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{
|
||||
layout == "compact"? (
|
||||
<Tab.Navigator tabBarPosition="bottom">
|
||||
|
@ -3,30 +3,24 @@ import {ScrollView, View, Text, useWindowDimensions} from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import ClipList from './ClipList';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { localAddToList } from '../../redux/reducers';
|
||||
import { localClipAddToList } from '../../redux/reducers';
|
||||
import { useToast } from 'react-native-toast-notifications';
|
||||
import * as Clipboard from 'expo-clipboard';
|
||||
import { ps } from '../../utils';
|
||||
import { store } from '../../redux/store';
|
||||
|
||||
export default function ClipViewLocal({}){
|
||||
const [clips, setClips] = React.useState([]);
|
||||
const [clips, setClips] = React.useState([...store.getState().localClip.localClip]);
|
||||
const dispatch = useDispatch();
|
||||
const toast = useToast();
|
||||
const {height,width} = useWindowDimensions();
|
||||
|
||||
|
||||
let title = 'Local Clipboard';
|
||||
|
||||
function getSignOutBtn() {
|
||||
return <Button mode="elevated" onPress={() => {}}>Sign out</Button>
|
||||
}
|
||||
|
||||
async function addToLocal(dispatch) {
|
||||
Clipboard.getStringAsync()
|
||||
.then(value =>{
|
||||
let newClip = {"content":value};
|
||||
console.log(value);
|
||||
dispatch(localAddToList(newClip));
|
||||
dispatch(localClipAddToList(newClip));
|
||||
setClips([...clips,newClip]);
|
||||
toast.show('copied "'+value+'" into history');
|
||||
})
|
||||
|
@ -6,18 +6,22 @@ import { store } from '../../redux/store';
|
||||
import { useToast } from 'react-native-toast-notifications';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { ps } from '../../utils';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { remoteClipAddToList } from '../../redux/reducers';
|
||||
|
||||
export default function ClipViewRemote(){
|
||||
const [clips,setClips] = React.useState([]);
|
||||
const [clips,setClips] = React.useState(store.getState().remoteClip.remoteClip);
|
||||
const toast = useToast();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {height,width} = useWindowDimensions();
|
||||
|
||||
function getClips() {
|
||||
async function getClips(dispatch) {
|
||||
axios.get("http://notifysync.simailadjalim.fr/clipboard?token="+store.getState().user.token)
|
||||
.then((response,status) => {
|
||||
console.log(response);
|
||||
setClips(Object.values(response["data"]['clipboard']));
|
||||
let remoteclips = Object.values(response["data"]['clipboard']);
|
||||
setClips(remoteclips);
|
||||
dispatch(remoteClipAddToList(remoteclips))
|
||||
toast.show("fetched latest clips from remote");
|
||||
})
|
||||
.catch(response =>{
|
||||
@ -37,7 +41,7 @@ export default function ClipViewRemote(){
|
||||
flex:1,
|
||||
margin: width > 600 ? ps(10) : 0
|
||||
}}>
|
||||
<Button mode="elevated" onPress={getClips}>
|
||||
<Button mode="elevated" onPress={() => dispatch(getClips)}>
|
||||
Refresh
|
||||
</Button>
|
||||
<ScrollView>
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React from "react";
|
||||
import { Drawer as DrawerLayout } from 'react-native-drawer-layout';
|
||||
import { Button, Drawer, Searchbar } from 'react-native-paper';
|
||||
import { Platform, View,StyleSheet, useWindowDimensions, ScrollView } from "react-native";
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||
import { Appbar, Modal, Text, Portal } from 'react-native-paper';
|
||||
import { Button, Drawer} from 'react-native-paper';
|
||||
import { Platform, View,StyleSheet, useWindowDimensions } from "react-native";
|
||||
import { Modal, Text, Portal } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { disconnect } from "../redux/reducers";
|
||||
import { ps } from "../utils";
|
||||
import { disconnect, localClipClear, localNotifClear, remoteClipClear, remoteNotifClear } from "../redux/reducers";
|
||||
import { ReactNavigationDracula as t } from "../themes";
|
||||
import ClipView from "../components/clip/ClipView";
|
||||
import Settings from "./Settings";
|
||||
import NotifView from "../components/notif/NotifView";
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modal: {
|
||||
@ -31,6 +30,15 @@ export default function ClipPage(){
|
||||
const containerStyle = {backgroundColor: 'white', padding: 20};
|
||||
const {height, width} = useWindowDimensions();
|
||||
|
||||
|
||||
function signout(){
|
||||
dispatch(localClipClear())
|
||||
dispatch(remoteClipClear())
|
||||
dispatch(localNotifClear())
|
||||
dispatch(remoteNotifClear())
|
||||
dispatch(disconnect())
|
||||
}
|
||||
|
||||
let layout = ""
|
||||
|
||||
if (width < 600) layout = "compact";
|
||||
@ -56,7 +64,7 @@ export default function ClipPage(){
|
||||
|
||||
let page;
|
||||
if (active == "clips") page = <ClipView/>;
|
||||
if (active == "notif") page = <ClipView/>;
|
||||
if (active == "notif") page = <NotifView/>;
|
||||
if (active == "settings") page = <Settings/>;
|
||||
|
||||
return (<View style={{
|
||||
@ -74,7 +82,7 @@ export default function ClipPage(){
|
||||
<Button mode="contained" onPress={hideModal}>
|
||||
no
|
||||
</Button>
|
||||
<Button mode="contained-tonal" onPress={()=> dispatch(disconnect())}>
|
||||
<Button mode="contained-tonal" onPress={()=> signout()}>
|
||||
yes
|
||||
</Button>
|
||||
</Modal>
|
||||
@ -99,6 +107,15 @@ export default function ClipPage(){
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
<Drawer.Item
|
||||
icon="clipboard-arrow-down"
|
||||
label="Notifications"
|
||||
active={active === 'notif'}
|
||||
onPress={() => {
|
||||
setActive('notif');
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
<Drawer.Item
|
||||
icon="cog"
|
||||
label="Settings"
|
||||
@ -120,7 +137,7 @@ export default function ClipPage(){
|
||||
</>
|
||||
}else{
|
||||
return <View style={{alignItems:"center", height:"100%"}}>
|
||||
<Drawer.Section>
|
||||
<Drawer.Section title=" ">
|
||||
<Drawer.CollapsedItem
|
||||
focusedIcon="clipboard-arrow-down"
|
||||
unfocusedIcon="clipboard-arrow-down-outline"
|
||||
@ -131,6 +148,16 @@ export default function ClipPage(){
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
<Drawer.CollapsedItem
|
||||
focusedIcon="clipboard-arrow-down"
|
||||
unfocusedIcon="clipboard-arrow-down-outline"
|
||||
label="Notifications"
|
||||
active={active === 'notif'}
|
||||
onPress={() => {
|
||||
setActive('notif');
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
<Drawer.CollapsedItem
|
||||
focusedIcon="cog"
|
||||
unfocusedIcon="cog-outline"
|
||||
|
@ -34,16 +34,19 @@ export const localClipsSlice = createSlice({
|
||||
localClip:[],
|
||||
},
|
||||
reducers: {
|
||||
localAddToList: (state,action) => {
|
||||
localClipAddToList: (state,action) => {
|
||||
state.localClip = [...state.localClip,action.payload]
|
||||
},
|
||||
localRemoveFromList: (state,action) => {
|
||||
localClipRemoveFromList: (state,action) => {
|
||||
state.localClip = state.localClip.filter(e => e !== action.payload)
|
||||
},
|
||||
localClipClear : (state,action) =>{
|
||||
state.localClip = []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {localAddToList, localRemoveFromList} = localClipsSlice.actions;
|
||||
export const {localClipAddToList, localClipRemoveFromList, localClipClear} = localClipsSlice.actions;
|
||||
|
||||
// Remote clip structure
|
||||
// [
|
||||
@ -56,17 +59,59 @@ export const remoteClipsSlice = createSlice({
|
||||
remoteClip:[],
|
||||
},
|
||||
reducers: {
|
||||
remoteAddToList: (state,) => {
|
||||
remoteClipAddToList: (state,action) => {
|
||||
console.log(action.payload);
|
||||
state.remoteClip = action.payload
|
||||
},
|
||||
remoteClipRemoveFromList: (state,action) => {
|
||||
state.remoteClip = state.remoteClip.filter(e => e !== action.payload);
|
||||
},
|
||||
remoteClipClear : (state,action) =>{
|
||||
state.remoteClip = []
|
||||
},
|
||||
remoteRemoveFromList: state => {
|
||||
state.remoteClip = []
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
export const {remoteClipAddToList, remoteClipRemoveFromList, remoteClipClear} = remoteClipsSlice.actions;
|
||||
|
||||
|
||||
export const localNotifsSlice = createSlice({
|
||||
name: 'localNotifs',
|
||||
initialState: {
|
||||
localNotif:[],
|
||||
},
|
||||
reducers: {
|
||||
localNotifAddToList: (state,action) => {
|
||||
state.localNotif = [...state.localNotif,action.payload]
|
||||
},
|
||||
localNotifRemoveFromList: (state,action) => {
|
||||
state.localNotif = state.localNotif.filter(e => e !== action.payload)
|
||||
},
|
||||
localNotifClear : (state,action) =>{
|
||||
state.localNotif = []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {remoteAddToList, remoteRemoveFromList} = remoteClipsSlice.actions;
|
||||
|
||||
|
||||
export const {localNotifAddToList, localNotifRemoveFromList, localNotifClear} = localNotifsSlice.actions;
|
||||
|
||||
export const remoteNotifsSlice = createSlice({
|
||||
name: 'remoteNotifs',
|
||||
initialState: {
|
||||
remoteNotif:[],
|
||||
},
|
||||
reducers: {
|
||||
remoteNotifAddToList: (state,action) => {
|
||||
state.remoteNotif = [...action.payload]
|
||||
},
|
||||
remoteNotifRemoveFromList: (state,action) => {
|
||||
state.remoteNotif = state.remoteNotif.filter(e => e !== action.payload);
|
||||
},
|
||||
remoteNotifClear : (state,action) =>{
|
||||
state.remoteNotif = []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const {remoteNotifAddToList, remoteNotifRemoveFromList, remoteNotifClear} = remoteNotifsSlice.actions;
|
||||
|
@ -1,17 +1,29 @@
|
||||
import { applyMiddleware,configureStore } from '@reduxjs/toolkit'
|
||||
import { combineReducers } from '@reduxjs/toolkit';
|
||||
import { userSlice, localClipsSlice ,remoteClipsSlice } from './reducers';
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import { localNotifsSlice,remoteNotifsSlice } from './reducers';
|
||||
import { persistStore, persistReducer } from 'redux-persist'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
storage: AsyncStorage,
|
||||
};
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
user:userSlice.reducer,
|
||||
local:localClipsSlice.reducer,
|
||||
remote:remoteClipsSlice.reducer
|
||||
localClip:localClipsSlice.reducer,
|
||||
remoteClip:remoteClipsSlice.reducer,
|
||||
localNotif:localNotifsSlice.reducer,
|
||||
remoteNotif:remoteNotifsSlice.reducer
|
||||
});
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer)
|
||||
|
||||
const store = configureStore({
|
||||
reducer: rootReducer
|
||||
reducer: persistedReducer
|
||||
})
|
||||
|
||||
export {store};
|
||||
const persistor = persistStore(store)
|
||||
|
||||
export {store, persistor};
|
||||
|
Loading…
Reference in New Issue
Block a user