diff --git a/android/app/build.gradle b/android/app/build.gradle index 40ff328e..bca8bdd9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -108,6 +108,7 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") implementation(platform("com.google.firebase:firebase-bom:32.6.0")) + implementation project(':react-native-share') debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.squareup.okhttp3', module:'okhttp' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 09316e07..d926ba5e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -27,6 +27,12 @@ + + + + + + diff --git a/android/app/src/main/java/com/kit_box/MainApplication.java b/android/app/src/main/java/com/kit_box/MainApplication.java index 5c781724..3d4f46c9 100644 --- a/android/app/src/main/java/com/kit_box/MainApplication.java +++ b/android/app/src/main/java/com/kit_box/MainApplication.java @@ -9,6 +9,8 @@ import com.facebook.react.defaults.DefaultReactNativeHost; import com.facebook.soloader.SoLoader; import java.util.List; +import cl.json.RNSharePackage; +import cl.json.ShareApplication; public class MainApplication extends Application implements ReactApplication { diff --git a/android/build.gradle b/android/build.gradle index a2a87f4b..c3c5d0fb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,6 +6,8 @@ buildscript { minSdkVersion = 21 compileSdkVersion = 33 targetSdkVersion = 33 + + // kotlinVersion = '1.5.0' // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. ndkVersion = "23.1.7779620" @@ -18,5 +20,6 @@ buildscript { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") classpath ('com.google.gms:google-services:4.4.0') + // classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } diff --git a/android/settings.gradle b/android/settings.gradle index b52bc8ba..def05afb 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -2,3 +2,7 @@ rootProject.name = 'kit_box' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' includeBuild('../node_modules/@react-native/gradle-plugin') +include ':react-native-audio-recorder-player' +project(':react-native-audio-recorder-player').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-audio-recorder-player/android') +include ':react-native-share' +project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android') \ No newline at end of file diff --git a/package.json b/package.json index 7bb6f538..60da166d 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,12 @@ "patch-package": "^8.0.0", "react": "18.2.0", "react-native": "0.72.6", + "react-native-audio-recorder-player": "^3.6.5", "react-native-blob-util": "^0.19.2", "react-native-calendars": "^1.1302.0", "react-native-document-picker": "^9.0.1", "react-native-dotenv": "^3.4.9", + "react-native-fs": "^2.20.0", "react-native-gesture-bottom-sheet": "^1.1.0", "react-native-gesture-handler": "^2.14.0", "react-native-gifted-chat": "^2.4.0", @@ -58,8 +60,10 @@ "react-native-safe-area-context": "^4.7.4", "react-native-screens": "^3.27.0", "react-native-select-dropdown": "^3.4.0", + "react-native-share": "^10.0.2", "react-native-size-matters": "^0.4.2", "react-native-slider": "^0.11.0", + "react-native-sound": "^0.11.2", "react-native-splash-screen": "^3.3.0", "react-native-svg": "^13.14.0", "react-native-svg-transformer": "^1.1.0", @@ -70,6 +74,7 @@ "react-native-twilio-video-webrtc": "^3.2.0", "react-native-vector-icons": "^10.0.1", "react-native-video": "^5.2.1", + "react-native-video-controls": "^2.8.1", "react-native-video-player": "^0.14.0", "react-native-vision-camera": "^3.6.10", "rn-bottom-drawer": "^1.4.3", diff --git a/src/Assets/images/video.png b/src/Assets/images/video.png new file mode 100644 index 00000000..a90e2b57 Binary files /dev/null and b/src/Assets/images/video.png differ diff --git a/src/Components/Chat/FileTransfer.jsx b/src/Components/Chat/FileTransfer.jsx index 467d6de1..863945c0 100644 --- a/src/Components/Chat/FileTransfer.jsx +++ b/src/Components/Chat/FileTransfer.jsx @@ -23,11 +23,11 @@ const FileTransfer = props => { {props.isFooter ? ( '' @@ -57,6 +57,7 @@ const styles = StyleSheet.create({ lineHeight: 20, marginLeft: 5, marginRight: 5, + maxWidth: moderateScale(175), }, textType: { color: 'black', diff --git a/src/Components/Chat/List.jsx b/src/Components/Chat/List.jsx index 4ad33bf9..a75cac1f 100644 --- a/src/Components/Chat/List.jsx +++ b/src/Components/Chat/List.jsx @@ -8,14 +8,19 @@ import Dot from 'react-native-vector-icons/Octicons'; import Check from 'react-native-vector-icons/AntDesign'; import {useNavigation} from '@react-navigation/core'; -const Lists = ({items, groupContact}) => { +const Lists = ({items, groupContact, handleGroupSelection}) => { const Item = items.item; const navigation = useNavigation(); return ( navigation.navigate('ChatRoom', {Item})}> + onLongPress={() => handleGroupSelection(Item)} + onPress={() => + groupContact?.length > 0 + ? handleGroupSelection(Item) + : navigation.navigate('ChatRoom', {Item}) + }> ( <> @@ -112,6 +117,7 @@ export default Lists; const styles = StyleSheet.create({ container: { + flex: 1, backgroundColor: colors.WHITE, marginBottom: moderateScale(-12), }, diff --git a/src/Components/Chat/ViewFile.jsx b/src/Components/Chat/ViewFile.jsx index f91e8918..795fdf14 100644 --- a/src/Components/Chat/ViewFile.jsx +++ b/src/Components/Chat/ViewFile.jsx @@ -2,31 +2,41 @@ import React, {useState} from 'react'; import {Modal, Portal, Text, TouchableRipple, Icon} from 'react-native-paper'; import {moderateScale} from 'react-native-size-matters'; import Pdf from 'react-native-pdf'; -import {StyleSheet, View} from 'react-native'; +import {Image, StyleSheet, View} from 'react-native'; import {colors} from '../../Utils/colors'; import {fonts} from '../../Utils/fonts'; -const ViewFile = ({props, visible, onClose}) => { - const filePath = props.currentMessage.file.url; +const ViewFile = ({props, visible, onClose, isImage, isVideo}) => { + const filePath = props.file.url || props.image; var name = ''; if (filePath !== undefined) { name = filePath.split('/').pop(); } - const [url, setUrl] = useState(props.currentMessage.file.url); + return ( {name} - + {!isImage && !isVideo && ( + + )} + {isImage && ( + + )} - + @@ -45,7 +55,7 @@ const styles = StyleSheet.create({ alignItems: 'center', position: 'absolute', borderColor: 'black', - left: '85%', + left: '88%', top: moderateScale(-30), }, textBtn: { @@ -63,8 +73,13 @@ const styles = StyleSheet.create({ // position: 'absolute', paddingTop: moderateScale(50), paddingLeft: moderateScale(20), - fontFamily: fonts.BOLD, - fontSize: moderateScale(20), - backgroundColor: colors.GRAY, + fontFamily: fonts.MEDIUM, + fontSize: moderateScale(12), + maxWidth: moderateScale(320), + }, + + viewImage: { + alignSelf: 'center', + resizeMode: 'contain', }, }); diff --git a/src/Components/Header/HeaderWithBackaction.jsx b/src/Components/Header/HeaderWithBackaction.jsx index 708c40b1..21695b0f 100644 --- a/src/Components/Header/HeaderWithBackaction.jsx +++ b/src/Components/Header/HeaderWithBackaction.jsx @@ -9,7 +9,7 @@ import MenuPopup from '../Menu/Menu'; import {useNavigation} from '@react-navigation/native'; import Icon from 'react-native-vector-icons/Feather'; -const HeaderWithBackaction = ({title, openMenu, isChat, profile}) => { +const HeaderWithBackaction = ({title, openMenu, isChat, profile_pic}) => { const navigation = useNavigation(); const _goBack = () => navigation.navigate('ChatList'); @@ -18,6 +18,8 @@ const HeaderWithBackaction = ({title, openMenu, isChat, profile}) => { const _handleMore = () => openMenu(); + console.log(profile_pic, ' pp'); + return ( { size={20} onPress={() => navigation.goBack()} /> - {isChat && } + {isChat && } { size={25} onPress={_handleMore} /> */} - {/* */} + ); @@ -65,7 +65,6 @@ const styles = StyleSheet.create({ title: { fontFamily: fonts.BOLD, fontSize: moderateScale(22), - fontWeight: 'bold', color: colors.WHITE, }, }); diff --git a/src/Components/Menu/Menu.jsx b/src/Components/Menu/Menu.jsx index ab98b655..b080c424 100644 --- a/src/Components/Menu/Menu.jsx +++ b/src/Components/Menu/Menu.jsx @@ -22,9 +22,19 @@ const MenuPopup = props => { }> - {}} title="Item 1" /> - {}} title="Item 2" /> - + {props.check && ( + <> + { + navigation.navigate('Groups'); + setVisible(false); + }} + title="Create Group" + /> + {}} title="Settings" /> + + + )} navigation.replace('Login')} title="Logout" /> ); diff --git a/src/Components/Modal/PopupModal.jsx b/src/Components/Modal/PopupModal.jsx index 96af5af2..10321967 100644 --- a/src/Components/Modal/PopupModal.jsx +++ b/src/Components/Modal/PopupModal.jsx @@ -14,16 +14,37 @@ import {colors} from '../../Utils/colors'; import InputField from '../TextInput/InputField'; import Cancel from 'react-native-vector-icons/MaterialIcons'; import CustomButton from '../Button/CustomButton'; +import {dateFormatter, groupChatList} from '../../Data/GroupChatList'; +import {user_1} from '../../Data/ChatRoom'; +import {useNavigation} from '@react-navigation/native'; const PopupModal = props => { const [visible, setVisible] = React.useState(false); + const [groupName, setGroupName] = React.useState(''); + const [groupDescription, setGroupDescription] = React.useState(''); + const navigation = useNavigation(); const showModal = () => setVisible(true); const hideModal = () => setVisible(false); - const containerStyle = {backgroundColor: 'white', padding: 20}; + + const handleGroupCreation = item => { + const data = { + user_id: groupChatList.length + 1, + name: item, + last_msg: groupDescription, + modified_date: dateFormatter(user_1[0].createdAt), + profile_pic: + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAHUAngMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAAAQIDBAUGB//EADYQAAEEAQIFAwIEAwkBAAAAAAEAAgMRBBIhBRMxQVEGYXEikRQjMoEVUmIWNEKCobHR4fEH/8QAGQEAAwEBAQAAAAAAAAAAAAAAAAECAwQF/8QAIhEAAgICAgICAwAAAAAAAAAAAAECERIhAzETQQRRBWFx/9oADAMBAAIRAxEAPwCIQnvScIhe9Wn0SgNKY7QmhoPT7JC1tbKTTaA1MLIDGHdU12O0jorWlGlAiiMZsZOkE2nubQospWy1IWg9UqGUCyOR2kX8KvNhNNkNtawiYP0hNe3bogDmp8AjcbhU5MYtO4XVlhr6owfhNdgwytst02ixYJnImI9D0VaSCiV2H8KjBsbqGfhYIss29kZEvi0cgYd9qThiPcLG/sugPC2vNNaR7qfH4cYSCRY7ilWRPjOX/CPuiw2mSYj2mi0hdpLgsLgWg2Omyr5uM0NAI3pGQ/GciMc+FO3h7nC+i0ZYg09EjZKFJ2RijtH2wWQmmUBt1Z8KxJGXsLQaJUbcUkaXfUfKxcjqULImzDSXOG39O6UZMZAq7PYq1BA2Ikhu589FNojraJoSyl6Rp44e3RSa/UNm/wCqeSANVWPZOe0g/S2lBK2ZtE2QfATy+yXH6HtlYfI+QkP5mzC1PbjPezc0T5UkcDGg2UZ2HjrsWPHNAPLR/mBSyQxgfRIHe1brL4px3hvDyWSZMfMY3UWat/j5WV/b7g/JaQ2fmHq3l9EkndtlPkhjionSadO4UTsmNjvrcP2C53G9Z8OkOl036n6QTt+5vstueEzxCSMWxw2rurMl+h/47F6GZqcJ4H9JGrFnwQ29Tad8pcVskVhjhfugVs1udjtJBI+VRzuJxw22KnlMmyZhAWEMvzW6xJWOJJ6JpClLVGjJxt+j6YwNt91TdxNxJLgDaIsNhAL5G0e1p7YcRhLZI7Pm09EbKs2e14rlt+ypPns7NWnPDhEWzY+FCMeF3+FUiWmd+NPak5tG9xSqywyu+uJ5a4Hoo74htpbFt2rqsLOmkXxJH3dVdyn/AE1YIP7rGyncQcCOSAP6eqfhSFpvIY5tb/pO6YjYa3UaFX9kpaWGxSzJuIRk6YySO46KLEHPefzdJd2aSpZalW12bGzhZpefeuvVGTizPw+HSvj30ve0Cht0Hv8A7Lt4sUQyU2UA+2xXnHr3gj4M/JyBq5ejnA9GNBdVfJN0mqFJs4lzi5xc4kuO5JNkpLRSRWYi2V2/p/iOZJwqJpe/RH9DacaoLh13vpXFe7gMZ07Oc4gnxf8A0gN+iw7KkN63E/uonZbh0cVYkwHu7hVnYpaSCCqTRLyGSZ7/ADaruznKx+Da/wBvlNbwzX+g3SdolqTK5z31Sacskb2pZcHl9QVCcWxsCq0S8hoyq6p4zWgdyon4jx2TRhyn/wARoWz1kC09rUNCWaaLGj5kzqF/dcWR34j2sTtFrE4xxos4frwtTX8zSXEdBSp4Xq2VuK9s8DHzAUx4NX8hVjJqycop0zpHY7XAjSPsqjuGP1FzJ3MPs0LF4V6qkjyAziI5sLnbubsWf8heiQYMeRhtyMd3Nie3U147hZck5cfZtxRhydM5FuBlslDxlk1/M21yf/0+HiMeBjyukidim4pC3ZxJIcAR3H03svSZMdzZCCwivZYHrjhf8T9NZcLQ50sbebE1jbcXN7D536IhzpsufxZKLaPCUJ743sJD2OaQaIIqimLr7OAt8L4fPxTOixMcW+Q9ezQOpK9XxMH8Jhw4sQIZEwMBI3Nd15fwTjWZwTIdNgGNr3gNcXxh23jdeocB9TYHGMaFpmijznN/MgO31DrXnyona/hvxKL7exX45CryR12WvM5gv6m/dZ8zgUIHRS0t/lCNh0FJZHi9lEX+6pGVhJpPUWoCGg/SAPhK9/uonO91aJbBxCYXprnJhdumQ2ej5crcPHe8sJk020dj7/C5mD1C+bmR58LZY3dNO2lY8GVNEwtbI6j21EBROe1x2q/CwhwpdnRyfIbqtF7PmhljfHAS1urUA40Fj6tJ32UheAdxSgmGrdrrW6VHLKV7JNTT3W3wP1XxTgkXIxJ/yS4OMbxqb+w7X3XMF5YaKcJr6olBSVMUZtO0enY/rKLKLDlRiJzh9RZ0B+PCvR8UwJHf3lhJF77LyqPIoADorTZ9YFuIpcsviQ9aPQh+Q5EqezqPXuZwfN9L5kcsreY2nwECzzB0r5sj4K8XK3vU3EWzPGIzUeU63Od3NCq+5CwFrCGCqzHl5XySyYtpzXFjg5pIcDYINEFMQrszOo4Px2fImbBkOaDp2fdFxW43JeD+s/dcJgBrsyEPdTdQXU873VJWRJ0zUOS49SmGdZ/P90vOvuniTmWzLfdMMqqOkTDMihZFp0qYZPdVXSJvMVUTkSHLkB3CgyOJshrmA6j4UDZtQsStWRmy82dzg7UOgKTdFRVvZuY3GopfpkaW0CbJ2VtuXE4fPhcja0MSX8mi7cdPYKYuypR+jZfJE67coHSNb0NqiZW+U0zeFZnRfbkaT1Tzl6WuN7VazOaO6ZPNcTgB1HVJsdMpyvMkjnuNlxspiELI3BCEIAtcPaHZTC79LfqWu+YDusKFxa/Y1spy938xWkejOatmmMgeU5uQsmz5Sh7hvZVWTibHPBTTKFlc1/lLz3BFhgzT5qbzFm89yOc/yiwwIedQAAB82o3GzskSLJs2BSxOa02SokqQFsuAATOYK91XspFWROJI99nboml500moSsoEIQkAIQhADmGnBTBwJoFV0tppiaJ7RaiDyB5RzCnYUSJHJmso1+U7Ch6RxTS9ITaVhQiEIUjBCEIAEiEIAEIQgAQhCABCEIAEqEIARCEIAEIQmAIQhAH/2Q==', + msg_read: false, + active: true, + last_seen: '', + }; + groupChatList.unshift(data); + navigation.replace('Groups'); + }; return ( - <> + { Selected Contacts: {props.selectedCount} - + setGroupName(val)} + /> - + setGroupDescription(val)} + /> - + handleGroupCreation(groupName)} + /> - + Create Group - + ); }; const styles = StyleSheet.create({ container: { - width: '90%', - height: '50%', + width: moderateScale(300), + height: moderateScale(350), alignSelf: 'center', backgroundColor: colors.WHITE, padding: moderateScale(20), @@ -71,7 +113,7 @@ const styles = StyleSheet.create({ }, btnText: { - top: moderateScale(20), + top: moderateScale(25), marginRight: moderateScale(10), alignSelf: 'flex-end', }, diff --git a/src/Components/Video/ChatVideoPlayer.jsx b/src/Components/Video/ChatVideoPlayer.jsx new file mode 100644 index 00000000..74b83e1d --- /dev/null +++ b/src/Components/Video/ChatVideoPlayer.jsx @@ -0,0 +1,26 @@ +import {View, Text, StyleSheet} from 'react-native'; +import React from 'react'; +import VideoPlayer from 'react-native-video-controls'; +import {useNavigation, useRoute} from '@react-navigation/native'; + +const ChatVideoPlayer = () => { + const navigation = useNavigation(); + const route = useRoute(); + + return ( + + navigation.goBack()} + /> + + ); +}; + +export default ChatVideoPlayer; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, +}); diff --git a/src/Containers/Chats/Chat.jsx b/src/Containers/Chats/Chat.jsx index c3616aea..9df2fbbc 100644 --- a/src/Containers/Chats/Chat.jsx +++ b/src/Containers/Chats/Chat.jsx @@ -1,56 +1,92 @@ -import { - View, - Text, - StyleSheet, - TouchableOpacity, - Image, - Button, - Platform, -} from 'react-native'; -import React, {useCallback, useEffect, useLayoutEffect, useState} from 'react'; +import {View, Text, StyleSheet, TouchableOpacity, Image} from 'react-native'; +import React, {useCallback, useEffect, useState} from 'react'; import {colors} from '../../Utils/colors'; import {GiftedChat, Bubble, Send, InputToolbar} from 'react-native-gifted-chat'; import HeaderWithBackaction from '../../Components/Header/HeaderWithBackaction'; -import {user_1, user_2, user_3} from '../../Data/ChatRoom'; -import {useRoute} from '@react-navigation/native'; +import {useNavigation, useRoute} from '@react-navigation/native'; import {Icon} from 'react-native-paper'; import {moderateScale} from 'react-native-size-matters'; -import DocumentPicker from 'react-native-document-picker'; import FileTransfer from '../../Components/Chat/FileTransfer'; import ViewFile from '../../Components/Chat/ViewFile'; import Plus from 'react-native-vector-icons/AntDesign'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; import ChatLogic from '../../Functions/Chat/Chat'; +import Mic from 'react-native-vector-icons/Entypo'; +import {fonts} from '../../Utils/fonts'; +import PlayPause from 'react-native-vector-icons/AntDesign'; +import Delete from 'react-native-vector-icons/MaterialCommunityIcons'; +import Slider from '@react-native-community/slider'; +import TrackPlayer from 'react-native-track-player'; +import _ from 'lodash'; +import Wave from 'react-native-vector-icons/MaterialIcons'; +import Share from 'react-native-share'; const Chat = () => { const { messages, - setMessages, attachments, setAttachments, fileVisible, setFileVisible, - isMenuOpen, openMenu, closeMenu, onSend, pickDocument, - navigation, currentTime, + audioURL, + recordingActive, + time, + StartRecording, + DeleteRecording, + playingAudio, + setPlayingAudio, + Format, + position, + duration, + currentAudioId, + TogglePlayback, + onSliderValueChange, } = ChatLogic(); + const navigation = useNavigation(); + + useEffect(() => { + if (position == duration) { + TrackPlayer.seekTo(0); + TrackPlayer.pause(); + setPlayingAudio(false); + } + }, [position, duration]); + const route = useRoute(); const renderSend = props => { return ( - - - - - + {!recordingActive && ( + + + + )} + + ); @@ -64,7 +100,9 @@ const Chat = () => { style={{ ...styles.fileContainer, backgroundColor: - props.currentMessage.user._id === 2 ? '#2e64e5' : '#efefef', + props.currentMessage.user._id === 2 + ? colors.APP_PRIMARY + : '#efefef', borderBottomLeftRadius: props.currentMessage.user._id === 2 ? 15 : 5, borderBottomRightRadius: @@ -72,7 +110,7 @@ const Chat = () => { }} onPress={() => setFileVisible(true)}> setFileVisible(false)} /> @@ -98,13 +136,197 @@ const Chat = () => { ); + } else if (currentMessage.audio && currentMessage.audio.url) { + const audioDurationString = currentMessage.audio.duration || '0:00'; + const [minutes, seconds] = audioDurationString.split(':'); + const audioDuration = parseInt(minutes, 10) * 60 + parseInt(seconds, 10); + + return ( + + TogglePlayback(currentMessage)}> + + + + + + {currentAudioId === currentMessage._id && playingAudio + ? Format(position) + : currentMessage.audio.duration} + + + + + {currentMessage.text} + + + {currentTime} + + + + ); + } else if (currentMessage.image && currentMessage.image !== '') { + return ( + + + setFileVisible(true)}> + setFileVisible(false)} + isImage={true} + /> + + + + + {currentMessage.text} + + + {currentTime} + + + + + ); + } else if (currentMessage.video && currentMessage.video.url !== '') { + const handleVideoClick = async () => { + try { + const options = { + dialogTitle: 'Choose a video player', + }; + await Share.open( + {url: currentMessage.video.url, failOnCancel: false}, + options, + ); + } catch (error) { + console.error('Error sharing video:', error); + } + }; + + return ( + + + + navigation.navigate('ChatVideoPlayer', { + url: currentMessage.video.url, + }) + }> + + + + + {currentMessage.text} + + + {currentTime} + + + + + ); } + return ( { const modifiedProps = {...props}; if (props.text.length === 0 && attachments[0] !== undefined) { modifiedProps.text = ' '; + } else if (audioURL !== '') { + modifiedProps.text = ' '; } return ( - + + + + + + ); }; @@ -182,6 +416,29 @@ const Chat = () => { isFooter={true} /> )} + {attachment.type === 'video' && ( + + + setImagePath('')} + style={styles.buttonFooterChat}> + X + + + )} { const updatedAttachments = [...attachments]; @@ -195,10 +452,39 @@ const Chat = () => { ))} ); - } else { - return null; + } else if (recordingActive) { + return ( + + + + + Recording Voice {' '} {time} + + + {/* toggleRecording()}> + + */} + audioURL !== '' && DeleteRecording()}> + + + + ); } - }, [attachments]); + }, [attachments, time, recordingActive, audioURL]); return ( @@ -209,6 +495,7 @@ const Chat = () => { openMenu={openMenu} closeMenu={closeMenu} isChat={true} + profile_pic={route.params?.Item.profile_pic} /> { scrollToBottomComponent={scrollToBottomComponent} renderChatFooter={renderChatFooter} renderInputToolbar={renderInputToolbar} - messagesContainerStyle={{ - paddingBottom: - Platform.OS === 'ios' ? moderateScale(40) : moderateScale(15), - }} + messagesContainerStyle={styles.messagesContainer} + placeholder="Say something..." /> ); @@ -241,8 +526,7 @@ const styles = StyleSheet.create({ shareContainer: { flexDirection: 'row', marginBottom: 12, - marginRight: moderateScale(10), - width: moderateScale(70), + marginRight: moderateScale(-40), height: moderateScale(40), justifyContent: 'space-between', }, @@ -317,6 +601,13 @@ const styles = StyleSheet.create({ fileShare: { marginTop: moderateScale(16), + marginRight: moderateScale(10), + borderWidth: 1, + borderColor: colors.TRANSPARENT, + width: moderateScale(40), + height: moderateScale(30), + // justifyContent: 'center', + // alignItems: 'center', }, timeText: { @@ -329,15 +620,110 @@ const styles = StyleSheet.create({ input: { borderRadius: moderateScale(30), backgroundColor: colors.GRAY10, - marginBottom: moderateScale(10), borderTopWidth: 0, marginHorizontal: moderateScale(10), - marginRight: moderateScale(4), - alignItems: 'center', + marginRight: moderateScale(60), + width: moderateScale(290), + paddingBottom: moderateScale(5), }, messagesContainer: { paddingBottom: moderateScale(15), + fontFamily: fonts.MEDIUM, + }, + + microphone: { + borderWidth: 1, + justifyContent: 'center', + alignSelf: 'flex-end', + alignItems: 'center', + marginRight: moderateScale(10), + marginTop: moderateScale(-5), + backgroundColor: colors.APP_PRIMARY, + width: moderateScale(50), + height: moderateScale(52), + borderRadius: moderateScale(30), + }, + + inputWrapper: { + alignItems: 'center', + justifyContent: 'space-between', + bottom: moderateScale(10), + }, + + recordContainer: { + width: moderateScale(300), + height: moderateScale(50), + borderRadius: moderateScale(30), + backgroundColor: colors.GRAY10, + alignSelf: 'flex-start', + justifyContent: 'center', + marginHorizontal: moderateScale(10), + bottom: moderateScale(15), + paddingHorizontal: moderateScale(15), + }, + + audioFooter: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + elevation: 2, + borderBottomLeftRadius: moderateScale(10), + borderBottomRightRadius: moderateScale(10), + borderTopLeftRadius: 10, + borderTopRightRadius: 10, + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.18)', + height: moderateScale(60), + marginVertical: moderateScale(10), + marginHorizontal: moderateScale(10), + paddingTop: moderateScale(5), + paddingHorizontal: moderateScale(10), + }, + + recorderText: { + fontFamily: fonts.BOLD, + fontSize: moderateScale(14), + marginHorizontal: moderateScale(10), + }, + + audioInnerContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + + audioBubble: { + flexDirection: 'row', + padding: moderateScale(5), + justifyContent: 'center', + alignItems: 'center', + maxWidth: moderateScale(250), + }, + + audioText: { + fontFamily: fonts.BOLD, + color: colors.WHITE, + paddingHorizontal: moderateScale(10), + }, + + sliderContainer: { + paddingHorizontal: moderateScale(5), + marginBottom: moderateScale(10), + }, + + PlayPauseButton: { + paddingTop: moderateScale(1), + paddingHorizontal: moderateScale(1), + }, + + renderImage: { + borderRadius: moderateScale(10), + }, + + timeContainer: { + marginTop: moderateScale(10), + marginBottom: moderateScale(-5), + marginRight: moderateScale(-5), }, }); diff --git a/src/Containers/Chats/ChatList.jsx b/src/Containers/Chats/ChatList.jsx index 16766412..6b963ccf 100644 --- a/src/Containers/Chats/ChatList.jsx +++ b/src/Containers/Chats/ChatList.jsx @@ -1,5 +1,5 @@ import {View, FlatList, StyleSheet} from 'react-native'; -import React from 'react'; +import React, {useEffect} from 'react'; import {colors} from '../../Utils/colors'; import List from '../../Components/Chat/List'; import HeaderWithSearch from '../../Components/Header/HeaderWithSearch'; @@ -7,7 +7,7 @@ import {Searchbar} from 'react-native-paper'; import Cancel from 'react-native-vector-icons/MaterialIcons'; import {moderateScale} from 'react-native-size-matters'; import ChatListLogic from '../../Functions/Chat/ChatList'; -import {chatList} from '../../Data/ChatList'; +import TrackPlayer from 'react-native-track-player'; const ChatList = () => { const { @@ -23,6 +23,14 @@ const ChatList = () => { navigation, } = ChatListLogic(); + useEffect(() => { + const setupPlayer = async () => { + await TrackPlayer.setupPlayer(); + console.log('Player is initialized'); + }; + setupPlayer(); + }, []); + return ( { setSearch={setSearch} openDrawer={openDrawer} setOpenDrawer={setOpenDrawer} + isChatList={true} /> {search && ( { selected, setSelected, handleSearch, - HandleGroupSelection, + handleGroupSelection, navigation, } = ContactsLogic(); @@ -47,13 +47,13 @@ const Contacts = () => { onPress={() => { setSearch(false); setSearchQuery(''); - setFilteredContacts(chatList); + // setFilteredContacts(chatList); }} /> )} style={styles.searchBar} /> - + {isGroupSelection && ( @@ -64,18 +64,12 @@ const Contacts = () => { ( - HandleGroupSelection(item.item)} - onPress={() => - groupContact.length > 0 && HandleGroupSelection(item.item) - }> - - + )} /> diff --git a/src/Containers/Chats/GroupList.jsx b/src/Containers/Chats/GroupList.jsx index d006fbc3..ea5d324c 100644 --- a/src/Containers/Chats/GroupList.jsx +++ b/src/Containers/Chats/GroupList.jsx @@ -42,14 +42,25 @@ const GroupListUI = () => { style={styles.searchBar} /> )} - } - /> + + } + /> + navigation.navigate('Contacts')}> - + ); @@ -78,9 +89,10 @@ const styles = StyleSheet.create({ addIcon: { position: 'absolute', - alignSelf: 'flex-end', - right: moderateScale(20), - top: '85%', + width: moderateScale(55), + left: moderateScale(280), + bottom: moderateScale(50), + borderRadius: moderateScale(40), }, }); diff --git a/src/Containers/VideoPlayer/VideoPlayer.jsx b/src/Containers/VideoPlayer/VideoPlayer.jsx index aa453d13..229438e9 100644 --- a/src/Containers/VideoPlayer/VideoPlayer.jsx +++ b/src/Containers/VideoPlayer/VideoPlayer.jsx @@ -171,8 +171,9 @@ const VideoPlayer = () => { value={progress.currentTime} minimumValue={0} maximumValue={progress.seekableDuration} - minimumTrackTintColor="#FFFFFF" - maximumTrackTintColor="#fff" + minimumTrackTintColor={colors.WHITE} + maximumTrackTintColor={colors.WHITE} + thumbTintColor={colors.WHITE} onValueChange={x => { ref.current.seek(x); setProgress({...progress, currentTime: x}); diff --git a/src/Data/GroupChatList.js b/src/Data/GroupChatList.js index 8e235ee1..5628cbef 100644 --- a/src/Data/GroupChatList.js +++ b/src/Data/GroupChatList.js @@ -1,6 +1,6 @@ import {user_1, user_2, user_3} from './ChatRoom'; -const dateFormatter = timestamp => { +export const dateFormatter = timestamp => { const date = new Date(timestamp); const today = new Date(); // Get the current date. @@ -126,4 +126,48 @@ export const groupChatList = [ active: false, last_seen: '1m', }, + { + user_id: 8, + name: 'GEN AI', + last_msg: "Let's catch up soon!", + modified_date: dateFormatter(user_3[1].createdAt), + profile_pic: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ9EWJXakOBhOQvhl8k0GCRsakU9RxV2m-qiQ&usqp=CAU', + msg_read: true, + active: false, + last_seen: '1m', + }, + { + user_id: 8, + name: 'GEN AI', + last_msg: "Let's catch up soon!", + modified_date: dateFormatter(user_3[1].createdAt), + profile_pic: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ9EWJXakOBhOQvhl8k0GCRsakU9RxV2m-qiQ&usqp=CAU', + msg_read: true, + active: false, + last_seen: '1m', + }, + { + user_id: 8, + name: 'GEN AI', + last_msg: "Let's catch up soon!", + modified_date: dateFormatter(user_3[1].createdAt), + profile_pic: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ9EWJXakOBhOQvhl8k0GCRsakU9RxV2m-qiQ&usqp=CAU', + msg_read: true, + active: false, + last_seen: '1m', + }, + { + user_id: 8, + name: 'GEN AI', + last_msg: "Let's catch up soon!", + modified_date: dateFormatter(user_3[1].createdAt), + profile_pic: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ9EWJXakOBhOQvhl8k0GCRsakU9RxV2m-qiQ&usqp=CAU', + msg_read: true, + active: false, + last_seen: '1m', + }, ]; diff --git a/src/Functions/Chat/Chat.js b/src/Functions/Chat/Chat.js index 3c447687..051c302a 100644 --- a/src/Functions/Chat/Chat.js +++ b/src/Functions/Chat/Chat.js @@ -1,12 +1,30 @@ -import {useCallback, useState} from 'react'; +import {useCallback, useEffect, useRef, useState} from 'react'; import DocumentPicker from 'react-native-document-picker'; import {GiftedChat, InputToolbar} from 'react-native-gifted-chat'; +import AudioRecorderPlayer, { + AVEncoderAudioQualityIOSType, + AVEncodingOption, + AudioEncoderAndroidType, + AudioSourceAndroidType, + OutputFormatAndroidType, +} from 'react-native-audio-recorder-player'; +import {Dimensions, PanResponder, PermissionsAndroid} from 'react-native'; +import TrackPlayer, { + useProgress, + usePlaybackState, +} from 'react-native-track-player'; +import _ from 'lodash'; const ChatLogic = navigation => { const [messages, setMessages] = useState([]); const [attachments, setAttachments] = useState([]); const [fileVisible, setFileVisible] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); + const [audioURL, setAudioURL] = useState(''); + const [recordingActive, setRecordingActive] = useState(false); + const [time, setTime] = useState(null); + const [isRecordingPaused, setIsRecordingPaused] = useState(false); + const [playingAudio, setPlayingAudio] = useState(false); // current time retrieving const currentTime = new Date().toLocaleTimeString('en-US', { @@ -24,8 +42,9 @@ const ChatLogic = navigation => { }, []); const onSend = useCallback( - (messages = []) => { + async (messages = []) => { const [messageToSend] = messages; + // console.log(messages[0], ' TYU'); if (attachments.length > 0) { const newMessages = attachments.map((attachment, index) => ({ _id: messageToSend._id + index + 1, @@ -39,6 +58,9 @@ const ChatLogic = navigation => { file: { url: attachment.type === 'file' ? attachment.path : '', }, + video: { + url: attachment.type === 'video' ? attachment.path : '', + }, })); setMessages(previousMessages => @@ -47,6 +69,39 @@ const ChatLogic = navigation => { // Clear selected files after sending setAttachments([]); + // console.log(messages, ' MESS'); + } else if (audioURL !== '') { + try { + // Stop recording + await StopRecording(); + + // Create a new audio message + const newMessage = { + _id: messageToSend._id, + text: messages[0].text, + createdAt: new Date(), + user: { + _id: 2, + avatar: '', + }, + audio: { + url: audioURL, + duration: time, + }, + }; + + // Update messages state + setMessages(previousMessages => + GiftedChat.append(previousMessages, newMessage), + ); + + // Clear recording states + setTime(''); + setRecordingActive(false); + setAudioURL(''); + } catch (error) { + console.error('Error while stopping recording:', error); + } } else { // Send regular text message setMessages(previousMessages => @@ -54,7 +109,7 @@ const ChatLogic = navigation => { ); } }, - [attachments], + [attachments, audioURL, time], ); const pickDocument = async () => { @@ -77,7 +132,9 @@ const ChatLogic = navigation => { type: fileUri.includes('.png') || fileUri.includes('.jpg') ? 'image' - : 'file', + : fileUri.includes('pdf') + ? 'file' + : 'video', }, ]); } @@ -102,6 +159,183 @@ const ChatLogic = navigation => { return {modifiedProps}; }; + // voice message + const [currentPositionSec, setCurrentPositionSec] = useState(0); + const [currentDurationSec, setCurrentDurationSec] = useState(0); + const [currentAudioId, setCurrentAudioId] = useState(null); + + const {position, duration} = useProgress(0); + const {state} = usePlaybackState(); + + const intervalIdRef = useRef(null); + + const path = Platform.select({ + ios: undefined, + android: undefined, + }); + + const audioRecorderPlayer = useRef(new AudioRecorderPlayer()); + + useEffect(() => { + const initAudioRecorder = async () => { + try { + await audioRecorderPlayer.current.setSubscriptionDuration(0.1); // optional. Default is 0.5 + } catch (error) { + console.error('Error initializing AudioRecorderPlayer:', error); + } + }; + + initAudioRecorder(); + }, []); + + let startTime = null; + let updateInterval = 100; + + const StartTimer = () => { + // Clear the interval if it's already running + clearInterval(intervalIdRef.current); + + startTime = Date.now(); + intervalIdRef.current = setInterval(() => { + const elapsedTimeInSeconds = (Date.now() - startTime) / 1000; + const minutes = Math.floor(elapsedTimeInSeconds / 60); + const seconds = Math.floor(elapsedTimeInSeconds % 60); + const formattedTime = `${minutes}:${seconds.toString().padStart(2, '0')}`; + setTime(formattedTime); + }, updateInterval); + }; + + const StopTimer = () => { + console.log(' Stopped'); + clearInterval(intervalIdRef.current); + setTime(''); + }; + + const StartRecording = async () => { + if (Platform.OS === 'android') { + try { + const grants = await PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, + PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, + PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, + ]); + + console.log('write external storage', grants); + + if ( + grants['android.permission.WRITE_EXTERNAL_STORAGE'] === + PermissionsAndroid.RESULTS.GRANTED && + grants['android.permission.READ_EXTERNAL_STORAGE'] === + PermissionsAndroid.RESULTS.GRANTED && + grants['android.permission.RECORD_AUDIO'] === + PermissionsAndroid.RESULTS.GRANTED + ) { + console.log('permissions granted'); + } else { + console.log('All required permissions not granted'); + return; + } + } catch (err) { + console.warn(err); + return; + } + } + + const audioSet = { + AudioEncoderAndroid: AudioEncoderAndroidType.AAC, + AudioSourceAndroid: AudioSourceAndroidType.MIC, + AVEncoderAudioQualityKeyIOS: AVEncoderAudioQualityIOSType.high, + AVNumberOfChannelsKeyIOS: 2, + AVFormatIDKeyIOS: AVEncodingOption.aac, + OutputFormatAndroid: OutputFormatAndroidType.AAC_ADTS, + }; + + console.log('audioSet', audioSet); + + setRecordingActive(true); + StartTimer(); + const uri = await audioRecorderPlayer.current.startRecorder(path, audioSet); + + console.log(`uri: ${uri}`); + setAudioURL(uri); + }; + + const StopRecording = async () => { + if (recordingActive) { + try { + console.log('HH'); + await audioRecorderPlayer.current.stopRecorder(); + // Additional cleanup or operations after stopping the recorder + clearInterval(intervalIdRef.current); + } catch (error) { + console.error('Error stopping recorder:', error); + } + } + }; + + function DeleteRecording() { + StopRecording(); + setRecordingActive(false); + setAudioURL(''); + } + + const toggleRecording = async () => { + try { + if (!recordingActive) { + // Start recording + await StartRecording(); + } else if (isRecordingPaused) { + // Resume recording + await audioRecorderPlayer.current.resumeRecorder(); + setIsRecordingPaused(false); + StartTimer(); + } else { + // Pause recording + StopRecording(); + setIsRecordingPaused(true); + StopTimer(); + } + } catch (error) { + console.error(error); + } + }; + + const Format = seconds => { + let mins = (parseInt(seconds / 60) % 60).toString(); + let secs = Math.trunc(seconds % 60) + .toString() + .padStart(2, '0'); + return `${mins}:${secs}`; + }; + + const playAudio = message => { + TrackPlayer.add({ + id: message._id, + url: message.audio.url, + title: message.text, + }); + TrackPlayer.play(); + setPlayingAudio(true); + setCurrentAudioId(message._id); + }; + + const TogglePlayback = message => { + if (state === 'playing') { + TrackPlayer.pause(); + setPlayingAudio(false); + setCurrentAudioId(null); + } else { + playAudio(message); + } + }; + + const onSliderValueChange = useCallback( + _.debounce(value => { + TrackPlayer.seekTo(value); + }, 300), // Adjust the debounce delay as needed + [], + ); + return { messages, setMessages, @@ -118,6 +352,32 @@ const ChatLogic = navigation => { renderInputToolbar, renderInputToolbar, currentTime, + audioURL, + setAudioURL, + recordingActive, + setRecordingActive, + time, + setTime, + StartRecording, + StopRecording, + DeleteRecording, + isRecordingPaused, + toggleRecording, + playingAudio, + setPlayingAudio, + Format, + position, + duration, + state, + currentAudioId, + setCurrentAudioId, + currentDurationSec, + setCurrentDurationSec, + currentPositionSec, + setCurrentPositionSec, + playAudio, + TogglePlayback, + onSliderValueChange, }; }; diff --git a/src/Functions/Chat/Contacts.js b/src/Functions/Chat/Contacts.js index cbbc3b25..d4285674 100644 --- a/src/Functions/Chat/Contacts.js +++ b/src/Functions/Chat/Contacts.js @@ -19,7 +19,7 @@ const ContactsLogic = () => { setFilteredContacts(filteredMessages); }; - const HandleGroupSelection = item => { + const handleGroupSelection = item => { const updatedGroupContact = new Set(groupContact); if (updatedGroupContact.has(item)) { @@ -45,7 +45,7 @@ const ContactsLogic = () => { selected, setSelected, handleSearch, - HandleGroupSelection, + handleGroupSelection, }; }; diff --git a/src/Navigations/StackNavigator.jsx b/src/Navigations/StackNavigator.jsx index 1ba2132d..ed47419e 100644 --- a/src/Navigations/StackNavigator.jsx +++ b/src/Navigations/StackNavigator.jsx @@ -36,7 +36,7 @@ import NewEvent from '../Containers/EventCalendar/NewEvent'; import EventList from '../Containers/EventCalendar/EventList'; import BottomTabNavigator from './Tab/BottomTabNavigator'; import TopTabNavigator from './Tab/TopTabNavigator'; -import DrawerNavigator from './Drawer/DrawerNavigator'; +import ChatVideoPlayer from '../Components/Video/ChatVideoPlayer'; export const initialState = { isAudioEnabled: true, @@ -57,50 +57,46 @@ const StackNavigator = () => { const {drawer} = useAppContext(); return ( - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); };