group screen, server update && note img
This commit is contained in:
parent
cc19df62c4
commit
641b7fe7db
6
frontend/.gitignore
vendored
Normal file
6
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
|
||||
# The following patterns were generated by expo-cli
|
||||
|
||||
expo-env.d.ts
|
||||
# @end expo-cli
|
@ -17,12 +17,21 @@ export default function TabLayout() {
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: 'Заметки',
|
||||
title: 'Заметка',
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name={focused ? 'home' : 'home-outline'} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="groups"
|
||||
options={{
|
||||
title: 'Группы',
|
||||
tabBarIcon: ({ color, focused }) => (
|
||||
<TabBarIcon name={focused ? 'grid' : 'grid-outline'} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="explore"
|
||||
options={{
|
||||
|
@ -15,75 +15,7 @@ export default function TabTwoScreen() {
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Settings</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedText>This code can be help you.</ThemedText>
|
||||
<Collapsible title="File-based routing">
|
||||
<ThemedText>
|
||||
This app has two screens:{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
||||
</ThemedText>
|
||||
<ThemedText>
|
||||
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
||||
sets up the tab navigator.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Android, iOS, and web support">
|
||||
<ThemedText>
|
||||
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
||||
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
||||
</ThemedText>
|
||||
</Collapsible>
|
||||
<Collapsible title="Images">
|
||||
<ThemedText>
|
||||
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
||||
different screen densities
|
||||
</ThemedText>
|
||||
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
|
||||
<ExternalLink href="https://reactnative.dev/docs/images">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Custom fonts">
|
||||
<ThemedText>
|
||||
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
|
||||
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
|
||||
custom fonts such as this one.
|
||||
</ThemedText>
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Light and dark mode components">
|
||||
<ThemedText>
|
||||
This template has light and dark mode support. The{' '}
|
||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
||||
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Animations">
|
||||
<ThemedText>
|
||||
This template includes an example of an animated component. The{' '}
|
||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
||||
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText> library
|
||||
to create a waving hand animation.
|
||||
</ThemedText>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<ThemedText>
|
||||
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
||||
component provides a parallax effect for the header image.
|
||||
</ThemedText>
|
||||
),
|
||||
})}
|
||||
</Collapsible>
|
||||
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
|
@ -1,107 +0,0 @@
|
||||
import { Image, StyleSheet, Platform, Button, ActivityIndicator } from 'react-native';
|
||||
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface Message {
|
||||
title: string;
|
||||
type: string;
|
||||
additionally: {
|
||||
payload: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Data {
|
||||
users: string[];
|
||||
messages: Message[];
|
||||
}
|
||||
|
||||
export default function GroupScreen() {
|
||||
const [groupData, setGroupData] = useState<Data | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("http://127.0.0.1:8099/grouphash:1/reloadDataFromServer")
|
||||
.then(v => v.json())
|
||||
.then((data: Data) => {
|
||||
setGroupData(data);
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
||||
headerImage={
|
||||
<Image
|
||||
source={require('@/assets/images/partial-react-logo.png')}
|
||||
style={styles.reactLogo}
|
||||
/>
|
||||
}>
|
||||
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Пользователи</ThemedText>
|
||||
{groupData?.users.map((user, index) => (
|
||||
<ThemedText key={index}>{user}</ThemedText>
|
||||
))}
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView >
|
||||
{ groupData === null ?
|
||||
<ActivityIndicator size="large" color="#00ff00" /> :
|
||||
groupData?.messages.map((message, index) => (
|
||||
<ThemedView key={index} style={styles.Message}>
|
||||
<ThemedText>{message.title}</ThemedText>
|
||||
{ message.type === "text" ?
|
||||
<ThemedText>{message.additionally.payload}</ThemedText> :
|
||||
<ThemedText>!!! Please Update Program !!!</ThemedText>
|
||||
}
|
||||
|
||||
</ThemedView>
|
||||
))
|
||||
}
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.buttonAddStyle}>
|
||||
<Button
|
||||
// onPress={onPressLearnMore}
|
||||
title="+"
|
||||
color="green"
|
||||
accessibilityLabel="Add new block"
|
||||
/>
|
||||
</ThemedView>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
Message: {
|
||||
backgroundColor: 'grey',
|
||||
padding: 10,
|
||||
marginBottom: 10,
|
||||
borderRadius: 10,
|
||||
},
|
||||
buttonAddStyle: {
|
||||
width: 50,
|
||||
height: 60,
|
||||
// flex: 1,
|
||||
// justifyContent: 'end',
|
||||
},
|
||||
});
|
232
frontend/app/(tabs)/groups.tsx
Normal file
232
frontend/app/(tabs)/groups.tsx
Normal file
@ -0,0 +1,232 @@
|
||||
import { Image, StyleSheet, Button, ActivityIndicator, TextInput, Modal, Pressable, View } from 'react-native';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { useEffect, useState } from 'react';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
|
||||
interface Message {
|
||||
title: string;
|
||||
type: string;
|
||||
additionally: {
|
||||
payload: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupData {
|
||||
groupName: string;
|
||||
users: string[];
|
||||
messages: Message[];
|
||||
}
|
||||
|
||||
export default function GroupsScreen() {
|
||||
const [groups, setGroups] = useState<GroupData[]>([]);
|
||||
const [isSyncing, setIsSyncing] = useState<boolean>(false);
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
||||
const [newGroupName, setNewGroupName] = useState<string>("");
|
||||
|
||||
const fetchGroups = async () => {
|
||||
try {
|
||||
setIsSyncing(true);
|
||||
const response = await fetch("http://127.0.0.1:8099/getAllGroups");
|
||||
const data: GroupData[] = await response.json();
|
||||
setGroups(data);
|
||||
} catch (error) {
|
||||
console.log('Fetch error:', error);
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const createGroup = async () => {
|
||||
if (!newGroupName.trim()) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:8099/create_group?group_name=` + newGroupName + `&username=` + "moxitech", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await fetchGroups(); // Обновляем список групп после создания новой
|
||||
setIsModalVisible(false); // Закрываем модальное окно
|
||||
setNewGroupName(""); // Сбрасываем имя новой группы
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Create group error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const syncWithServer = async () => {
|
||||
await fetchGroups();
|
||||
};
|
||||
|
||||
const unsubscribe = NetInfo.addEventListener(state => {
|
||||
if (state.isConnected) {
|
||||
syncWithServer();
|
||||
}
|
||||
});
|
||||
|
||||
// Первоначальная загрузка данных
|
||||
syncWithServer();
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
||||
headerImage={
|
||||
<Image
|
||||
source={require('@/assets/images/partial-react-logo.png')}
|
||||
style={styles.reactLogo}
|
||||
/>
|
||||
}>
|
||||
|
||||
<ThemedView style={styles.buttonContainer}>
|
||||
<Button
|
||||
title="Создать группу"
|
||||
color="green"
|
||||
onPress={() => setIsModalVisible(true)}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
{/* {isSyncing ? (
|
||||
<ActivityIndicator size="large" color="#00ff00" />
|
||||
) : (
|
||||
<Text>None</Text>
|
||||
// groups.map( (group, groupIndex) => (
|
||||
// <ThemedView key={groupIndex} style={styles.groupContainer}>
|
||||
// <ThemedText type="title">{group.groupName}</ThemedText>
|
||||
|
||||
// <ThemedView style={styles.titleContainer}>
|
||||
// <ThemedText type="subtitle">Пользователи</ThemedText>
|
||||
// {group.users.map((user, userIndex) => (
|
||||
// <ThemedText key={userIndex}>{user}</ThemedText>
|
||||
// ))}
|
||||
// </ThemedView>
|
||||
|
||||
// <ThemedView>
|
||||
// {group.messages.map((message, messageIndex) => (
|
||||
// <ThemedView key={messageIndex} style={styles.Message}>
|
||||
// <ThemedText>{message.title}</ThemedText>
|
||||
// {message.type === "text" ?
|
||||
// <ThemedText>{message.additionally.payload}</ThemedText> :
|
||||
// <ThemedText>!!! Please Update Program !!!</ThemedText>
|
||||
// }
|
||||
// </ThemedView>
|
||||
// ))}
|
||||
// </ThemedView>
|
||||
// </ThemedView>
|
||||
// ))
|
||||
)
|
||||
} */}
|
||||
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={isModalVisible}
|
||||
onRequestClose={() => setIsModalVisible(false)}
|
||||
>
|
||||
<View style={styles.centeredView}>
|
||||
<View style={styles.modalView}>
|
||||
<ThemedText type="title">Создать новую группу</ThemedText>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Введите имя группы"
|
||||
value={newGroupName}
|
||||
onChangeText={setNewGroupName}
|
||||
/>
|
||||
<Pressable
|
||||
style={[styles.button, styles.buttonClose]}
|
||||
onPress={createGroup}
|
||||
>
|
||||
<ThemedText style={styles.textStyle}>Создать</ThemedText>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[styles.button, styles.buttonClose]}
|
||||
onPress={() => setIsModalVisible(false)}
|
||||
>
|
||||
<ThemedText style={styles.textStyle}>Отмена</ThemedText>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
groupContainer: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
Message: {
|
||||
backgroundColor: 'grey',
|
||||
padding: 10,
|
||||
marginBottom: 10,
|
||||
borderRadius: 10,
|
||||
},
|
||||
buttonContainer: {
|
||||
margin: 10,
|
||||
alignItems: 'center',
|
||||
},
|
||||
centeredView: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 22,
|
||||
},
|
||||
modalView: {
|
||||
margin: 20,
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 20,
|
||||
padding: 35,
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
},
|
||||
button: {
|
||||
borderRadius: 20,
|
||||
padding: 10,
|
||||
elevation: 2,
|
||||
},
|
||||
buttonClose: {
|
||||
backgroundColor: '#2196F3',
|
||||
marginTop: 10,
|
||||
},
|
||||
input: {
|
||||
height: 40,
|
||||
margin: 12,
|
||||
borderWidth: 1,
|
||||
padding: 10,
|
||||
width: 200,
|
||||
},
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
@ -1,9 +1,9 @@
|
||||
import { Image, StyleSheet, Platform, Button, ActivityIndicator } from 'react-native';
|
||||
|
||||
import { Image, StyleSheet, Button, ActivityIndicator, TextInput, Alert } from 'react-native';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { useEffect, useState } from 'react';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
|
||||
interface Message {
|
||||
title: string;
|
||||
@ -20,18 +20,83 @@ interface Data {
|
||||
|
||||
export default function HomeScreen() {
|
||||
const [groupData, setGroupData] = useState<Data | null>(null);
|
||||
const [newMessageTitle, setNewMessageTitle] = useState<string>('');
|
||||
const [newMessagePayload, setNewMessagePayload] = useState<string>('');
|
||||
const [isSyncing, setIsSyncing] = useState<boolean>(false);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch("http://127.0.0.1:8099/grouphash:1/reloadDataFromServer");
|
||||
const data: Data = await response.json();
|
||||
setGroupData(data);
|
||||
} catch (error) {
|
||||
console.log('Fetch error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const addNewMessage = async () => {
|
||||
if (!newMessageTitle || !newMessagePayload) {
|
||||
Alert.alert("Error", "Please fill in both the title and the content of the message.");
|
||||
return;
|
||||
}
|
||||
|
||||
const newMessage: Message = {
|
||||
title: newMessageTitle,
|
||||
type: "text",
|
||||
additionally: {
|
||||
payload: newMessagePayload,
|
||||
}
|
||||
};
|
||||
|
||||
// Мгновенное обновление локального состояния
|
||||
setGroupData(prevData => {
|
||||
if (prevData) {
|
||||
return {
|
||||
...prevData,
|
||||
messages: [...prevData.messages, newMessage],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
setNewMessageTitle('');
|
||||
setNewMessagePayload('');
|
||||
|
||||
// Отправка новой заметки на сервер
|
||||
try {
|
||||
await fetch("http://127.0.0.1:8099/grouphash:1/addMessage", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(newMessage),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Error syncing new message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetch("http://127.0.0.1:8099/grouphash:1/reloadDataFromServer")
|
||||
.then(v => v.json())
|
||||
.then((data: Data) => {
|
||||
setGroupData(data);
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
});
|
||||
const syncWithServer = async () => {
|
||||
setIsSyncing(true);
|
||||
await fetchData();
|
||||
setIsSyncing(false);
|
||||
};
|
||||
|
||||
const unsubscribe = NetInfo.addEventListener(state => {
|
||||
if (state.isConnected) {
|
||||
syncWithServer();
|
||||
}
|
||||
});
|
||||
|
||||
// Первоначальная загрузка данных
|
||||
syncWithServer();
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
||||
@ -49,8 +114,8 @@ export default function HomeScreen() {
|
||||
))}
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView >
|
||||
{ groupData === null ?
|
||||
<ThemedView>
|
||||
{isSyncing ?
|
||||
<ActivityIndicator size="large" color="#00ff00" /> :
|
||||
groupData?.messages.map((message, index) => (
|
||||
<ThemedView key={index} style={styles.Message}>
|
||||
@ -59,14 +124,29 @@ export default function HomeScreen() {
|
||||
<ThemedText>{message.additionally.payload}</ThemedText> :
|
||||
<ThemedText>!!! Please Update Program !!!</ThemedText>
|
||||
}
|
||||
|
||||
</ThemedView>
|
||||
))
|
||||
}
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.inputContainer}>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Title"
|
||||
value={newMessageTitle}
|
||||
onChangeText={setNewMessageTitle}
|
||||
/>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Content"
|
||||
value={newMessagePayload}
|
||||
onChangeText={setNewMessagePayload}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonAddStyle}>
|
||||
<Button
|
||||
// onPress={onPressLearnMore}
|
||||
onPress={addNewMessage}
|
||||
title="+"
|
||||
color="green"
|
||||
accessibilityLabel="Add new block"
|
||||
@ -75,16 +155,13 @@ export default function HomeScreen() {
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
@ -101,7 +178,15 @@ const styles = StyleSheet.create({
|
||||
buttonAddStyle: {
|
||||
width: 50,
|
||||
height: 60,
|
||||
// flex: 1,
|
||||
// justifyContent: 'end',
|
||||
},
|
||||
inputContainer: {
|
||||
padding: 10,
|
||||
},
|
||||
input: {
|
||||
height: 40,
|
||||
borderColor: 'gray',
|
||||
borderWidth: 1,
|
||||
marginBottom: 10,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
});
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 24 KiB |
9
frontend/package-lock.json
generated
9
frontend/package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-native-community/netinfo": "^11.3.2",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"expo": "~51.0.28",
|
||||
"expo-constants": "~16.0.2",
|
||||
@ -5869,6 +5870,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-community/netinfo": {
|
||||
"version": "11.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz",
|
||||
"integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==",
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.59"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/assets-registry": {
|
||||
"version": "0.74.87",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz",
|
||||
|
@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-native-community/netinfo": "^11.3.2",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"expo": "~51.0.28",
|
||||
"expo-constants": "~16.0.2",
|
||||
|
@ -9,6 +9,8 @@ require (
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||
github.com/gofiber/websocket/v2 v2.2.1 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
@ -17,6 +19,7 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
|
@ -2,8 +2,12 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
|
||||
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@ -23,6 +27,8 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
|
@ -1,12 +1,189 @@
|
||||
package fiber
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
)
|
||||
|
||||
// Структуры для данных
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
Secret []string `json:"secret_words"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
Name string `json:"name"`
|
||||
Users []string `json:"users"`
|
||||
Messages []Message `json:"messages"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
Additionally struct {
|
||||
Timestamp int64 `json:"ts"`
|
||||
Payload string `json:"payload"`
|
||||
} `json:"additionally"`
|
||||
}
|
||||
|
||||
var (
|
||||
users = make(map[string]User) // карта пользователей по никнейму
|
||||
groups = make(map[string]Group) // карта групп по имени
|
||||
)
|
||||
|
||||
func getAllGroups(c *fiber.Ctx) error {
|
||||
// groupList := []GroupData{}
|
||||
// for _, group := range groups {
|
||||
// groupList = append(groupList, group)
|
||||
// }
|
||||
return c.JSON(groups)
|
||||
}
|
||||
|
||||
// Функция генерации уникального набора слов (можно заменить на более сложную логику)
|
||||
func generateSecretWords() []string {
|
||||
return []string{"word1", "word2", "word3", "word4", "word5", "word6", "word7", "word8"}
|
||||
}
|
||||
|
||||
// Обработчик регистрации
|
||||
func register(c *fiber.Ctx) error {
|
||||
username := c.Query("username")
|
||||
if username == "" {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Username is required"})
|
||||
}
|
||||
if _, exists := users[username]; exists {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Username already exists"})
|
||||
}
|
||||
|
||||
secretWords := generateSecretWords()
|
||||
users[username] = User{
|
||||
Username: username,
|
||||
Secret: secretWords,
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"username": username,
|
||||
"secret_words": secretWords,
|
||||
})
|
||||
}
|
||||
|
||||
// Обработчик создания группы
|
||||
func createGroup(c *fiber.Ctx) error {
|
||||
groupName := c.Query("group_name")
|
||||
username := c.Query("username")
|
||||
|
||||
if groupName == "" || username == "" {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Group name and username are required"})
|
||||
}
|
||||
if _, exists := groups[groupName]; exists {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Group already exists"})
|
||||
}
|
||||
if _, exists := users[username]; !exists {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "User does not exist"})
|
||||
}
|
||||
|
||||
group := Group{
|
||||
Name: groupName,
|
||||
Users: []string{username},
|
||||
Messages: []Message{},
|
||||
}
|
||||
|
||||
groups[groupName] = group
|
||||
return c.JSON(group)
|
||||
}
|
||||
|
||||
// Обработчик добавления пользователя в группу
|
||||
func addUserToGroup(c *fiber.Ctx) error {
|
||||
groupName := c.Query("group_name")
|
||||
username := c.Query("username")
|
||||
|
||||
if groupName == "" || username == "" {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Group name and username are required"})
|
||||
}
|
||||
group, exists := groups[groupName]
|
||||
if !exists {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Group does not exist"})
|
||||
}
|
||||
if _, exists := users[username]; !exists {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "User does not exist"})
|
||||
}
|
||||
|
||||
group.Users = append(group.Users, username)
|
||||
groups[groupName] = group
|
||||
|
||||
return c.JSON(group)
|
||||
}
|
||||
|
||||
// Обработчик подключения через WebSocket для синхронизации заметок
|
||||
func syncGroup(c *fiber.Ctx) error {
|
||||
groupName := c.Params("grouphash")
|
||||
if websocket.IsWebSocketUpgrade(c) {
|
||||
c.Locals("groupName", groupName)
|
||||
return c.Next()
|
||||
}
|
||||
return c.Status(426).SendString("Upgrade required")
|
||||
}
|
||||
|
||||
func handleWebSocket(c *websocket.Conn) {
|
||||
groupName := c.Locals("groupName").(string)
|
||||
for {
|
||||
// Пример синхронизации: получение сообщения и его обработка
|
||||
msgType, msg, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Обновляем данные группы (на практике нужно добавить больше логики)
|
||||
group := groups[groupName]
|
||||
group.Messages = append(group.Messages, Message{
|
||||
Title: string(msg),
|
||||
Type: "text",
|
||||
Additionally: struct {
|
||||
Timestamp int64 `json:"ts"`
|
||||
Payload string `json:"payload"`
|
||||
}{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Payload: string(msg),
|
||||
},
|
||||
})
|
||||
groups[groupName] = group
|
||||
|
||||
// Отправка обновленных данных всем пользователям группы
|
||||
for _, user := range group.Users {
|
||||
fmt.Printf("Sending update to user: %s\n", user)
|
||||
err = c.WriteMessage(msgType, []byte("New message in group: "+groupName))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func addMessageToGroup(c *fiber.Ctx) error {
|
||||
groupName := c.Params("grouphash")
|
||||
group, exists := groups[groupName]
|
||||
if !exists {
|
||||
return c.Status(404).JSON(fiber.Map{"error": "Group not found"})
|
||||
}
|
||||
|
||||
var newMessage Message
|
||||
if err := c.BodyParser(&newMessage); err != nil {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
// Добавление метаданных
|
||||
newMessage.Additionally.Timestamp = time.Now().Unix()
|
||||
|
||||
group.Messages = append(group.Messages, newMessage)
|
||||
groups[groupName] = group
|
||||
|
||||
return c.JSON(group)
|
||||
}
|
||||
|
||||
// Основная функция запуска сервера
|
||||
func SpawnFiberCtx() error {
|
||||
app := fiber.New()
|
||||
app.Use(cors.New(cors.Config{
|
||||
@ -14,60 +191,30 @@ func SpawnFiberCtx() error {
|
||||
AllowMethods: "*",
|
||||
AllowHeaders: "*",
|
||||
}))
|
||||
|
||||
// app.Use()
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(404)
|
||||
})
|
||||
// Login endpoint
|
||||
app.Get("/login", func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(404)
|
||||
})
|
||||
// Register endpoint
|
||||
app.Get("/register", func(c *fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{
|
||||
"Login": "Moxitech",
|
||||
"Secret words": []string{
|
||||
"Moxi",
|
||||
"Tech",
|
||||
"Cruiser",
|
||||
"Well",
|
||||
"Small",
|
||||
"NotBad",
|
||||
},
|
||||
})
|
||||
})
|
||||
// Give websocket connection for sync groups list
|
||||
app.Get("/connect", func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(404)
|
||||
})
|
||||
// Give websocket connection for sync concrete group
|
||||
app.Get("/connect/grouphash::grouphash/", func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(404)
|
||||
})
|
||||
app.Get("/grouphash::grouphash/reloadDataFromServer", func(c *fiber.Ctx) error {
|
||||
return c.SendString(`
|
||||
{
|
||||
"users": [
|
||||
"Aksina",
|
||||
"Moxitech"
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"title": "Выполнить конфигурацию проекта",
|
||||
"type": "text",
|
||||
"additionally": {
|
||||
"payload": "information message"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Написать очередной сервис",
|
||||
"type": "text",
|
||||
"additionally": {
|
||||
"payload": "Сервис Quic-GO..."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
// Регистрация пользователя
|
||||
app.Get("/register", register)
|
||||
|
||||
// Создание группы
|
||||
app.Get("/create_group", createGroup)
|
||||
|
||||
// Добавление пользователя в группу
|
||||
app.Get("/add_user_to_group", addUserToGroup)
|
||||
|
||||
// Обработчик добавления нового сообщения в группу
|
||||
app.Post("/grouphash::grouphash/addMessage", addMessageToGroup)
|
||||
// Возвращает все группы
|
||||
app.Get("/getAllGroups", getAllGroups)
|
||||
// Подключение к группе через WebSocket
|
||||
app.Get("/connect/grouphash::grouphash", syncGroup)
|
||||
|
||||
// Обработчик для работы с WebSocket
|
||||
app.Use("/ws", websocket.New(handleWebSocket))
|
||||
|
||||
return app.Listen("0.0.0.0:" + os.Getenv("API_PORT"))
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package websocket
|
||||
|
||||
// Make conn pull && observable all data from group client to websocket
|
||||
|
||||
var ()
|
||||
|
||||
func Provider() {
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user