🎨 style(App.tsx, SignIn.tsx, SignUp.tsx, ClipElementLocal.tsx, ClipElementRemote.tsx, ClipList.tsx, ClipView.tsx, ClipViewLocal.tsx, ClipViewRemote.tsx, Auth.tsx, Clips.tsx, Intro.tsx, Settings.tsx): Improve UI/UX by adding responsive design and theme support

🔧 refactor(App.tsx, SignIn.tsx, SignUp.tsx, ClipElementLocal.tsx, ClipElementRemote.tsx, ClipList.tsx, ClipView.tsx, ClipViewLocal.tsx, ClipViewRemote.tsx, Auth.tsx, Clips.tsx, Intro.tsx, Settings.tsx): Refactor code for better readability and maintainability
🔥 remove(ClipElementLocal.tsx, ClipElementRemote.tsx, ClipList.tsx): Remove unused imports and props
 feat(App.tsx, SignIn.tsx, SignUp.tsx, ClipElementLocal.tsx, ClipElementRemote.tsx, ClipList.tsx, ClipView.tsx, ClipViewLocal.tsx, ClipViewRemote.tsx, Auth.tsx, Clips.tsx, Intro.tsx, Settings.tsx, themes.ts, utils.ts): Add new features such as theme support, responsive design, and utility functions
This commit is contained in:
Djalim Simaila 2024-04-03 14:31:44 +02:00
parent b969b4f36b
commit ba5d2697c5
15 changed files with 364 additions and 138 deletions

21
App.tsx
View File

