Update chat screen and add a StubMessageService
continuous-integration/drone/push Build is passing Details

pull/19/head
Emre KARTAL 1 year ago
parent d2421812e2
commit b746fbf7d2

@ -116,7 +116,7 @@ Une fois sur la page, saisissez votre nom, votre adresse e-mail, et votre mot de
Pour accéder aux détails d'une musique, maintenez votre doigt appuyé sur un Spot ou rendez-vous sur la page des favoris. Vous pourrez écouter la musique :arrow_forward:, obtenir des informations sur l'artiste et la chanson, découvrir des musiques similaires, et même l'ajouter à votre playlist Spotify ou la partager.
<br/>
`Dans la page **settings** ⚙️, vous avez accès à toutes vos informations ```Spotify```, que vous pouvez modifier à votre guise. Toutefois, ces modifications ne seront prises en compte que dans notre application. Vous pouvez également choisir le mode sombre (dark mode) dans les paramètres pour une expérience de navigation plus confortable.
Dans la page **settings** ⚙️, vous avez accès à toutes vos informations ```Spotify```, que vous pouvez modifier à votre guise. Toutefois, ces modifications ne seront prises en compte que dans notre application. Vous pouvez également choisir le mode sombre (dark mode) dans les paramètres pour une expérience de navigation plus confortable.
<br/>
### Voici un petit récapitulatif

@ -1,4 +1,4 @@
import { Router, Request, Response, NextFunction } from 'express';
import { Router, Request, Response } from 'express';
import IController from './interfaces/IController';
import User from '../models/User';
import UserService from '../services/UserService';

@ -4,11 +4,12 @@ import { useSelector } from 'react-redux';
import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight';
import normalize from './Normalize';
import Message from '../models/Message';
type FriendProps = {
image: string;
name: string;
lastMessage: string;
lastMessage: Message;
}
export default function Friend(props: FriendProps) {
@ -67,14 +68,41 @@ export default function Friend(props: FriendProps) {
}
})
const getTimeDifferenceString = (date: Date): string => {
const now = new Date();
const differenceInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
const intervals = {
an: 31536000,
mois: 2592000,
sem: 604800,
jour: 86400,
heure: 3600,
min: 60,
};
for (const [intervalName, seconds] of Object.entries(intervals)) {
const intervalCount = Math.floor(differenceInSeconds / seconds);
if (intervalCount > 0) {
if (intervalName === 'mois' || intervalName === 'min') {
return `il y a ${intervalCount} ${intervalName}`;
} else {
return `il y a ${intervalCount} ${intervalName}${intervalCount !== 1 ? 's' : ''}`;
}
}
}
return 'À linstant';
};
return (
<View style={styles.container}>
<Image style={styles.image} source={source} />
<View style={styles.profilContainer}>
<Text style={styles.name} numberOfLines={1}>{props.name}</Text>
<View style={styles.lastMessageContainer}>
<Text style={styles.lastMessage} numberOfLines={1}>{props.lastMessage}</Text>
<Text style={styles.time}> · 1sem</Text>
<Text style={styles.lastMessage} numberOfLines={1}>{props.lastMessage.content}</Text>
<Text style={styles.time}> · {getTimeDifferenceString(props.lastMessage.date)}</Text>
</View>
</View>
<Image style={styles.button} source={require('../assets/images/chevron_right_icon.png')} />

@ -8,7 +8,7 @@ export default function Paginator({ data, scrollX }) {
return (
<View style={{ flexDirection: 'row', height: 64, marginBottom: normalize(50) }}>
{data.map((_ : any, i : any) => {
{ data.map((_ : any, i : any) => {
const inputRange = [(i - 1) * width, i * width, (i + 1) * width];
const dotWidth = scrollX.interpolate({

@ -3,7 +3,7 @@ import Music from '../models/Music';
import normalize from './Normalize';
export interface RenderCellProps {
music : Music;
music: Music;
}
export const SimilarMusic = (props: RenderCellProps) => {
return (

@ -0,0 +1,47 @@
import Message from "./Message";
export default class Conversation {
private _id: string;
private _name: string;
private _image: string;
private _lastMessage: Message;
constructor(id: string, name: string, image: string, lastMessage: Message) {
this._id = id;
this._name = name;
this._image = image;
this._lastMessage = lastMessage;
}
get id(): string {
return this._id;
}
set id(value: string) {
this._id = value;
}
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
get image(): string {
return this._image;
}
set image(value: string) {
this._image = value;
}
get lastMessage(): Message {
return this._lastMessage;
}
set lastMessage(value: Message) {
this._lastMessage = value;
}
}

@ -0,0 +1,55 @@
export default class Message {
private _id: string;
private _content: string;
private _sender: string;
private _date: Date;
private _audio: string;
constructor(id: string, content: string, sender: string, date: Date, audio: string = '') {
this._id = id;
this._content = content;
this._sender = sender;
this._date = date;
this._audio = audio;
}
get id(): string {
return this._id;
}
set id(value: string) {
this._id = value;
}
get content(): string {
return this._content;
}
set content(value: string) {
this._content = value;
}
get sender(): string {
return this._sender;
}
set sender(value: string) {
this._sender = value;
}
get date(): Date {
return this._date;
}
set date(value: Date) {
this._date = value;
}
get audio(): string {
return this._audio;
}
set audio(value: string) {
this._audio = value;
}
}

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { View, Alert, Platform } from 'react-native';
import { faUser, faEnvelope, faHeart, faMusic } from "@fortawesome/free-solid-svg-icons"
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { NavigationContainer } from '@react-navigation/native';
import { NavigationContainer, getFocusedRouteNameFromRoute } from '@react-navigation/native';
import FavoriteNavigation from './FavoriteNavigation';
import SettingNavigation from './SettingNavigation';
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome";
@ -156,10 +156,15 @@ export default function HomeNavigation() {
tabBarIcon: ({ color }) => <View><TabBarIcon name={faHeart} color={color} size={23} /></View>,
}} />
<BottomTabNavigator.Screen name="Messages" component={MessagingNavigation}
options={{
options={({ route }) => ({
headerShown: false,
tabBarStyle: {
display: getFocusedRouteNameFromRoute(route) !== "Chat" ? "flex" : "none",
position: "absolute",
borderTopColor: isDark ? 'rgba(255, 255, 255, 0.25)' : 'rgba(50, 50, 50, 0.07)',
},
tabBarIcon: ({ color }) => <View ><TabBarIcon name={faEnvelope} color={color} size={23} /></View>,
}} />
})} />
<BottomTabNavigator.Screen name="Profil" component={SettingNavigation}
options={{
headerShown: false,

@ -1,12 +1,38 @@
import React from 'react';
import ConversationScreen from '../screens/ConversationScreen'
import { Image, View, Text, StyleSheet } from 'react-native';
import ChatScreen from '../screens/ChatScreen';
import { createStackNavigator } from '@react-navigation/stack';
import { colorsDark } from "../constants/colorsDark";
import { colorsLight } from "../constants/colorsLight";
import { useSelector } from 'react-redux';
export default function MessagingNavigation() {
// @ts-ignore
const isDark = useSelector(state => state.userReducer.dark);
const style = isDark ? colorsDark : colorsLight;
const Stack = createStackNavigator();
const styles = StyleSheet.create({
headerContainer: {
flexDirection: 'row',
alignItems: 'center'
},
headerImage: {
width: 30,
height: 30,
borderRadius: 20,
marginRight: 8
},
headerText: {
color: style.Text,
fontSize: 16,
fontWeight: 'bold'
}
});
return (
<Stack.Navigator initialRouteName="Conversation" >
<Stack.Navigator initialRouteName="Conversation">
<Stack.Screen
name="Conversation"
component={ConversationScreen}
@ -15,7 +41,29 @@ export default function MessagingNavigation() {
<Stack.Screen
name="Chat"
component={ChatScreen}
options={{ headerShown: false }}
options={({ route }) => ({
headerShown: true,
headerBackTitleVisible: false,
headerStyle: {
backgroundColor: style.Card
},
headerTitle: () => (
<View style={styles.headerContainer}>
<Image
// @ts-ignore
source={{ uri: route.params.image }}
style={styles.headerImage}
/>
<Text style={styles.headerText}>
{/* @ts-ignore */}
{route.params.username}
</Text>
</View>
),
headerTitleStyle: {
color: style.Text
},
})}
/>
</Stack.Navigator>
)

@ -25,6 +25,7 @@
"expo-image-picker": "~14.0.2",
"expo-linear-gradient": "~12.0.1",
"expo-location": "~15.0.1",
"expo-random": "^13.0.0",
"expo-secure-store": "~12.0.0",
"expo-splash-screen": "~0.17.5",
"react": "18.1.0",
@ -37,8 +38,9 @@
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "~3.18.0",
"react-native-svg": "13.4.0",
"react-navigation-shared-element": "^3.1.3",
"react-native-vector-icons": "^10.0.3",
"react-native-web": "~0.18.9",
"react-navigation-shared-element": "^3.1.3",
"react-redux": "^8.0.5",
"redux": "^4.2.1"
},
@ -46,6 +48,7 @@
"@babel/core": "^7.12.9",
"@types/react": "~18.0.14",
"@types/react-native": "~0.70.8",
"@types/react-native-vector-icons": "^6.4.18",
"typescript": "^4.6.3"
},
"private": true

@ -1,5 +1,8 @@
import Conversation from "../../models/Conversation";
import Message from "../../models/Message";
import Music from "../../models/Music";
import { Spot } from "../../models/Spot";
import { chatTypes } from "../types/chatTypes";
import { favoritesTypes } from "../types/favoritesTypes";
import { spotifyTypes } from "../types/spotifyTypes";
@ -29,3 +32,17 @@ export const resetNbAddedFavoriteMusic = () => {
type: favoritesTypes.RESET_NB_ADDED_FAVORITE_MUSIC
};
}
export const setConversations = (conversations: Conversation[]) => {
return {
type: chatTypes.FETCH_CONVERSATIONS,
payload: conversations,
};
}
export const setMessages = (messages: Message[]) => {
return {
type: chatTypes.FETCH_MESSAGES,
payload: messages,
};
}

@ -1,4 +1,7 @@
import Conversation from "../../models/Conversation";
import Message from "../../models/Message";
import { Spot } from "../../models/Spot";
import { chatTypes } from "../types/chatTypes";
import { favoritesTypes } from "../types/favoritesTypes";
import { spotifyTypes } from "../types/spotifyTypes";
import { spotTypes } from "../types/spotTypes";
@ -10,6 +13,8 @@ const initialState = {
userCurrentMusic: null,
nbAddedFavoriteMusic: 0,
oldSpot: [] as string[],
conversations: [] as Conversation[],
messages: [] as Message[]
}
const appReducer = (state = initialState, action: any) => {
@ -23,6 +28,10 @@ const appReducer = (state = initialState, action: any) => {
};
case favoritesTypes.RESET_NB_ADDED_FAVORITE_MUSIC:
return { ...state, nbAddedFavoriteMusic: 0 };
case chatTypes.FETCH_CONVERSATIONS:
return { ...state, conversations: action.payload };
case chatTypes.FETCH_MESSAGES:
return { ...state, messages: action.payload };
case spotTypes.FETCH_SPOT:
const uniqueSpots = action.payload.filter((spot: Spot) => {
const spotKey = `${spot.user}_${spot.music.id}`;

@ -28,6 +28,7 @@ export const getUserCurrentMusic = () => {
const music = await MusicServiceProvider.musicService.getMusicById(idTrack);
dispatch(setUserCurrentMusic(music))
} catch (error: any) {
console.log(error);
switch (error.response.status) {
case 401:
dispatch(logout);

@ -0,0 +1,42 @@
import Message from "../../models/Message";
import IMessageService from "../../services/messages/interfaces/IMessageService"
import StubMessageService from "../../services/messages/stub/StubMessageService";
import { setConversations, setMessages } from "../actions/appActions";
const chatService: IMessageService = new StubMessageService();
export const getConversations = () => {
//@ts-ignore
return async dispatch => {
try {
const conversations = await chatService.getConversations();
dispatch(setConversations(conversations));
} catch (error: any) {
}
}
}
export const getMessages = (id: string) => {
//@ts-ignore
return async dispatch => {
try {
const messages = await chatService.getMessagesWithIdConversation(id);
dispatch(setMessages(messages));
} catch (error: any) {
}
}
}
export const sendMessage = (id: string, message: Message) => {
//@ts-ignore
return async dispatch => {
try {
await chatService.sendMessage(id, message);
dispatch(getMessages(id));
} catch (error: any) {
}
}
}

@ -0,0 +1,4 @@
export const chatTypes = {
FETCH_CONVERSATIONS: 'FETCH_CONVERSATIONS',
FETCH_MESSAGES: 'FETCH_MESSAGES',
}

@ -1,22 +1,178 @@
import { useNavigation } from "@react-navigation/native";
import React, { useEffect } from "react";
import { GiftedChat } from "react-native-gifted-chat";
import React, { useCallback, useEffect, useState } from "react";
import { Bubble, GiftedChat, IMessage, InputToolbar, Send } from "react-native-gifted-chat";
import { faFileImage, faMicrophone } from "@fortawesome/free-solid-svg-icons"
import { colorsDark } from "../constants/colorsDark";
import { colorsLight } from "../constants/colorsLight";
import { useDispatch, useSelector } from 'react-redux';
import { SafeAreaView, TouchableOpacity, View } from "react-native";
import Icon from 'react-native-vector-icons/Ionicons';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import Message from "../models/Message";
import { getMessages, sendMessage } from "../redux/thunk/chatThunk";
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome";
export default function Chat() {
//@ts-ignore
export default function Chat({ route }) {
const item: string = route.params.conversation;
const [messages, setMessages] = useState<IMessage[]>();
//@ts-ignore
const conversations: Message[] = useSelector(state => state.appReducer.messages);
const navigation = useNavigation();
// @ts-ignore
const isDark = useSelector(state => state.userReducer.dark);
const style = isDark ? colorsDark : colorsLight;
const dispatch = useDispatch();
useEffect(() => {
navigation.getParent()?.setOptions({
tabBarStyle: {
display: "none"
}
});
return () => navigation.getParent()?.setOptions({
tabBarStyle: undefined
// @ts-ignore
dispatch(getMessages(item));
}, []);
useEffect(() => {
const mappedMessages = conversations.map((msg: Message) => ({
_id: msg.id,
text: msg.content,
createdAt: msg.date,
user: {
_id: msg.sender,
name: msg.sender,
avatar: 'https://picsum.photos/536/354',
},
audio: msg.audio
}));
mappedMessages.reverse();
mappedMessages.push({
_id: "0",
text: 'Vous avez matché !!!',
system: true,
});
}, [navigation]);
setMessages(mappedMessages);
}, [conversations]);
const onSend = useCallback((messages: any = []) => {
const newMessage = new Message(
"-1",
messages[0].text,
"User1",
new Date()
);
// @ts-ignore
dispatch(sendMessage(item, newMessage));
}, [])
const renderBubble = (props: any) => {
return (
<Bubble
{...props}
wrapperStyle={{
left: {
backgroundColor: style.Card,
}
}}
textStyle={{
left: {
paddingHorizontal: 3,
color: style.Text,
},
right: {
paddingHorizontal: 3
}
}}
/>
);
};
const renderInputToolbar = (props: any) => {
return (
<InputToolbar
{...props}
containerStyle={{
backgroundColor: style.Card,
borderTopWidth: 0,
}}
primaryStyle={{
alignItems: "center"
}}
/>
);
};
function handleImageIconPress(): void {
console.log("Image");
}
function handleMicrophoneIconPress(): void {
console.log("Audio");
}
const renderActions = (props: any) => {
return (
<View style={{ flexDirection: 'row', marginLeft: 15 }}>
<TouchableOpacity onPress={handleImageIconPress}>
<FontAwesomeIcon icon={faFileImage} size={20} color={style.Line} style={{ marginRight: 10 }} />
</TouchableOpacity>
<TouchableOpacity onPress={handleMicrophoneIconPress}>
<FontAwesomeIcon icon={faMicrophone} size={20} color={style.Line} />
</TouchableOpacity>
</View>
);
};
const renderSend = (props: any) => {
return (
<Send {...props}>
<View>
<Icon name="send" size={20} style={{ marginBottom: 12, marginHorizontal: 8 }} color="#2e64e5" />
</View>
</Send>
);
};
const scrollToBottomComponent = () => {
return (
<FontAwesome name='angle-double-down' size={22} color="#333" />
)
}
return (
<GiftedChat />
)
<SafeAreaView style={{ flex: 1, backgroundColor: style.Card }}>
<GiftedChat
messages={messages}
onSend={messages => onSend(messages)}
user={{
_id: "User1",
}}
listViewProps={{
style: {
backgroundColor: style.body
},
}}
// @ts-ignore
textInputStyle={{
backgroundColor: style.Line,
borderRadius: 20,
paddingHorizontal: 15,
paddingTop: 10,
marginTop: 5,
color: style.Text
}}
renderActions={renderActions}
maxInputLength={255}
renderBubble={renderBubble}
renderInputToolbar={renderInputToolbar}
renderSend={renderSend}
scrollToBottom
scrollToBottomComponent={scrollToBottomComponent}
placeholder="Chat"
/>
</SafeAreaView>
);
}

@ -1,25 +1,39 @@
import { useNavigation } from "@react-navigation/native";
import { StyleSheet, Text, View, FlatList, TouchableOpacity } from "react-native";
import { useSelector } from "react-redux";
import { StyleSheet, Text, View, FlatList, TouchableOpacity, RefreshControl } from "react-native";
import { useSelector, useDispatch } from "react-redux";
import { colorsDark } from '../constants/colorsDark';
import { colorsLight } from '../constants/colorsLight';
import Friend from "../components/FriendComponent";
import normalize from '../components/Normalize';
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useCallback, useEffect, useState } from "react";
import { getConversations } from "../redux/thunk/chatThunk";
export default function ConversationScreen() {
//@ts-ignore
const friends = useSelector(state => state.appReducer.conversations);
// @ts-ignore
const isDark = useSelector(state => state.userReducer.dark);
const [refreshing, setRefreshing] = useState(false);
const navigation = useNavigation();
const dispatch = useDispatch();
useEffect(() => {
// @ts-ignore
dispatch(getConversations());
}, []);
const friends = [
{ id: 1, name: "Lucas", lastMessage: "J'en ai marre de provot", source: "https://i1.sndcdn.com/artworks-ncJnbnDbNOFd-0-t500x500.jpg" },
{ id: 2, name: "Louison", lastMessage: "Tu vien piscine ?", source: "https://i1.sndcdn.com/artworks-ncJnbnDbNOFd-0-t500x500.jpg" },
{ id: 3, name: "Dave", lastMessage: "Ok c noté !", source: "https://img.lemde.fr/2019/04/05/0/0/960/960/664/0/75/0/18299d3_tUvp2AZPH_jnsIL2ypVFGUro.jpg" },
{ id: 4, name: "Valentin", lastMessage: "Haha react native c incroyable !!!", source: "https://i1.sndcdn.com/artworks-ncJnbnDbNOFd-0-t500x500.jpg" },
];
const handleRefresh = () => {
setRefreshing(true);
//@ts-ignore
dispatch(getConversations());
setTimeout(() => {
setRefreshing(false);
}, 700);
};
const navigation = useNavigation();
const style = isDark ? colorsDark : colorsLight;
@ -44,6 +58,18 @@ export default function ConversationScreen() {
fontSize: normalize(20),
color: '#787878',
marginBottom: 5
},
body: {
alignItems: 'center',
justifyContent: 'center',
flex: 1,
marginHorizontal: "7%"
},
text: {
color: style.Text,
fontSize: normalize(18),
opacity: 0.8,
textAlign: 'center'
}
})
@ -53,18 +79,33 @@ export default function ConversationScreen() {
<Text style={styles.title}>Messages</Text>
<Text style={styles.description}>Retrouvez ici les discussions</Text>
</View>
<FlatList
style={{ marginTop: 10 }}
data={friends}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity
{friends.length === 0 ? (
<View style={styles.body}>
<Text style={ styles.text }>
Pas de conversations pour le moment. 🥲{'\n'}
Va liker des musiques pour créer des conversations avec des gens dans le monde ! 🔥🎆
</Text>
</View>
) : (
<FlatList
style={{ marginTop: 10 }}
data={friends.sort((a: any, b: any) => b.lastMessage.date - a.lastMessage.date)}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
// @ts-ignore
onPress={() => navigation.navigate('Chat')}>
<Friend image={item.source} name={item.name} lastMessage={item.lastMessage} />
</TouchableOpacity>
)}
/>
<TouchableOpacity onPress={() => navigation.navigate('Chat', { username: item.name, image: item.image, conversation: item.id })}>
<Friend image={item.image} name={item.name} lastMessage={item.lastMessage} />
</TouchableOpacity>
)}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={style.Text}
/>
}
/>
)}
</View>
)
}

@ -136,7 +136,7 @@ export default function FavoriteScreen() {
<Text style={styles.description}>Retrouvez ici vos musiques favorites</Text>
</View>
<SectionList
style = {styles.collection}
style={styles.collection}
sections={groupByDate(favoriteMusic)}
refreshControl={
<RefreshControl

@ -19,9 +19,9 @@ import { colorsLight } from '../constants/colorsLight';
export default function SpotScreen() {
//@ts-ignore
const spotReducer: Spot[] = useSelector(state => state.appReducer.spot)
// @ts-ignore
const isDark = useSelector(state => state.userReducer.dark);
const style = isDark ? colorsDark : colorsLight;
// @ts-ignore
const isDark = useSelector(state => state.userReducer.dark);
const style = isDark ? colorsDark : colorsLight;
const [cards, setCards] = useState<Spot[]>(spotReducer);
const [currentCard, setcurrentCard] = useState<Spot>(cards[cards.length - 1]);

@ -0,0 +1,8 @@
import Conversation from "../../../models/Conversation";
import Message from "../../../models/Message";
export default interface IMessageService {
getConversations(): Promise<Conversation[]>;
getMessagesWithIdConversation(id: string): Promise<Message[]>;
sendMessage(id: string, mes: Message): Promise<void>;
}

File diff suppressed because one or more lines are too long

@ -53,9 +53,11 @@ export default class SpotifyService implements IMusicService {
},
});
if (response.data.item === undefined) {
if (response.data.item === undefined || response.data.item === null) {
return null;
}
return response.data.item.id
}

Loading…
Cancel
Save