🔧 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:
Djalim Simaila 2024-04-04 13:02:12 +02:00
parent 504b83386d
commit 94df141d06
13 changed files with 167 additions and 83 deletions

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ ios/.xcode.env.local
# Android/IntelliJ
#
android/
build/
.idea
.gradle

15
App.tsx
View File

@ -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>

View File

@ -21,7 +21,8 @@
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"package": "fr.simailadjalim.ClipSync"
},
"web": {
"favicon": "./assets/favicon.png"

BIN
bun.lockb

Binary file not shown.

View File

@ -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": {

View File

@ -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)

View File

@ -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>;
}
}

View File

@ -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">

View File

@ -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');
})

View File

@ -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>

View File

@ -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"

View File

@ -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;

View File

@ -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};