@ -1,7 +1,7 @@
import 'react-native-gesture-handler';
// ^ le bouge pas ca casse tout ^
import { useState } from "react";
import { NavigationContainer } from '@react-navigation/native';
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';
@ -11,25 +11,33 @@ import AuthPage from './src/pages/Auth';
import ClipPage from './src/pages/Clips';
import IntroPage from './src/pages/Intro';
import { StatusBar } from 'expo-status-bar';
import { Material3Dracula, ReactNavigationDracula } from './src/themes';
import { PixelRatio, useWindowDimensions } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
const Stack = createNativeStackNavigator();
function App(){
const [token,setToken] = useState("");
const [username,setUsername] = useState("");
const {height,width} = useWindowDimensions();
console.log(width);
console.log(PixelRatio.get())
store.subscribe(()=>{
console.log("state Changed .w.");
let newToken = store.getState().user.token;
setToken(newToken);
console.log(store.getState());
})
return (
<PaperProvider theme={MD3LightTheme}>
<PaperProvider theme={Material3Dracula}>
<ToastProvider>
<Provider store={store}>
<StatusBar style="light"/>
<NavigationContainer>
<SafeAreaProvider>
<NavigationContainer theme={ReactNavigationDracula}>
<Stack.Navigator>
{
token !== "" ? (
@ -45,6 +53,7 @@ function App(){
}
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
</Provider>
</ToastProvider>
</PaperProvider>

View File

@ -1,10 +1,12 @@
import axios from 'axios';
import React from 'react';
import { View, Text } from 'react-native';
import { View, Text, Platform, useWindowDimensions } from 'react-native';
import { useToast } from "react-native-toast-notifications";
import { Button, TextInput } from 'react-native-paper';
import { login } from '../../redux/reducers';
import { useDispatch } from 'react-redux';
import { ReactNavigationDracula as t } from '../../themes';
import { ps } from '../../utils';
export default function SignIn(){
const [username, setUsername] = React.useState("");
@ -12,7 +14,8 @@ export default function SignIn(){
const [token,setToken] = React.useState("");
const toast = useToast();
const dispatch = useDispatch();
const {height, width} = useWindowDimensions();
async function signInFunction(dispatch) {
axios.post("https://notifysync.simailadjalim.fr/user",{
@ -32,16 +35,22 @@ export default function SignIn(){
});
}
return <View style={{
return <View style={[{
borderRadius:12,
height:"100%"
},{
flex:1,
margin:10,
margin:ps(8),
padding:ps(8),
paddingTop:ps(8),
gap:10,
flexDirection:'column',
justifyContent:"center",
alignItems:"center"
}}>
alignItems:"center",
backgroundColor: width > 600 ? t.colors.card : t.colors.background,
}]}>
<Text style={{ fontWeight: 'bold', fontSize: 30, color:"black" }}>
<Text style={{ fontWeight: 'bold', fontSize: 30, color:t.colors.text }}>
Connexion
</Text>
<TextInput
@ -50,7 +59,7 @@ export default function SignIn(){
value={username}
onChangeText={setUsername}
style={{
width:"80%"
width: 300
}}
/>
<TextInput
@ -59,7 +68,7 @@ export default function SignIn(){
value={password}
onChangeText={setPassword}
style={{
width:"80%"
width: 300
}}
/>
<Button

View File

@ -1,10 +1,12 @@
import React from 'react';
import {View, Text } from 'react-native';
import {View, Text, Platform, useWindowDimensions } from 'react-native';
import axios from 'axios';
import { useToast } from "react-native-toast-notifications";
import { Button, TextInput } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { login } from '../../redux/reducers';
import { ReactNavigationDracula as t } from '../../themes';
import { ps } from '../../utils';
export default function SignUp(){
const [username, setUsername] = React.useState("");
@ -12,9 +14,10 @@ export default function SignUp(){
const [confirm, setConfirm] = React.useState("");
const toast = useToast();
const dispatch = useDispatch();
const {height, width} = useWindowDimensions();
function signUpFunction(dispatch) {
if (confirm != password){
toast.show("The password and its confirmation are not the same",{
type:"warning"
@ -50,14 +53,21 @@ export default function SignUp(){
}
return (
<View style={{flex:1,
gap:10,
margin:10,
justifyContent:"center",
alignItems:"center",
flexDirection:'column'
}}>
<Text style={{fontWeight: 'bold', fontSize: 30 ,color:"black"}}>
<View style={[{
borderRadius:12,
height:"100%"
},{
flex:1,
margin:ps(8),
padding:ps(8),
paddingTop:ps(8),
gap:10,
flexDirection:'column',
justifyContent:"center",
alignItems:"center",
backgroundColor: width > 600 ? t.colors.card : t.colors.background,
}]}>
<Text style={{fontWeight: 'bold', fontSize: 30 ,color:t.colors.text}}>
Créer un compte
</Text>
<TextInput
@ -66,7 +76,7 @@ export default function SignUp(){
value={username}
onChangeText={setUsername}
style={{
width:"80%"
width:300
}}
/>
<TextInput
@ -75,7 +85,7 @@ export default function SignUp(){
value={password}
onChangeText={setPassword}
style={{
width:"80%"
width:300
}}
/>
<TextInput
@ -84,7 +94,7 @@ export default function SignUp(){
value={confirm}
onChangeText={setConfirm}
style={{
width:"80%"
width:300
}}
/>
<Button mode="contained" onPress={()=>{dispatch(signUpFunction)}}>

View File

@ -1,7 +1,6 @@
import axios from 'axios';
import {View} from 'react-native';
import IconVector from 'react-native-vector-icons/FontAwesome5';
import { Store } from 'redux';
import { useToast } from "react-native-toast-notifications";
import * as Clipboard from 'expo-clipboard';
import { Avatar, Card, Text } from 'react-native-paper';
@ -42,7 +41,7 @@ export default function ClipElementLocal({content}:{content: string}){
}}>
<Card style={{width:"100%"}} onPress={onCopy}>
<Card.Title title={content} right={() =>
<Button mode="elevated" onPress={() => sendToRemote()} >
<Button mode="contained" onPress={() => sendToRemote()} >
<IconVector name="paper-plane" size={20} color="black" />
</Button>
}

View File

@ -1,11 +1,10 @@
import { View } from 'react-native';
import IconVector from 'react-native-vector-icons/FontAwesome5';
import { Store } from 'redux';
import * as Clipboard from 'expo-clipboard';
import { useToast } from "react-native-toast-notifications";
import { Avatar, Card, Text } from 'react-native-paper';
import { Card, Text, Button } from 'react-native-paper';
export default function ClipElementRemote({content,timestamp,deviceName,store}:{content:string;timestamp:number,deviceName:string;store: Store}){
export default function ClipElementRemote({content,timestamp,deviceName}:{content:string;timestamp:number,deviceName:string}){
const toast = useToast();
function onCopy() {
@ -18,19 +17,17 @@ export default function ClipElementRemote({content,timestamp,deviceName,store}:{
const date= new Date(timestamp*1000);
return(
<View style={{flex:1,
margin:10,
padding:10,
width:"100%",
flexDirection:'row',
justifyContent:'center',
alignItems:'center'}}>
<Card>
<Card style={{width:"100%"}} onPress={onCopy} >
<Card.Title title={content}/>
<Card.Content>
<Text variant="titleLarge">{content.length >28 ?content.slice(0,24)+"...":content } </Text>
<Text variant="bodyMedium">{date.getHours() + ":" + date.getMinutes() + ", "+ date.toDateString()}</Text>
<Text variant="bodyMedium">{deviceName}</Text>
</Card.Content>
<Card.Actions>
<IconVector name="clipboard" size={40} color="black" onPress={() => onCopy()} />
</Card.Actions>
</Card>
</View>);
}

View File

@ -9,7 +9,7 @@ export default class ClipList extends React.Component<any, any> {
}
createClipElementLocal(content: string): JSX.Element {
return <ClipElementLocal store={this.props.store} content={content} />;
return <ClipElementLocal content={content} />;
}
createClipElementRemote(
@ -19,7 +19,6 @@ export default class ClipList extends React.Component<any, any> {
): JSX.Element {
return (
<ClipElementRemote
store={this.props.store}
content={content}
deviceName={deviceName}
timestamp={timestamp}

View File

@ -0,0 +1,51 @@
import React from "react";
import { useWindowDimensions, ScrollView } from "react-native";
import { Searchbar } from "react-native-paper"
import ClipViewLocal from "./ClipViewLocal";
import ClipViewRemote from "./ClipViewRemote";
import { ps } from "../../utils";
import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs";
export default function ClipView(){
const [searchQuery, setSearchQuery] = React.useState('');
const {height, width} = useWindowDimensions();
const Tab = createMaterialTopTabNavigator();
let layout = ""
if (width < 600) layout = "compact";
else if (width < 1200 ) layout = "medium";
else layout = "expanded";
return ( <>
<Searchbar
placeholder="Clipboards"
onChangeText={setSearchQuery}
value={searchQuery}
/>
{
layout == "compact"? (
<Tab.Navigator tabBarPosition="bottom">
<Tab.Screen name="Local" options={{title: 'local'}}>
{() => (
<ClipViewLocal/>
)}
</Tab.Screen>
<Tab.Screen name="Remote" options={{title: 'distant'}}>
{()=> (
<ClipViewRemote/>
)}
</Tab.Screen>
</Tab.Navigator>
):(
<ScrollView contentContainerStyle={{flexDirection:"row",justifyContent:"center",height:"100%",padding:ps(30)}}>
<ClipViewLocal/>
<ClipViewRemote/>
</ScrollView>
)
}
</>
)
}

View File

@ -1,18 +1,18 @@
import React from 'react';
import {ScrollView, Text} from 'react-native';
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 { useToast } from 'react-native-toast-notifications';
import * as Clipboard from 'expo-clipboard';
import { ps } from '../../utils';
export default function ClipViewLocal({}){
const [clips, setClips] = React.useState([]);
const dispatch = useDispatch();
const toast = useToast();
const {height,width} = useWindowDimensions();
let title = 'Local Clipboard';
@ -37,8 +37,13 @@ export default function ClipViewLocal({}){
}
return (
<ScrollView>
<Button
<View style={{
width:"100%",
height:"100%",
flex:1,
margin: width > 600 ? ps(10) : 0
}}>
<Button
mode="elevated"
onPress={() => {
dispatch(addToLocal);
@ -46,11 +51,13 @@ export default function ClipViewLocal({}){
>
Coller depuis le presse papier
</Button>
<ScrollView>
<ClipList
type={"local"}
clips={clips}
/>
</ScrollView>
</View>
);
}

View File

@ -1,24 +1,26 @@
import React from 'react';
import axios from 'axios';
import { ScrollView, Text } from 'react-native';
import { ScrollView,View, Text, useWindowDimensions } from 'react-native';
import ClipList from './ClipList';
import { store } from '../../redux/store';
import { useToast } from 'react-native-toast-notifications';
import { Button } from 'react-native-paper';
import { ps } from '../../utils';
export default function ClipViewRemote({type} :{type:string} ){
export default function ClipViewRemote(){
const [clips,setClips] = React.useState([]);
const toast = useToast();
const {height,width} = useWindowDimensions();
function getClips() {
axios.get("http://notifysync.simailadjalim.fr/clipboard?token="+store.getState().user.token)
.then((response,status) => {
console.log(response);
setClips(Object.values(response["data"]['clipboard']));
toast.show("fetched latest clips from remote");
})
.catch(response =>{
.then((response,status) => {
console.log(response);
setClips(Object.values(response["data"]['clipboard']));
toast.show("fetched latest clips from remote");
})
.catch(response =>{
console.log(response);
toast.show("failed to fetch latest clips");
});
@ -29,13 +31,18 @@ export default function ClipViewRemote({type} :{type:string} ){
}
let title = "Remote Clipboard";
return <>
<ScrollView>
<Button mode="elevated" onPress={getClips}>
Refresh
</Button>
<ClipList type={type} clips={clips} />
return <View style={{
width:"100%",
height:"100%",
flex:1,
margin: width > 600 ? ps(10) : 0
}}>
<Button mode="elevated" onPress={getClips}>
Refresh
</Button>
<ScrollView>
<ClipList type="remote" clips={clips} />
</ScrollView>
</>;
</View>;
}

View File

@ -1,10 +1,22 @@
import { Platform, View, useWindowDimensions } from "react-native";
import SignIn from "../components/auth/SignIn";
import SignUp from "../components/auth/SignUp";
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { ps } from "../utils";
export default function AuthPage(){
const {height, width} = useWindowDimensions();
const Tab = createMaterialTopTabNavigator();
if (width > 700){
return (
<View style={{flexDirection:"row",justifyContent:"center",alignItems:"center",height:"100%",padding:ps(30)}}>
<SignIn/>
<SignUp/>
</View>
);}
else{
return(
<Tab.Navigator>
<Tab.Screen
@ -19,4 +31,5 @@ export default function AuthPage(){
/>
</Tab.Navigator>
);
}
}

View File

@ -1,14 +1,15 @@
import React from "react";
import ClipViewLocal from "../components/clip/ClipViewLocal";
import ClipViewRemote from "../components/clip/ClipViewRemote";
import { Drawer as DrawerLayout } from 'react-native-drawer-layout';
import { Button, Drawer } from 'react-native-paper';
import { store } from "../redux/store";
import { Platform, View,StyleSheet } from "react-native";
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 { useDispatch } from 'react-redux';
import { disconnect } from "../redux/reducers";
import { ps } from "../utils";
import { ReactNavigationDracula as t } from "../themes";
import ClipView from "../components/clip/ClipView";
import Settings from "./Settings";
const styles = StyleSheet.create({
modal: {
@ -24,90 +25,137 @@ const styles = StyleSheet.create({
export default function ClipPage(){
const [open, setOpen] = React.useState(false);
const [visible, setVisible] = React.useState(false);
const Tab = createMaterialTopTabNavigator();
const [active, setActive] = React.useState('clips');
const dispatch = useDispatch()
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
const containerStyle = {backgroundColor: 'white', padding: 20};
function signout(){
setVisible(true);
//dispatch(disconnect());
const {height, width} = useWindowDimensions();
let layout = ""
if (width < 600) layout = "compact";
else if (width < 1200 ) layout = "medium";
else layout = "expanded";
let style = {};
if (layout == "medium"){
style = StyleSheet.create({
drawer: {
backgroundColor: t.colors.background,
width : 100
}
})
} else {
style = StyleSheet.create({
drawer : {
backgroundColor: t.colors.background
}
})
}
return (<>
let page;
if (active == "clips") page = <ClipView/>;
if (active == "notif") page = <ClipView/>;
if (active == "settings") page = <Settings/>;
return (<View style={{
width:"100%",
height:"100%",
paddingTop: Platform.OS == "android" ? 30 : 0 }}>
<Portal>
<Modal
visible={visible}
onDismiss={hideModal}
contentContainerStyle={containerStyle}
style={styles.modal}
<Modal
visible={visible}
onDismiss={hideModal}
contentContainerStyle={containerStyle}
style={styles.modal}
>
<Text>Do you really want to sign out?</Text>
<Button mode="contained" onPress={hideModal}>
no
</Button>
<Button mode="contained-tonal" onPress={()=> dispatch(disconnect())}>
yes
</Button>
</Modal>
</Portal>
<Text>Do you really want to sign out?</Text>
<Button mode="contained" onPress={hideModal}>
no
</Button>
<Button mode="contained-tonal" onPress={()=> dispatch(disconnect())}>
yes
</Button>
</Modal>
</Portal>
<DrawerLayout
swipeEdgeWidth={70}
drawerType={(Platform.OS == "android" || Platform.OS == "ios") ? "slide":"permanent"}
drawerType={layout == "compact" ? "slide":"permanent"}
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
drawerStyle={style.drawer}
renderDrawerContent={() => {
return <>
<Drawer.Section title="Some title">
<Drawer.Item
label="Clipboards"
active={active === 'clips'}
onPress={() => setActive('clips')}
/>
<Drawer.Item
label="Settings"
active={active === 'second'}
onPress={() => setActive('second')}
/>
</Drawer.Section>
<Drawer.Section>
<Drawer.Item
label="Sign out"
icon="door"
active={active === 'third'}
onPress={() => signout()}
/>
</Drawer.Section>
</>
if (layout != "medium"){
return <>
<Drawer.Section title=" ">
<Drawer.Item
icon="clipboard-arrow-down"
label="Clipboards"
active={active === 'clips'}
onPress={() => {
setActive('clips');
setOpen(false)
}}
/>
<Drawer.Item
icon="cog"
label="Settings"
active={active === 'settings'}
onPress={() => {
setActive('settings');
setOpen(false)
}}
/>
</Drawer.Section>
<Drawer.Section>
<Drawer.Item
icon="door"
active={active === 'third'}
onPress={() => setVisible(true)}
label="Sign out"
/>
</Drawer.Section>
</>
}else{
return <View style={{alignItems:"center", height:"100%"}}>
<Drawer.Section>
<Drawer.CollapsedItem
focusedIcon="clipboard-arrow-down"
unfocusedIcon="clipboard-arrow-down-outline"
label="Clipboards"
active={active === 'clips'}
onPress={() => {
setActive('clips');
setOpen(false)
}}
/>
<Drawer.CollapsedItem
focusedIcon="cog"
unfocusedIcon="cog-outline"
label="Settings"
active={active === 'settings'}
onPress={() => {
setActive('settings');
setOpen(false)
}}
/>
</Drawer.Section>
<Drawer.Section>
<Drawer.CollapsedItem
focusedIcon="door-open"
unfocusedIcon="door"
label="Sign out"
onPress={() => setVisible(true)}
/>
</Drawer.Section>
</View>
}
}}
>
<Appbar.Header>
<Appbar.BackAction onPress={() => {setOpen(true)}} />
<Appbar.Content title="Clipboards" />
</Appbar.Header>
<Tab.Navigator>
<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>
{page}
</DrawerLayout>
</>
</View>
);
}

View File

@ -1,11 +1,23 @@
import { View, Text } from "react-native";
import { Button } from 'react-native-paper';
import { ps } from "../utils";
import { ReactNavigationDracula as t } from "../themes";
export default function IntroPage({navigation}){
return(
<View style={{alignItems:"center",justifyContent:"center",flex:1,margin:10,flexDirection:'column'}}>
<Text style={{fontSize:20,color:"black"}}>
Holla Benvenido in l'application du turfu
<View style={{
alignItems:"center",
justifyContent:"center",
gap:ps(6),
flex:1,
margin:ps(16),
flexDirection:'column'}}>
<Text style={{
fontSize:20,
textAlign:"center",
color: t.colors.text
}}>
Salut, bienvenue dans ClipSync :) connecte toi pour commencer
</Text>
<Button mode="contained" onPress={() => navigation.navigate('SignIn')}>
Connecte moi le sang

View File

@ -0,0 +1,26 @@
import { ScrollView } from "react-native";
import { View, Text } from "react-native";
import { Button } from 'react-native-paper';
import { ps } from "../utils";
import { ReactNavigationDracula as t } from "../themes";
export default function Settings(){
return(
<View style={{
width: "100%",
height: "100%",
alignItems:"center",
justifyContent:"center",
gap:ps(6),
flex:1,
flexDirection:'column'}}>
<Text style={{
fontSize:20,
textAlign:"center",
color: t.colors.text
}}>
Pas encore pret mais on y travaille .w.
</Text>
</View>)
}

28
src/themes.ts Normal file
View File

@ -0,0 +1,28 @@
import { MD3DarkTheme, Surface } from 'react-native-paper';
export const Material3Dracula = {
...MD3DarkTheme,
// Specify custom property
// Specify custom property in nested object
colors: {
...MD3DarkTheme.colors,
background: '#44475a',
surface: '#44475a',
elevation: {
...MD3DarkTheme.colors.elevation,
level1: "#44475A"
}
},
};
export const ReactNavigationDracula = {
dark: true,
colors: {
primary: '#bd93f9',
background: '#282a36',
card: '#44475a',
text: '#F8F8F2',
border: '#44475a',
notification: 'rgb(255, 69, 58)',
},
};

11
src/utils.ts Normal file
View File

@ -0,0 +1,11 @@
import { PixelRatio,StyleSheet } from "react-native";
export const ps = (lp:number) => PixelRatio.getPixelSizeForLayoutSize(PixelRatio.roundToNearestPixel(lp));
export const style = StyleSheet.create({
default : {
margin: PixelRatio.getPixelSizeForLayoutSize(8),
marginTop : PixelRatio.getPixelSizeForLayoutSize(8),
marginLeft : PixelRatio.getPixelSizeForLayoutSize(8)
}
})