1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497 |
- import React, { useState, useCallback, useEffect, useRef } from 'react';
- import {
- View,
- TouchableOpacity,
- Image,
- Text,
- FlatList,
- Dimensions,
- Alert,
- ScrollView,
- Linking,
- ActivityIndicator,
- AppState,
- AppStateStatus,
- TextInput,
- Platform
- } from 'react-native';
- import {
- GiftedChat,
- Bubble,
- InputToolbar,
- IMessage,
- Send,
- BubbleProps,
- Composer,
- TimeProps,
- MessageProps,
- Actions,
- isSameUser,
- isSameDay,
- SystemMessage,
- MessageText
- } from 'react-native-gifted-chat';
- import { MaterialCommunityIcons } from '@expo/vector-icons';
- import { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';
- import { Header, WarningModal } from 'src/components';
- import { Colors } from 'src/theme';
- import { useFocusEffect, useNavigation } from '@react-navigation/native';
- import { Audio } from 'expo-av';
- import ChatMessageBox from '../Components/ChatMessageBox';
- import ReplyMessageBar from '../Components/ReplyMessageBar';
- import { useSharedValue, withTiming } from 'react-native-reanimated';
- import { BlurView } from 'expo-blur';
- import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
- import Clipboard from '@react-native-clipboard/clipboard';
- import { trigger } from 'react-native-haptic-feedback';
- import ReactModal from 'react-native-modal';
- import {
- editMessageInStorage,
- sendMessageOffline,
- checkAndSendSavedMessages,
- storage,
- StoreType
- } from 'src/storage';
- import {
- usePostGetGroupChatQuery,
- usePostSendGroupMessageMutation,
- usePostReactToGroupMessageMutation,
- usePostGroupMessagesReadMutation,
- usePostDeleteGroupMessageMutation,
- usePostGetPinnedGroupMessageQuery,
- usePostSetPinGroupMessageMutation,
- usePostGetGroupSettingsQuery,
- usePostGetGroupMembersQuery,
- usePostEditGroupMessageMutation
- } from '@api/chat';
- import { CustomMessage, GroupMessage, Reaction } from '../types';
- import { API_HOST, APP_VERSION, WEBSOCKET_URL } from 'src/constants';
- import ReactionBar from '../Components/ReactionBar';
- import OptionsMenu from '../Components/OptionsMenu';
- import EmojiSelectorModal from '../Components/EmojiSelectorModal';
- import TypingIndicator from '../Components/TypingIndicator';
- import { styles } from '../ChatScreen/styles';
- import SendIcon from 'assets/icons/messages/send.svg';
- import { SheetManager } from 'react-native-actions-sheet';
- import { NAVIGATION_PAGES } from 'src/types';
- import { usePushNotification } from 'src/contexts/PushNotificationContext';
- import ReactionsListModal from '../Components/ReactionsListModal';
- import { dismissChatNotifications, isMessageEdited } from '../utils';
- import { useMessagesStore } from 'src/stores/unreadMessagesStore';
- import FileViewer from 'react-native-file-viewer';
- import * as FileSystem from 'expo-file-system';
- import ImageView from 'better-react-native-image-viewing';
- import * as MediaLibrary from 'expo-media-library';
- import BanIcon from 'assets/icons/messages/ban.svg';
- import AttachmentsModal from '../Components/AttachmentsModal';
- import RenderMessageVideo from '../Components/renderMessageVideo';
- import RenderMessageImage from '../Components/RenderMessageImage';
- import MessageLocation from '../Components/MessageLocation';
- import GroupIcon from 'assets/icons/messages/group-chat.svg';
- import { CACHED_ATTACHMENTS_DIR } from 'src/constants/constants';
- import GroupStatusModal from '../Components/GroupStatusModal';
- import PinIcon from 'assets/icons/messages/pin.svg';
- import MentionsList from '../Components/MentionsList';
- import { useConnection } from 'src/contexts/ConnectionContext';
- import moment from 'moment';
- const options = {
- enableVibrateFallback: true,
- ignoreAndroidSystemSettings: false
- };
- const reactionEmojis = ['👍', '❤️', '😂', '😮', '😭'];
- const GroupChatScreen = ({ route }: { route: any }) => {
- const token = storage.get('token', StoreType.STRING) as string;
- const [isConnected, setIsConnected] = useState<boolean>(true);
- const netInfo = useConnection();
- const {
- group_token,
- name,
- avatar,
- userType = 'normal'
- }: {
- group_token: string;
- name: string;
- avatar: string | null;
- userType: 'normal' | 'not_exist' | 'blocked';
- } = route.params;
- const groupName =
- userType === 'blocked'
- ? 'Account is blocked'
- : userType === 'not_exist'
- ? 'Account does not exist'
- : name;
- const currentUserId = storage.get('uid', StoreType.STRING) as number;
- const insets = useSafeAreaInsets();
- const [groupAvatar, setGroupAvatar] = useState<string | null>(null);
- const [messages, setMessages] = useState<CustomMessage[] | null>();
- const navigation = useNavigation();
- const [prevThenMessageId, setPrevThenMessageId] = useState<number>(-1);
- const {
- data: chatData,
- refetch: refetch,
- isFetching: isFetching
- } = usePostGetGroupChatQuery(token, group_token, 50, prevThenMessageId, true);
- const [canSeeMembers, setCanSeeMembers] = useState(false);
- const { data: pinData, refetch: refetchPinned } = usePostGetPinnedGroupMessageQuery(
- token,
- group_token,
- true
- );
- const { data } = usePostGetGroupSettingsQuery(token, group_token, true);
- const { data: members, refetch: refetchMembers } = usePostGetGroupMembersQuery(
- token,
- group_token,
- canSeeMembers
- );
- const { mutateAsync: sendMessage } = usePostSendGroupMessageMutation();
- const [isSearchingMessage, setIsSearchingMessage] = useState<number | null>(null);
- const swipeableRowRef = useRef<Swipeable | null>(null);
- const messageContainerRef = useRef<FlatList<IMessage> | null>(null);
- const [selectedMedia, setSelectedMedia] = useState<any>(null);
- const [pinned, setPinned] = useState<any>(null);
- const [replyMessage, setReplyMessage] = useState<CustomMessage | null>(null);
- const [modalInfo, setModalInfo] = useState({
- visible: false,
- type: 'confirm',
- message: '',
- action: () => {},
- buttonTitle: '',
- title: ''
- });
- const [selectedMessage, setSelectedMessage] = useState<BubbleProps<CustomMessage> | null>(null);
- const [emojiSelectorVisible, setEmojiSelectorVisible] = useState(false);
- const [messagePosition, setMessagePosition] = useState<{
- x: number;
- y: number;
- width: number;
- height: number;
- isMine: boolean;
- } | null>(null);
- const [isModalVisible, setIsModalVisible] = useState(false);
- const [unreadMessageIndex, setUnreadMessageIndex] = useState<number | null>(null);
- const { mutateAsync: markMessagesAsRead } = usePostGroupMessagesReadMutation();
- const { mutateAsync: deleteMessage } = usePostDeleteGroupMessageMutation();
- const { mutateAsync: reactToMessage } = usePostReactToGroupMessageMutation();
- const { mutateAsync: pinMessage } = usePostSetPinGroupMessageMutation();
- const { mutateAsync: editMessage } = usePostEditGroupMessageMutation();
- const [highlightedMessageId, setHighlightedMessageId] = useState<number | null>(null);
- const [isRerendering, setIsRerendering] = useState<boolean>(false);
- const [isTyping, setIsTyping] = useState<string | null>(null);
- const messageRefs = useRef<{ [key: string]: any }>({});
- const flatList = useRef<FlatList | null>(null);
- const scrollY = useSharedValue(0);
- const { isSubscribed } = usePushNotification();
- const [isLoadingEarlier, setIsLoadingEarlier] = useState(false);
- const [hasMoreMessages, setHasMoreMessages] = useState(true);
- const updateUnreadMessagesCount = useMessagesStore((state) => state.updateUnreadMessagesCount);
- const [insetsColor, setInsetsColor] = useState(Colors.FILL_LIGHT);
- const [text, setText] = useState('');
- const [mentionList, setMentionList] = useState<any>([]);
- const [showMentions, setShowMentions] = useState(false);
- const [inputHeight, setInputHeight] = useState(45);
- const [editingMessage, setEditingMessage] = useState<CustomMessage | null>(null);
- const [cacheKey, setCacheKey] = useState(Date.now());
- const appState = useRef(AppState.currentState);
- const textInputRef = useRef<TextInput>(null);
- const socket = useRef<WebSocket | null>(null);
- const scrollViewRef = useRef<ScrollView>(null);
- useEffect(() => {
- if (isModalVisible) {
- setTimeout(() => {
- scrollViewRef.current?.scrollToEnd({ animated: false });
- }, 50);
- }
- }, [isModalVisible]);
- useFocusEffect(
- useCallback(() => {
- setCacheKey(Date.now());
- }, [navigation])
- );
- useEffect(() => {
- if (netInfo && netInfo.isConnected !== null) {
- setIsConnected(netInfo.isConnected);
- if (netInfo.isConnected) {
- checkAndSendSavedMessages();
- refetch();
- }
- }
- }, [netInfo]);
- const closeModal = () => {
- setModalInfo({ ...modalInfo, visible: false });
- };
- useEffect(() => {
- if (!Audio || !Audio.setAudioModeAsync) {
- return;
- }
- Audio.setAudioModeAsync({
- allowsRecordingIOS: false,
- staysActiveInBackground: false,
- playsInSilentModeIOS: true,
- shouldDuckAndroid: true,
- playThroughEarpieceAndroid: false
- });
- }, []);
- useEffect(() => {
- if (pinData && pinData?.message) {
- setPinned(pinData.message);
- }
- }, [pinData]);
- const onSendMedia = useCallback(
- async (files: { uri: string; type: 'image' | 'video' }[]) => {
- for (const file of files) {
- const tempMessage: CustomMessage = {
- _id: Date.now() + Math.random(),
- text: '',
- createdAt: new Date(),
- user: { _id: +currentUserId, name: 'Me', avatar: null as never },
- reactions: {},
- deleted: false,
- attachment: {
- id: -1,
- filename: file.type,
- filetype: file.type,
- attachment_link: file.uri,
- attachment_full_url: file.uri
- },
- pending: true,
- isSending: true,
- edited: false,
- image: file.type === 'image' ? file.uri : undefined,
- video: file.type === 'video' ? file.uri : undefined
- };
- if (replyMessage) {
- tempMessage.replyMessage = {
- text: replyMessage.text,
- id: replyMessage._id,
- name:
- replyMessage.user._id !== +currentUserId ? (replyMessage.user.name as string) : 'Me'
- };
- }
- setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [tempMessage]));
- const messageData = {
- token,
- to_group_token: group_token,
- text: '',
- reply_to_id: replyMessage ? (replyMessage._id as number) : -1,
- attachment: {
- uri: file.uri,
- type: file.type,
- name: file.uri.split('/').pop()
- }
- };
- const res = await sendMessage(messageData, {
- onSuccess: (res) => {
- const { attachment, message_id } = res;
- const newMessage = {
- _id: message_id,
- text: '',
- attachment,
- replyMessage: { ...tempMessage.replyMessage, sender: replyMessage?.user?._id },
- image: file.type === 'image' ? API_HOST + attachment.attachment_full_url : undefined,
- video: file.type === 'video' ? file.uri : undefined
- };
- setMessages((previousMessages) =>
- (previousMessages ?? []).map((msg) =>
- msg._id === tempMessage._id
- ? {
- ...msg,
- _id: res.message_id,
- isSending: false
- }
- : msg
- )
- );
- sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
- }
- });
- clearReplyMessage();
- }
- },
- [replyMessage]
- );
- const onSendLocation = useCallback(
- async (coords: { latitude: number; longitude: number }) => {
- const tempMessage: CustomMessage = {
- _id: Date.now() + Math.random(),
- text: '',
- createdAt: new Date(),
- user: { _id: +currentUserId, name: 'Me', avatar: null as never },
- pending: true,
- deleted: false,
- edited: false,
- reactions: {},
- attachment: {
- id: -1,
- filename: 'location.json',
- filetype: 'nomadmania/location',
- lat: coords.latitude,
- lng: coords.longitude
- }
- };
- if (replyMessage) {
- tempMessage.replyMessage = {
- text: replyMessage.text,
- id: replyMessage._id,
- name: replyMessage.user._id !== +currentUserId ? (replyMessage.user.name as string) : 'Me'
- };
- }
- setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [tempMessage]));
- const locationData = JSON.stringify({ lat: coords.latitude, lng: coords.longitude });
- const fileUri = FileSystem.documentDirectory + 'location.json';
- await FileSystem.writeAsStringAsync(fileUri, locationData);
- const locationFile = {
- uri: fileUri,
- type: 'application/json',
- name: 'location.json'
- };
- const messageData = {
- token,
- to_group_token: group_token,
- text: tempMessage.text,
- reply_to_id: replyMessage ? (replyMessage._id as number) : -1,
- attachment: locationFile
- };
- sendMessage(messageData, {
- onSuccess: async (res) => {
- const { attachment, message_id } = res;
- const newMessage = {
- _id: message_id,
- text: '',
- attachment,
- replyMessage: { ...tempMessage.replyMessage, sender: replyMessage?.user?._id }
- };
- setMessages((previousMessages) =>
- (previousMessages ?? []).map((msg) =>
- msg._id === tempMessage._id ? { ...msg, _id: res.message_id } : msg
- )
- );
- sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
- await FileSystem.deleteAsync(fileUri);
- },
- onError: async (err) => {
- await FileSystem.deleteAsync(fileUri);
- }
- });
- clearReplyMessage();
- },
- [replyMessage]
- );
- const onSendFile = useCallback(
- (files: { uri: string; type: string; name?: string }[]) => {
- const newMsgs = files.map((file) => {
- const msg: CustomMessage = {
- _id: Date.now() + Math.random(),
- text: '',
- createdAt: new Date(),
- user: { _id: +currentUserId, name: 'Me', avatar: null as never },
- deleted: false,
- reactions: {},
- isSending: true,
- edited: false,
- attachment: {
- id: -1,
- filename: file.name ?? 'File',
- filetype: file.type,
- attachment_link: file.uri
- }
- };
- if (replyMessage) {
- msg.replyMessage = {
- text: replyMessage.text,
- id: replyMessage._id,
- name:
- replyMessage.user._id !== +currentUserId ? (replyMessage.user.name as string) : 'Me'
- };
- }
- if (file.type.includes('image')) {
- msg.image = file.uri;
- } else if (file.type.includes('video')) {
- msg.video = file.uri;
- }
- setMessages((previousMessages) => GiftedChat.append(previousMessages ?? [], [msg]));
- const messageData = {
- token,
- to_group_token: group_token,
- text: '',
- reply_to_id: replyMessage ? (replyMessage._id as number) : -1,
- attachment: {
- uri: file.uri,
- type: file.type,
- name: file.name || file.uri.split('/').pop()
- }
- };
- sendMessage(messageData, {
- onSuccess: (res) => {
- const { attachment, message_id } = res;
- const newMessage = {
- _id: message_id,
- text: '',
- attachment,
- replyMessage: { ...msg.replyMessage, sender: replyMessage?.user?._id },
- image: file.type === 'image' ? API_HOST + attachment.attachment_full_url : undefined,
- video: file.type === 'video' ? file.uri : undefined
- };
- setMessages((previousMessages) =>
- (previousMessages ?? []).map((prevMsg) =>
- prevMsg._id === msg._id
- ? {
- ...prevMsg,
- _id: res.message_id,
- attachment: res.attachment,
- isSending: false,
- image:
- res.attachment?.attachment_small_url && file.type?.startsWith('image')
- ? API_HOST + res.attachment.attachment_small_url
- : undefined,
- video:
- res.attachment?.attachment_link && file.type?.startsWith('video')
- ? API_HOST + res.attachment.attachment_link
- : undefined
- }
- : prevMsg
- )
- );
- sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
- }
- });
- return msg;
- });
- clearReplyMessage();
- },
- [replyMessage]
- );
- async function openFileInApp(uri: string, fileName: string) {
- try {
- const dirExist = await FileSystem.getInfoAsync(CACHED_ATTACHMENTS_DIR);
- if (!dirExist.exists) {
- await FileSystem.makeDirectoryAsync(CACHED_ATTACHMENTS_DIR, { intermediates: true });
- }
- const fileUri = `${CACHED_ATTACHMENTS_DIR}${fileName}`;
- const fileExists = await FileSystem.getInfoAsync(fileUri);
- if (fileExists.exists && fileExists.size > 1024) {
- await FileViewer.open(fileUri, {
- showOpenWithDialog: true,
- showAppsSuggestions: true
- });
- return;
- }
- const { uri: localUri } = await FileSystem.downloadAsync(uri, fileUri, {
- headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS }
- });
- await FileViewer.open(localUri, {
- showOpenWithDialog: true,
- showAppsSuggestions: true
- });
- } catch (err) {
- console.warn('openFileInApp error:', err);
- Alert.alert('Cannot open file', 'No application found to open this file.');
- }
- }
- async function downloadFileToDevice(currentMessage: CustomMessage) {
- if (!currentMessage.image && !currentMessage.video) {
- return;
- }
- const fileUrl = currentMessage.video
- ? currentMessage.video
- : API_HOST + currentMessage.attachment?.attachment_full_url;
- const fileType = currentMessage.attachment?.filetype || 'application/octet-stream';
- let fileExt = fileType.split('/').pop() || (currentMessage.video ? 'mp4' : 'jpg');
- if (Platform.OS === 'android' && fileType === 'video/quicktime') {
- fileExt = 'mp4';
- }
- const fileName = currentMessage.attachment?.filename?.split('.')[0] || 'file';
- const fileUri = `${FileSystem.cacheDirectory}${fileName}.${fileExt}`;
- try {
- const { status } = await MediaLibrary.requestPermissionsAsync();
- if (status !== 'granted') {
- return;
- }
- const downloadOptions = {
- headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS }
- };
- const { uri } = await FileSystem.downloadAsync(fileUrl, fileUri, downloadOptions);
- await MediaLibrary.createAssetAsync(uri);
- Alert.alert(
- 'Success',
- `${fileType.startsWith('video') ? 'Video' : 'Image'} saved to gallery.`
- );
- } catch (error) {
- Alert.alert('Error', 'Failed to download the file.');
- }
- }
- const renderMessageFile = (props: BubbleProps<CustomMessage>) => {
- const { currentMessage } = props;
- const leftMessage = currentMessage?.user?._id !== +currentUserId;
- if (!currentMessage?.attachment) return null;
- const { attachment_link, filename } = currentMessage.attachment;
- const fileName = filename ?? 'Attachment';
- const uri = API_HOST + attachment_link;
- return (
- <TouchableOpacity
- style={[
- styles.fileContainer,
- { backgroundColor: leftMessage ? 'rgba(15, 63, 79, 0.2)' : 'rgba(244, 244, 244, 0.2)' }
- ]}
- onPress={() => {
- openFileInApp(uri, fileName);
- }}
- onLongPress={() => handleLongPress(currentMessage, props)}
- disabled={currentMessage?.isSending}
- >
- {currentMessage?.isSending ? (
- <ActivityIndicator
- size="small"
- color={leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT}
- />
- ) : (
- <MaterialCommunityIcons
- name="file"
- size={32}
- color={leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT}
- />
- )}
- <Text
- style={[
- styles.fileNameText,
- { color: leftMessage ? Colors.DARK_BLUE : Colors.FILL_LIGHT }
- ]}
- >
- {fileName}
- </Text>
- </TouchableOpacity>
- );
- };
- const renderMessageLocation = (props: BubbleProps<CustomMessage>) => {
- const { currentMessage } = props;
- if (!currentMessage?.attachment) return null;
- const { lat, lng } = currentMessage.attachment;
- if (!lat || !lng) return null;
- return (
- <View
- style={[
- {
- alignItems: 'center',
- borderRadius: 8,
- marginVertical: 6,
- marginHorizontal: 6,
- width: 220
- }
- ]}
- >
- <MessageLocation props={props} lat={lat} lng={lng} onLongPress={handleLongPress} />
- </View>
- );
- };
- const onShareLiveLocation = useCallback(() => {}, []);
- useEffect(() => {
- if (data && data.settings) {
- setCanSeeMembers(data.settings.members_can_see_members === 1 || data.settings.admin === 1);
- }
- }, [data]);
- useEffect(() => {
- let unsubscribe: any;
- const setupNotificationHandler = async () => {
- unsubscribe = await dismissChatNotifications(
- group_token,
- isSubscribed,
- setModalInfo,
- navigation
- );
- };
- setupNotificationHandler();
- return () => {
- if (unsubscribe) unsubscribe();
- updateUnreadMessagesCount();
- };
- }, [group_token]);
- useEffect(() => {
- socket.current = new WebSocket(WEBSOCKET_URL);
- socket.current.onopen = () => {
- socket.current?.send(JSON.stringify({ token }));
- };
- socket.current.onmessage = (event) => {
- const data = JSON.parse(event.data);
- handleWebSocketMessage(data);
- };
- socket.current.onclose = () => {
- console.log('WebSocket connection closed chat screen');
- };
- return () => {
- if (socket.current) {
- socket.current.close();
- socket.current = null;
- }
- };
- }, [token]);
- useEffect(() => {
- const handleAppStateChange = async (nextAppState: AppStateStatus) => {
- const prevState = appState.current;
- appState.current = nextAppState;
- if (prevState.match(/inactive|background/) && nextAppState === 'active') {
- refetch();
- if (!socket.current || socket.current.readyState === WebSocket.CLOSED) {
- socket.current = new WebSocket(WEBSOCKET_URL);
- socket.current.onopen = () => {
- socket.current?.send(JSON.stringify({ token }));
- };
- socket.current.onmessage = (event) => {
- const data = JSON.parse(event.data);
- handleWebSocketMessage(data);
- };
- }
- await dismissChatNotifications(group_token, isSubscribed, setModalInfo, navigation);
- }
- };
- const subscription = AppState.addEventListener('change', handleAppStateChange);
- return () => {
- subscription.remove();
- if (socket.current) {
- socket.current.close();
- socket.current = null;
- }
- };
- }, [token]);
- const handleWebSocketMessage = (data: any) => {
- switch (data.action) {
- case 'new_message':
- if (data.group_token === group_token && data.message) {
- const newMessage = mapApiMessageToGiftedMessage(data.message);
- setMessages((previousMessages) => {
- const messageExists =
- previousMessages && previousMessages.some((msg) => msg._id === newMessage._id);
- if (!messageExists) {
- return GiftedChat.append(previousMessages ?? [], [
- {
- ...newMessage,
- user: {
- _id: data.uid,
- name: data.name,
- avatar: API_HOST + data.avatar
- }
- }
- ]);
- }
- return previousMessages;
- });
- }
- break;
- case 'new_reaction':
- if (data.group_token === group_token && data.reaction) {
- // todo: name
- updateMessageWithReaction(data.reaction);
- }
- break;
- case 'unreact':
- if (data.group_token === group_token && data.unreacted_message_id) {
- // todo: name
- removeReactionFromMessage(data.unreacted_message_id);
- }
- break;
- case 'delete_message':
- if (data.group_token === group_token && data.deleted_message_id) {
- removeDeletedMessage(data.deleted_message_id);
- }
- break;
- case 'is_typing':
- if (data.group_token === group_token && data.uid !== +currentUserId) {
- setIsTyping(data.name);
- }
- break;
- case 'stopped_typing':
- if (data.group_token === group_token) {
- setIsTyping(null);
- }
- break;
- case 'messages_read':
- if (data.group_token === group_token && data.read_messages_ids) {
- setMessages(
- (prevMessages) =>
- prevMessages?.map((msg) => {
- if (data.read_messages_ids.includes(msg._id)) {
- return { ...msg, received: true };
- }
- return msg;
- }) ?? []
- );
- }
- break;
- case 'messages_received':
- if (data.group_token === group_token && data.received_messages_ids) {
- setMessages(
- (prevMessages) =>
- prevMessages?.map((msg) => {
- if (data.received_messages_ids.includes(msg._id)) {
- return { ...msg, sent: true };
- }
- return msg;
- }) ?? []
- );
- }
- break;
- case 'edited_message':
- if (data.group_token === group_token && data.message) {
- setMessages(
- (prevMessages) =>
- prevMessages?.map((msg) => {
- if (msg._id === data.message.id) {
- return { ...msg, text: data.message.text, edited: true };
- }
- return msg;
- }) ?? []
- );
- }
- break;
- default:
- break;
- }
- };
- const updateMessageWithReaction = (reactionData: any) => {
- setMessages(
- (prevMessages) =>
- prevMessages?.map((msg) => {
- if (msg._id === reactionData.message_id) {
- const updatedReactions = [
- ...(Array.isArray(msg.reactions)
- ? msg.reactions?.filter((r: any) => r.uid !== reactionData.uid)
- : []),
- reactionData
- ];
- return { ...msg, reactions: updatedReactions };
- }
- return msg;
- }) ?? []
- );
- };
- const removeReactionFromMessage = (messageId: number) => {
- setMessages(
- (prevMessages) =>
- prevMessages?.map((msg) => {
- if (msg._id === messageId) {
- const updatedReactions = Array.isArray(msg.reactions)
- ? msg.reactions?.filter((r: any) => r.uid === +currentUserId)
- : [];
- return { ...msg, reactions: updatedReactions };
- }
- return msg;
- }) ?? []
- );
- };
- const removeDeletedMessage = (messageId: number) => {
- setMessages(
- (prevMessages) =>
- prevMessages?.map((msg) => {
- if (msg._id === messageId) {
- return {
- ...msg,
- deleted: true,
- text: 'This message was deleted',
- pending: false,
- sent: false,
- received: false
- };
- }
- return msg;
- }) ?? []
- );
- };
- useEffect(() => {
- const pingInterval = setInterval(() => {
- if (socket.current && socket.current.readyState === WebSocket.OPEN) {
- socket.current.send(
- JSON.stringify({ action: 'ping', conversation_with_group: group_token })
- );
- } else {
- socket.current = new WebSocket(WEBSOCKET_URL);
- socket.current.onopen = () => {
- socket.current?.send(JSON.stringify({ token }));
- };
- socket.current.onmessage = (event) => {
- const data = JSON.parse(event.data);
- handleWebSocketMessage(data);
- };
- return () => {
- if (socket.current) {
- socket.current.close();
- socket.current = null;
- }
- };
- }
- }, 50000);
- return () => clearInterval(pingInterval);
- }, []);
- const sendWebSocketMessage = (
- action: string,
- message: CustomMessage | null = null,
- reaction: string | null = null,
- readMessagesIds: number[] | null = null
- ) => {
- if (socket.current && socket.current.readyState === WebSocket.OPEN) {
- const data: any = {
- action,
- conversation_with_group: group_token
- };
- if (action === 'new_message' && message) {
- data.message = {
- id: message._id,
- text: message.text,
- sender: +currentUserId,
- sent_datetime: new Date().toISOString().replace('T', ' ').substring(0, 19),
- reply_to_id: message.replyMessage?.id ?? -1,
- reply_to: message.replyMessage ?? null,
- reactions: message.reactions ?? '{}',
- status: 2,
- attachement: message.attachment ? message.attachment : -1
- };
- }
- if (action === 'new_reaction' && message && reaction) {
- data.reaction = {
- message_id: message._id,
- reaction,
- uid: +currentUserId,
- datetime: new Date().toISOString()
- };
- }
- if (action === 'unreact' && message) {
- data.message_id = message._id;
- }
- if (action === 'delete_message' && message) {
- data.message_id = message._id;
- }
- if (action === 'messages_read' && readMessagesIds) {
- data.messages_ids = readMessagesIds;
- }
- if (action === 'edited_message' && message) {
- data.message = {
- id: message._id,
- text: message.text
- };
- }
- socket.current.send(JSON.stringify(data));
- }
- };
- const handleTyping = (isTyping: boolean) => {
- if (isTyping) {
- sendWebSocketMessage('is_typing');
- } else {
- sendWebSocketMessage('stopped_typing');
- }
- };
- const mapApiMessageToGiftedMessage = (message: GroupMessage): CustomMessage => {
- return {
- _id: message.id,
- text: message.text,
- createdAt: new Date(message.sent_datetime + 'Z'),
- user: {
- _id: message.sender,
- name: message.sender !== +currentUserId ? message.sender_name : 'Me',
- avatar:
- message.sender !== +currentUserId && message.sender_avatar
- ? API_HOST + message.sender_avatar
- : message.sender === +currentUserId
- ? (null as never)
- : undefined
- },
- replyMessage:
- message.reply_to_id && message.reply_to_id > 0
- ? {
- text: message.reply_to.text,
- id: message.reply_to.id,
- name:
- message.reply_to.sender !== +currentUserId ? message.reply_to?.sender_name : 'Me'
- }
- : null,
- reactions: JSON.parse(message.reactions || '{}'),
- attachment: message.attachement !== -1 ? message.attachement : null,
- pending: message.status === 1,
- sent: message.status === 2,
- received: message.status === 3,
- deleted: message.status === 4,
- edited: isMessageEdited(message.edits),
- isSending: message?.isSending ? message.isSending : false,
- video:
- message.attachement !== -1 && message.attachement?.filetype?.startsWith('video')
- ? API_HOST + message.attachement?.attachment_link
- : null,
- image:
- message.attachement !== -1 && message.attachement?.filetype?.startsWith('image')
- ? API_HOST + message.attachement?.attachment_small_url
- : null,
- system: message.sender === -1
- };
- };
- useFocusEffect(
- useCallback(() => {
- refetch();
- }, [])
- );
- useFocusEffect(
- useCallback(() => {
- if (chatData?.groupAvatar) {
- setGroupAvatar(API_HOST + chatData.groupAvatar);
- }
- if (chatData?.messages) {
- const mappedMessages = chatData.messages.map(mapApiMessageToGiftedMessage);
- if (unreadMessageIndex === null && !isFetching) {
- const firstUnreadIndex = mappedMessages.findLastIndex(
- (msg) => !msg.received && !msg?.deleted && msg.user._id !== +currentUserId
- );
- if (firstUnreadIndex !== -1) {
- setUnreadMessageIndex(firstUnreadIndex);
- const unreadMarker: any = {
- _id: 'unreadMarker',
- text: 'Unread messages',
- system: true
- };
- mappedMessages.splice(firstUnreadIndex + 1, 0, unreadMarker);
- setTimeout(() => {
- if (flatList.current) {
- flatList.current.scrollToIndex({
- index: firstUnreadIndex,
- animated: true,
- viewPosition: 0.5
- });
- }
- }, 500);
- } else {
- setUnreadMessageIndex(0);
- }
- }
- setMessages((previousMessages) => {
- const newMessages = mappedMessages.filter(
- (newMsg) => !previousMessages?.some((oldMsg) => oldMsg._id === newMsg._id)
- );
- let unsentMapped = [];
- if (prevThenMessageId === -1) {
- const unsentMessages = storage.get(`unsent_${group_token}`, StoreType.STRING)
- ? JSON.parse(storage.get(`unsent_${group_token}`, StoreType.STRING) as string)
- : [];
- unsentMapped = unsentMessages.map(mapApiMessageToGiftedMessage);
- }
- return prevThenMessageId !== -1 && previousMessages
- ? GiftedChat.prepend(previousMessages, newMessages)
- : [...unsentMapped, ...mappedMessages];
- });
- if (mappedMessages.length < 50) {
- setHasMoreMessages(false);
- }
- if (mappedMessages.length === 0 && !modalInfo.visible) {
- setTimeout(() => {
- textInputRef.current?.focus();
- }, 500);
- }
- setIsLoadingEarlier(false);
- }
- }, [chatData])
- );
- useEffect(() => {
- if (messages) {
- if (isSearchingMessage) {
- const messageIndex = messages.findIndex((msg) => msg._id === isSearchingMessage);
- if (messageIndex !== -1 && flatList.current) {
- setIsSearchingMessage(null);
- }
- scrollToMessage(isSearchingMessage);
- }
- }
- }, [messages]);
- useEffect(() => {
- if (messages?.length === 0 && !modalInfo.visible) {
- setTimeout(() => {
- textInputRef.current?.focus();
- }, 500);
- }
- }, [modalInfo]);
- const loadEarlierMessages = async () => {
- if (!hasMoreMessages || (isLoadingEarlier && !isSearchingMessage) || !messages) return;
- setIsLoadingEarlier(true);
- const previousMessageId = messages[messages.length - 1]._id;
- setPrevThenMessageId(previousMessageId);
- };
- const sentToServer = useRef<Set<number>>(new Set());
- const handleViewableItemsChanged = ({ viewableItems }: { viewableItems: any[] }) => {
- const newViewableUnreadMessages = viewableItems
- .filter(
- (item) =>
- !item.item.received &&
- !item.item.deleted &&
- item.item._id !== 'unreadMarker' &&
- item.item.user._id !== +currentUserId &&
- !sentToServer.current.has(item.item._id)
- )
- .map((item) => item.item._id);
- if (newViewableUnreadMessages.length > 0) {
- markMessagesAsRead(
- {
- token,
- group_token,
- messages_id: newViewableUnreadMessages
- },
- {
- onSuccess: (res) => {
- newViewableUnreadMessages.forEach((id) => sentToServer.current.add(id));
- sendWebSocketMessage('messages_read', null, null, newViewableUnreadMessages);
- }
- }
- );
- }
- };
- const renderSystemMessage = (props: any) => {
- if (props.currentMessage._id === 'unreadMarker') {
- return (
- <View style={styles.unreadMessagesContainer}>
- <Text style={styles.unreadMessagesText}>{props.currentMessage.text}</Text>
- </View>
- );
- } else if (props.currentMessage.user._id === -1) {
- return (
- <SystemMessage
- currentMessage={props.currentMessage}
- containerStyle={{
- marginTop: 0,
- marginBottom: 0,
- paddingVertical: 2,
- paddingHorizontal: 12
- }}
- wrapperStyle={{
- backgroundColor: Colors.FILL_LIGHT,
- paddingHorizontal: 6,
- paddingVertical: 4,
- borderRadius: 10
- }}
- textStyle={{
- color: Colors.DARK_BLUE,
- fontStyle: 'italic',
- fontSize: 12,
- textAlign: 'center'
- }}
- />
- );
- }
- return null;
- };
- const clearReplyMessage = () => setReplyMessage(null);
- const clearEditMessage = () => {
- setEditingMessage(null);
- setText('');
- };
- const handleLongPress = (message: CustomMessage, props: BubbleProps<CustomMessage>) => {
- const messageRef = messageRefs.current[message._id];
- setSelectedMessage(props);
- trigger('impactMedium', options);
- const isMine = message.user._id === +currentUserId;
- if (messageRef) {
- messageRef.measureInWindow((x: number, y: number, width: number, height: number) => {
- const screenHeight = Dimensions.get('window').height;
- const spaceAbove = y - insets.top;
- const spaceBelow = screenHeight - (y + height) - insets.bottom * 2;
- let finalY = y;
- scrollY.value = 0;
- if (isNaN(y) || isNaN(height)) {
- console.error("Invalid measurement values for 'y' or 'height'", { y, height });
- return;
- }
- const maxSpaceBelow = isMine ? 280 : 200;
- if (spaceBelow < maxSpaceBelow) {
- const extraShift = maxSpaceBelow - spaceBelow;
- finalY -= extraShift;
- }
- if (spaceAbove < 50) {
- const extraShift = 50 - spaceAbove;
- finalY += extraShift;
- }
- if (spaceBelow < 220 || spaceAbove < 50) {
- const targetY = screenHeight / 2 - height / 2;
- scrollY.value = withTiming(finalY - finalY);
- }
- if (height > Dimensions.get('window').height - 200) {
- finalY = 100;
- }
- finalY = isNaN(finalY) ? 0 : finalY;
- setMessagePosition({ x, y: finalY, width, height, isMine });
- setIsModalVisible(true);
- });
- }
- };
- const openEmojiSelector = () => {
- SheetManager.show('emoji-selector');
- trigger('impactLight', options);
- };
- const closeEmojiSelector = () => {
- SheetManager.hide('emoji-selector');
- };
- const handleReactionPress = (emoji: string, messageId: number) => {
- addReaction(messageId, emoji);
- };
- const handleDeleteMessage = (messageId: number) => {
- deleteMessage(
- {
- token,
- message_id: messageId,
- group_token
- },
- {
- onSuccess: () => {
- setMessages(
- (prevMessages) =>
- prevMessages?.map((msg) => {
- if (msg._id === messageId) {
- return {
- ...msg,
- deleted: true,
- text: 'This message was deleted',
- pending: false,
- sent: false,
- received: false,
- attachment: null,
- image: undefined,
- video: undefined
- };
- }
- return msg;
- }) ?? []
- );
- const messageToDelete = messages?.find((msg) => msg._id === messageId);
- if (messageToDelete) {
- sendWebSocketMessage('delete_message', messageToDelete, null, null);
- }
- }
- }
- );
- };
- const handlePinMessage = (messageId: number, pin: 0 | 1) => {
- pinMessage(
- {
- token,
- message_id: messageId,
- group_token,
- pin
- },
- {
- onSuccess: () => {
- refetchPinned();
- if (pin === 0) {
- setPinned(null);
- }
- }
- }
- );
- };
- const handleOptionPress = (option: string) => {
- if (!selectedMessage) return;
- switch (option) {
- case 'reply':
- setReplyMessage(selectedMessage.currentMessage);
- setIsModalVisible(false);
- break;
- case 'copy':
- Clipboard.setString(selectedMessage?.currentMessage?.text ?? '');
- setIsModalVisible(false);
- Alert.alert('Copied');
- break;
- case 'delete':
- handleDeleteMessage(selectedMessage.currentMessage?._id);
- setIsModalVisible(false);
- break;
- case 'download':
- downloadFileToDevice(selectedMessage.currentMessage);
- setIsModalVisible(false);
- break;
- case 'info':
- SheetManager.show('group-status', {
- payload: {
- messageId: selectedMessage.currentMessage._id,
- groupToken: group_token,
- setInsetsColor
- } as any
- });
- setIsModalVisible(false);
- setInsetsColor(Colors.WHITE);
- break;
- case 'pin':
- handlePinMessage(selectedMessage.currentMessage?._id, 1);
- setIsModalVisible(false);
- break;
- case 'edit':
- handleEditMessage(selectedMessage.currentMessage);
- setIsModalVisible(false);
- break;
- default:
- break;
- }
- closeEmojiSelector();
- };
- const openReactionList = (
- reactions: { uid: number; name: string; reaction: string }[],
- messageId: number
- ) => {
- SheetManager.show('reactions-list-modal', {
- payload: {
- users: reactions,
- currentUserId: +currentUserId,
- token,
- messageId,
- conversation_with_user: group_token,
- setMessages,
- sendWebSocketMessage,
- isGroup: true,
- groupToken: group_token
- } as any
- });
- };
- const renderTimeContainer = (time: TimeProps<CustomMessage>) => {
- const createdAt = new Date(time.currentMessage.createdAt);
- const formattedTime = createdAt.toLocaleTimeString([], {
- hour: '2-digit',
- minute: '2-digit',
- hour12: true
- });
- const hasReactions =
- time.currentMessage.reactions &&
- Array.isArray(time.currentMessage.reactions) &&
- time.currentMessage.reactions.length > 0;
- return (
- <View
- style={[
- styles.bottomContainer,
- {
- justifyContent: hasReactions ? 'space-between' : 'flex-end'
- }
- ]}
- >
- {hasReactions && (
- <TouchableOpacity
- style={[
- styles.bottomCustomContainer,
- {
- backgroundColor:
- time.position === 'left' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'
- }
- ]}
- onPress={() =>
- Array.isArray(time.currentMessage.reactions) &&
- openReactionList(
- time.currentMessage.reactions.map((reaction) => ({
- ...reaction,
- name: reaction.uid !== +currentUserId ? reaction?.name : 'Me'
- })),
- time.currentMessage._id
- )
- }
- >
- {Object.entries(
- (Array.isArray(time.currentMessage.reactions)
- ? time.currentMessage.reactions
- : []
- ).reduce(
- (acc: Record<string, { count: number }>, { reaction }: { reaction: string }) => {
- if (!acc[reaction]) {
- acc[reaction] = { count: 0 };
- }
- acc[reaction].count += 1;
- return acc;
- },
- {}
- )
- ).map(([emoji, { count }]: any) => {
- return (
- <View key={emoji}>
- <Text style={{}}>
- {emoji}
- {(count as number) > 1 ? ` ${count}` : ''}
- </Text>
- </View>
- );
- })}
- </TouchableOpacity>
- )}
- <View style={styles.timeContainer}>
- {time.currentMessage.edited && <Text style={styles.timeText}>Edited</Text>}
- <Text style={[styles.timeText, time.currentMessage.edited ? { paddingLeft: 0 } : {}]}>
- {formattedTime}
- </Text>
- {renderTicks(time.currentMessage)}
- </View>
- </View>
- );
- };
- const renderSelectedMessage = () => {
- if (!selectedMessage) return;
- const messageToCompare = selectedMessage.previousMessage;
- const showUserName =
- selectedMessage.position === 'left' &&
- selectedMessage.currentMessage &&
- messageToCompare &&
- (!isSameUser(selectedMessage.currentMessage, messageToCompare) ||
- !isSameDay(selectedMessage.currentMessage, messageToCompare));
- return (
- <View
- style={{
- left: !messagePosition?.isMine && messagePosition?.x ? messagePosition?.x - 8 : undefined,
- top: messagePosition?.y && messagePosition?.y > 120 ? messagePosition?.y : 0,
- overflow: 'hidden'
- }}
- >
- <ScrollView
- ref={scrollViewRef}
- contentContainerStyle={{
- paddingBottom: 60
- }}
- showsVerticalScrollIndicator={false}
- >
- <Bubble
- {...selectedMessage}
- wrapperStyle={{
- right: {
- backgroundColor: Colors.DARK_BLUE,
- marginTop: messagePosition?.y && messagePosition?.y > 120 ? 0 : 120,
- marginRight: 8
- },
- left: {
- backgroundColor: Colors.FILL_LIGHT,
- marginTop: messagePosition?.y && messagePosition?.y > 120 ? 0 : 120,
- marginLeft: 8
- }
- }}
- textStyle={{
- right: { color: Colors.WHITE },
- left: { color: Colors.DARK_BLUE }
- }}
- renderTicks={() => null}
- renderTime={renderTimeContainer}
- renderCustomView={() => (
- <View>
- {showUserName ? (
- <Text
- style={{
- color: Colors.BLACK,
- fontWeight: '600',
- fontSize: 13,
- paddingHorizontal: 10,
- paddingTop: 8,
- paddingBottom: 2
- }}
- >
- {selectedMessage.currentMessage.user.name}
- </Text>
- ) : null}
- {selectedMessage.currentMessage.attachment?.filetype === 'nomadmania/location'
- ? renderMessageLocation(selectedMessage)
- : selectedMessage.currentMessage.attachment &&
- !selectedMessage.currentMessage.image &&
- !selectedMessage.currentMessage.video
- ? renderMessageFile(selectedMessage)
- : renderReplyMessageView(selectedMessage)}
- </View>
- )}
- />
- <OptionsMenu
- selectedMessage={selectedMessage}
- handleOptionPress={handleOptionPress}
- messagePosition={messagePosition}
- isGroup={true}
- isAdmin={data?.settings?.admin == 1}
- />
- </ScrollView>
- </View>
- );
- };
- const handleBackgroundPress = () => {
- setIsModalVisible(false);
- setSelectedMessage(null);
- closeEmojiSelector();
- };
- useFocusEffect(
- useCallback(() => {
- navigation?.getParent()?.setOptions({
- tabBarStyle: {
- display: 'none'
- }
- });
- }, [navigation])
- );
- const replaceMentionsWithNames = (text: string) => {
- const userList = members?.settings ?? [];
- return text.replace(/@\{(\d+)\}/g, (_, uid) => {
- const user = userList.find((m) => m.uid === +uid);
- return user ? `@${user.name}` : `@{${uid}}`;
- });
- };
- const handleEditMessage = (message: CustomMessage) => {
- setReplyMessage(null);
- setEditingMessage({ ...message, text: replaceMentionsWithNames(message.text) });
- setText(replaceMentionsWithNames(message.text));
- textInputRef.current?.focus();
- };
- const onSend = useCallback(
- (newMessages: CustomMessage[] = []) => {
- if (editingMessage) {
- if (editingMessage.text !== newMessages[0].text) {
- const editedText = transformMessageForServer(newMessages[0].text);
- setMessages((prevMessages) =>
- (prevMessages ?? []).map((msg) =>
- msg._id === editingMessage._id ? { ...msg, text: editedText, isSending: true } : msg
- )
- );
- editMessage(
- {
- token,
- group_token: group_token,
- message_id: editingMessage._id,
- text: editedText
- },
- {
- onSuccess: () => {
- editMessageInStorage(group_token, editingMessage._id, editedText, true);
- const editedMessage = {
- _id: editingMessage._id,
- text: editedText
- };
- setMessages((previousMessages) =>
- (previousMessages ?? []).map((msg) =>
- msg._id === editingMessage._id
- ? { ...msg, isSending: false, edited: true }
- : msg
- )
- );
- sendWebSocketMessage('edited_message', editedMessage as unknown as CustomMessage);
- }
- }
- );
- }
- clearEditMessage();
- clearReplyMessage();
- return;
- }
- if (replyMessage) {
- newMessages[0].replyMessage = {
- text: transformMessageForServer(replyMessage.text),
- id: replyMessage._id,
- name: replyMessage.user._id !== +currentUserId ? (replyMessage.user.name as string) : 'Me'
- };
- }
- const user = {
- _id: +currentUserId,
- name: 'Me',
- avatar: null as never
- };
- const message = { ...newMessages[0], pending: true, isSending: true, user };
- setMessages((previousMessages) =>
- GiftedChat.append(previousMessages ?? [], [
- { ...message, text: transformMessageForServer(newMessages[0].text) }
- ])
- );
- if (!isConnected) {
- const staticMessage = {
- id: message._id,
- text: transformMessageForServer(message.text),
- isSending: true,
- sender: +currentUserId,
- status: 1,
- sent_datetime: moment(message.createdAt).utc().format('YYYY-MM-DD HH:mm:ss'),
- reply_to_id: message.replyMessage ? message.replyMessage.id : -1,
- reactions: '{}',
- edits: '{}',
- attachement: -1,
- encrypted: 0,
- sender_avatar: null,
- sender_name: 'Me'
- };
- sendMessageOffline(group_token, staticMessage as any);
- clearReplyMessage();
- return;
- }
- sendMessage(
- {
- token,
- to_group_token: group_token,
- text: transformMessageForServer(message.text),
- reply_to_id: replyMessage ? (replyMessage._id as number) : -1
- },
- {
- onSuccess: (res) => {
- const newMessage = {
- _id: res.message_id,
- text: message.text,
- replyMessage: { ...message.replyMessage, sender: replyMessage?.user?._id }
- };
- setMessages((previousMessages) =>
- (previousMessages ?? []).map((msg) =>
- msg._id === message._id ? { ...msg, _id: res.message_id, isSending: false } : msg
- )
- );
- sendWebSocketMessage('new_message', newMessage as unknown as CustomMessage);
- }
- }
- );
- clearReplyMessage();
- },
- [replyMessage, editingMessage, isConnected]
- );
- const addReaction = (messageId: number, reaction: string) => {
- if (!messages) return;
- const updatedMessages = messages.map((msg: any) => {
- if (msg._id === messageId) {
- const updatedReactions: Reaction[] = [
- ...(Array.isArray(msg.reactions)
- ? msg.reactions?.filter((r: Reaction) => r.uid !== +currentUserId)
- : []),
- { datetime: new Date().toISOString(), reaction: reaction, uid: +currentUserId }
- ];
- return {
- ...msg,
- reactions: updatedReactions
- };
- }
- return msg;
- });
- setMessages(updatedMessages);
- reactToMessage(
- { token, message_id: messageId, reaction: reaction, group_token: group_token },
- {
- onSuccess: () => {
- const message = messages.find((msg) => msg._id === messageId);
- if (message) {
- sendWebSocketMessage('new_reaction', message, reaction);
- }
- }
- }
- );
- setIsModalVisible(false);
- };
- const updateRowRef = useCallback(
- (ref: any) => {
- if (
- ref &&
- replyMessage &&
- ref.props.children.props.currentMessage?._id === replyMessage._id
- ) {
- swipeableRowRef.current = ref;
- }
- },
- [replyMessage]
- );
- const renderReplyMessageView = (props: BubbleProps<CustomMessage>) => {
- if (!props.currentMessage) {
- return null;
- }
- const { currentMessage } = props;
- if (!currentMessage || !currentMessage?.replyMessage) {
- return null;
- }
- return (
- <TouchableOpacity
- style={[
- styles.replyMessageContainer,
- {
- backgroundColor:
- currentMessage.user._id !== +currentUserId
- ? 'rgba(255, 255, 255, 0.7)'
- : 'rgba(0, 0, 0, 0.2)',
- borderColor:
- currentMessage.user._id !== +currentUserId ? Colors.DARK_BLUE : Colors.WHITE
- }
- ]}
- onPress={() => {
- if (currentMessage?.replyMessage?.id) {
- scrollToMessage(currentMessage.replyMessage.id);
- }
- }}
- >
- <View style={styles.replyContent}>
- <Text
- style={[
- styles.replyAuthorName,
- {
- color: currentMessage.user._id !== +currentUserId ? Colors.DARK_BLUE : Colors.WHITE
- }
- ]}
- >
- {currentMessage.replyMessage.name}
- </Text>
- <Text
- numberOfLines={1}
- style={[
- styles.replyMessageText,
- {
- color: currentMessage.user._id !== +currentUserId ? Colors.DARK_BLUE : Colors.WHITE
- }
- ]}
- >
- {currentMessage.replyMessage.text}
- </Text>
- </View>
- </TouchableOpacity>
- );
- };
- const scrollToMessage = (messageId: number) => {
- if (!messages) return;
- const messageIndex = messages.findIndex((message) => message._id === messageId);
- if (messageIndex !== -1 && flatList.current) {
- setIsSearchingMessage(null);
- flatList.current.scrollToIndex({
- index: messageIndex,
- animated: true,
- viewPosition: 0.5
- });
- setHighlightedMessageId(messageId);
- setMessages((previousMessages) =>
- (previousMessages ?? []).map((msg) =>
- msg._id === messageId
- ? {
- ...msg,
- isRendering: msg?.isRendering ? false : true
- }
- : msg
- )
- );
- }
- if (hasMoreMessages && messageIndex === -1) {
- setIsSearchingMessage(messageId);
- loadEarlierMessages();
- }
- };
- useEffect(() => {
- if (highlightedMessageId && isRerendering) {
- setTimeout(() => {
- setHighlightedMessageId(null);
- setIsRerendering(false);
- }, 1500);
- }
- }, [highlightedMessageId, isRerendering]);
- useEffect(() => {
- if (replyMessage && swipeableRowRef.current) {
- swipeableRowRef.current.close();
- swipeableRowRef.current = null;
- }
- }, [replyMessage]);
- const renderTicks = (message: CustomMessage) => {
- if (message.user._id !== +currentUserId) return null;
- if (message.isSending) {
- return (
- <View>
- <ActivityIndicator
- size={16}
- color={Colors.LIGHT_GRAY}
- style={{ transform: 'scale(0.8)' }}
- />
- </View>
- );
- }
- return message.received ? (
- <View>
- <MaterialCommunityIcons name="check-all" size={16} color={Colors.WHITE} />
- </View>
- ) : message.sent ? (
- <View>
- <MaterialCommunityIcons name="check" size={16} color={Colors.WHITE} />
- </View>
- ) : message.pending ? (
- <View>
- <MaterialCommunityIcons name="check" size={16} color={Colors.LIGHT_GRAY} />
- </View>
- ) : null;
- };
- const renderBubble = (props: BubbleProps<CustomMessage>) => {
- const { currentMessage } = props;
- if (currentMessage.deleted) {
- const text = currentMessage.text.length
- ? props.currentMessage.text
- : 'This message was deleted';
- return (
- <View>
- <Bubble
- {...props}
- renderTime={() => null}
- currentMessage={{
- ...props.currentMessage,
- text: text
- }}
- renderMessageText={() => (
- <View style={{ paddingHorizontal: 12, paddingVertical: 6 }}>
- <Text style={{ color: Colors.LIGHT_GRAY, fontStyle: 'italic', fontSize: 12 }}>
- {text}
- </Text>
- </View>
- )}
- wrapperStyle={{
- right: {
- backgroundColor: Colors.DARK_BLUE
- },
- left: {
- backgroundColor: Colors.FILL_LIGHT
- }
- }}
- textStyle={{
- left: {
- color: Colors.DARK_BLUE
- },
- right: {
- color: Colors.WHITE
- }
- }}
- />
- </View>
- );
- }
- const isHighlighted = currentMessage._id === highlightedMessageId;
- const backgroundColor = isHighlighted
- ? Colors.ORANGE
- : currentMessage.user._id === +currentUserId
- ? Colors.DARK_BLUE
- : Colors.FILL_LIGHT;
- const messageToCompare = props.previousMessage;
- const showUserName =
- props.position === 'left' &&
- currentMessage &&
- messageToCompare &&
- (!isSameUser(currentMessage, messageToCompare) ||
- !isSameDay(currentMessage, messageToCompare));
- return (
- <View
- key={`${currentMessage._id}-${isHighlighted ? 'highlighted' : 'normal'}`}
- ref={(ref) => {
- if (ref && currentMessage) {
- messageRefs.current[currentMessage._id] = ref;
- }
- }}
- collapsable={false}
- >
- <Bubble
- {...props}
- wrapperStyle={{
- right: {
- backgroundColor: backgroundColor
- },
- left: {
- backgroundColor: backgroundColor
- }
- }}
- textStyle={{
- left: {
- color: Colors.DARK_BLUE
- },
- right: {
- color: Colors.FILL_LIGHT
- }
- }}
- onLongPress={() => handleLongPress(currentMessage, props)}
- renderTicks={() => null}
- renderTime={renderTimeContainer}
- renderCustomView={() => {
- return (
- <View>
- {showUserName ? (
- <Text
- style={{
- color: Colors.BLACK,
- fontWeight: '600',
- fontSize: 13,
- paddingHorizontal: 10,
- paddingTop: 8,
- paddingBottom: 2
- }}
- >
- {/* {'~ '} */}
- {props.currentMessage.user.name}
- </Text>
- ) : null}
- {currentMessage.attachment?.filetype === 'nomadmania/location'
- ? renderMessageLocation(props)
- : currentMessage.attachment && !currentMessage.image && !currentMessage.video
- ? renderMessageFile(props)
- : renderReplyMessageView(props)}
- </View>
- );
- }}
- />
- </View>
- );
- };
- const openAttachmentsModal = () => {
- SheetManager.show('chat-attachments', {
- payload: {
- name: groupName,
- uid: group_token,
- setModalInfo,
- closeOptions: () => {},
- onSendMedia,
- onSendLocation,
- onShareLiveLocation,
- onSendFile,
- isGroup: true
- } as any
- });
- };
- const renderInputToolbar = (props: any) => {
- if (!chatData?.can_send_messages) return null;
- return (
- <>
- {showMentions && canSeeMembers ? (
- <MentionsList
- mentionList={mentionList}
- inputHeight={inputHeight}
- onMentionSelect={onMentionSelect}
- />
- ) : null}
- <View
- onLayout={(e) => {
- setInputHeight(e.nativeEvent.layout.height);
- }}
- >
- <InputToolbar
- {...props}
- renderActions={() =>
- userType === 'normal' && !editingMessage ? (
- <Actions
- icon={() => (
- <MaterialCommunityIcons name="plus" size={28} color={Colors.DARK_BLUE} />
- )}
- onPressActionButton={openAttachmentsModal}
- />
- ) : null
- }
- containerStyle={{
- backgroundColor: Colors.FILL_LIGHT
- }}
- />
- </View>
- </>
- );
- };
- const renderScrollToBottom = () => {
- return (
- <TouchableOpacity
- style={styles.scrollToBottom}
- onPress={() => {
- if (flatList.current) {
- flatList.current.scrollToIndex({ index: 0, animated: true });
- }
- }}
- >
- <MaterialCommunityIcons name="chevron-down" size={24} color={Colors.WHITE} />
- </TouchableOpacity>
- );
- };
- const shouldUpdateMessage = (
- props: MessageProps<IMessage>,
- nextProps: MessageProps<IMessage>
- ) => {
- setIsRerendering(true);
- const currentId = nextProps.currentMessage._id;
- return currentId === highlightedMessageId;
- };
- const onInputTextChanged = (value: string) => {
- handleTyping(value.length > 0);
- setText(value);
- const mentionMatch = value.match(/(^|\s)(@\w*)$/);
- if (mentionMatch) {
- setShowMentions(true);
- const searchText = mentionMatch[2].slice(1).toLowerCase();
- setMentionList(
- (members?.settings ?? [])?.filter(
- (m) => m.name.toLowerCase().includes(searchText) && m.uid !== +currentUserId
- )
- );
- } else {
- setShowMentions(false);
- }
- };
- const onMentionSelect = (member: { uid: number; name: string }) => {
- const words = text.split(' ');
- words[words.length - 1] = `@${member.name} `;
- setText(words.join(' '));
- setShowMentions(false);
- };
- const transformMessageForServer = (text: string) => {
- let transformedText = text;
- members?.settings?.forEach((member) => {
- const mentionRegex = new RegExp(`@${member.name}\\b`, 'g');
- transformedText = transformedText.replace(mentionRegex, `@{${member.uid}}`);
- });
- return transformedText;
- };
- return (
- <SafeAreaView
- edges={['top']}
- style={{
- height: '100%'
- }}
- >
- <View style={{ paddingHorizontal: '5%' }}>
- <Header
- label={groupName}
- textColor={userType !== 'normal' ? Colors.RED : Colors.DARK_BLUE}
- rightElement={
- <TouchableOpacity
- onPress={() =>
- navigation.navigate(
- ...([NAVIGATION_PAGES.GROUP_SETTINGS, { groupToken: group_token }] as never)
- )
- }
- disabled={userType !== 'normal'}
- >
- {groupAvatar && userType === 'normal' ? (
- <Image
- source={{ uri: `${groupAvatar}?cacheBust=${cacheKey}` }}
- style={styles.avatar}
- />
- ) : userType === 'normal' ? (
- <GroupIcon fill={Colors.DARK_BLUE} width={30} height={30} />
- ) : (
- <BanIcon fill={Colors.RED} width={30} height={30} />
- )}
- </TouchableOpacity>
- }
- />
- </View>
- {pinned && (
- <TouchableOpacity
- style={{
- height: 38,
- flexDirection: 'row',
- backgroundColor: Colors.FILL_LIGHT,
- borderBottomWidth: 1,
- borderBottomColor: Colors.DARK_LIGHT
- }}
- onPress={() => scrollToMessage(pinned.id)}
- >
- <View
- style={{
- height: 50,
- width: 6,
- backgroundColor: Colors.DARK_BLUE
- }}
- ></View>
- <View
- style={{
- paddingLeft: 8,
- height: '100%',
- justifyContent: 'center'
- }}
- >
- <PinIcon fill={Colors.DARK_BLUE} height={18} />
- </View>
- <View style={{ flex: 1, justifyContent: 'center' }}>
- <Text style={{ color: Colors.DARK_BLUE, paddingLeft: 10 }} numberOfLines={1}>
- {pinned.text}
- </Text>
- </View>
- {data?.settings?.admin === 1 && (
- <View style={{ alignItems: 'flex-end', justifyContent: 'center' }}>
- <TouchableOpacity
- style={{ paddingRight: 10 }}
- onPress={() => handlePinMessage(pinned.id, 0)}
- >
- <MaterialCommunityIcons
- name="close-circle-outline"
- size={24}
- color={Colors.DARK_BLUE}
- />
- </TouchableOpacity>
- </View>
- )}
- </TouchableOpacity>
- )}
- <GestureHandlerRootView style={styles.container}>
- {messages &&
- ((canSeeMembers && members?.settings) ||
- (data &&
- data.settings &&
- data.settings.members_can_see_members === 0 &&
- data.settings.admin === 0) ||
- !isConnected) ? (
- <GiftedChat
- messages={messages as CustomMessage[]}
- text={text}
- listViewProps={{
- ref: flatList,
- showsVerticalScrollIndicator: false,
- initialNumToRender: messages.length,
- onViewableItemsChanged: handleViewableItemsChanged,
- viewabilityConfig: { itemVisiblePercentThreshold: 50 },
- onScrollToIndexFailed: (info: any) => {
- const wait = new Promise((resolve) => setTimeout(resolve, 300));
- wait.then(() => {
- flatList.current?.scrollToIndex({
- index: info.index,
- animated: true,
- viewPosition: 0.5
- });
- });
- }
- }}
- renderSystemMessage={renderSystemMessage}
- onSend={(newMessages: CustomMessage[]) => onSend(newMessages)}
- user={{ _id: +currentUserId, name: 'Me' }}
- renderBubble={renderBubble}
- renderMessageImage={(props) => (
- <RenderMessageImage
- props={props}
- token={token}
- currentUserId={+currentUserId}
- onLongPress={handleLongPress}
- setSelectedMedia={setSelectedMedia}
- />
- )}
- showUserAvatar={true}
- onPressAvatar={(user) => {
- navigation.navigate(
- ...([NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW, { userId: user._id }] as never)
- );
- }}
- renderInputToolbar={renderInputToolbar}
- renderCustomView={renderReplyMessageView}
- isCustomViewBottom={false}
- messageContainerRef={messageContainerRef}
- minComposerHeight={34}
- onInputTextChanged={onInputTextChanged}
- textInputRef={textInputRef}
- isTyping={isTyping ? true : false}
- renderTypingIndicator={() => <TypingIndicator isTyping={isTyping} />}
- renderSend={(props) =>
- editingMessage ? (
- <View style={[styles.sendBtn, { paddingHorizontal: 8 }]}>
- {props.text?.trim() && (
- <Send
- {...props}
- containerStyle={{
- justifyContent: 'center'
- }}
- >
- <View style={styles.editBtn}>
- <MaterialCommunityIcons name="check" size={22} color={Colors.WHITE} />
- </View>
- </Send>
- )}
- {!props.text?.trim() && (
- <View style={[styles.editBtn, { backgroundColor: Colors.LIGHT_GRAY }]}>
- <MaterialCommunityIcons name="check" size={22} color={Colors.WHITE} />
- </View>
- )}
- </View>
- ) : (
- <View style={styles.sendBtn}>
- {props.text?.trim() && (
- <Send
- {...props}
- containerStyle={{
- justifyContent: 'center'
- }}
- >
- <SendIcon fill={Colors.DARK_BLUE} />
- </Send>
- )}
- {!props.text?.trim() && <SendIcon fill={Colors.LIGHT_GRAY} />}
- </View>
- )
- }
- renderMessageVideo={(props) => (
- <RenderMessageVideo
- props={props}
- token={token}
- currentUserId={+currentUserId}
- onLongPress={handleLongPress}
- />
- )}
- textInputProps={{
- ...styles.composer,
- selectionColor: Colors.LIGHT_GRAY
- }}
- placeholder=""
- renderMessage={(props) => (
- <ChatMessageBox
- {...(props as MessageProps<CustomMessage>)}
- updateRowRef={updateRowRef}
- setReplyOnSwipeOpen={setReplyMessage}
- />
- )}
- renderChatFooter={() => (
- <ReplyMessageBar
- clearReply={clearReplyMessage}
- clearEditMessage={clearEditMessage}
- message={
- replyMessage
- ? { ...replyMessage, text: replaceMentionsWithNames(replyMessage.text) }
- : null
- }
- editingMessage={editingMessage}
- />
- )}
- maxComposerHeight={100}
- renderComposer={(props) => <Composer {...props} />}
- keyboardShouldPersistTaps="handled"
- renderChatEmpty={() => (
- <View style={styles.emptyChat}>
- <Text
- style={styles.emptyChatText}
- >{`No messages yet.\nFeel free to start the conversation.`}</Text>
- </View>
- )}
- shouldUpdateMessage={shouldUpdateMessage}
- scrollToBottom={true}
- scrollToBottomComponent={renderScrollToBottom}
- scrollToBottomStyle={{ backgroundColor: 'transparent' }}
- parsePatterns={(linkStyle) => [
- {
- type: 'url',
- style: { color: Colors.ORANGE, textDecorationLine: 'underline' },
- onPress: (url: string) => Linking.openURL(url),
- onLongPress: (url: string) => {
- Clipboard.setString(url ?? '');
- Alert.alert('Link copied');
- }
- },
- {
- pattern: /@\{(\d+)\}/g,
- renderText: (messageText: string) => {
- const tagId = messageText.slice(2, messageText.length - 1);
- const user = (members?.settings ?? [])?.find((m) => m.uid === +tagId);
- if (user) {
- return (
- <Text
- style={{ color: Colors.ORANGE }}
- onPress={() =>
- navigation.navigate(
- ...([
- NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
- { userId: user.uid }
- ] as never)
- )
- }
- >
- @{user.name}
- </Text>
- );
- } else {
- return messageText;
- }
- }
- }
- ]}
- infiniteScroll={true}
- loadEarlier={hasMoreMessages}
- isLoadingEarlier={isLoadingEarlier}
- onLoadEarlier={loadEarlierMessages}
- renderLoadEarlier={() => (
- <View style={{ paddingVertical: 20 }}>
- <ActivityIndicator size="small" color={Colors.DARK_BLUE} />
- </View>
- )}
- />
- ) : (
- <ActivityIndicator size="large" color={Colors.DARK_BLUE} />
- )}
- <ImageView
- images={[
- {
- uri: selectedMedia,
- cache: 'force-cache',
- headers: { Nmtoken: token, 'App-Version': APP_VERSION, Platform: Platform.OS }
- }
- ]}
- imageIndex={0}
- visible={!!selectedMedia}
- onRequestClose={() => setSelectedMedia(null)}
- backgroundColor={Colors.DARK_BLUE}
- />
- <ReactModal
- isVisible={isModalVisible}
- onBackdropPress={handleBackgroundPress}
- onBackButtonPress={handleBackgroundPress}
- style={styles.reactModalContainer}
- animationIn="fadeIn"
- animationOut="fadeOut"
- useNativeDriver
- backdropColor="transparent"
- >
- <BlurView
- intensity={80}
- style={styles.modalBackground}
- experimentalBlurMethod="dimezisBlurView"
- >
- <TouchableOpacity
- style={styles.modalBackground}
- activeOpacity={1}
- onPress={handleBackgroundPress}
- >
- <ReactionBar
- messagePosition={messagePosition}
- selectedMessage={selectedMessage}
- reactionEmojis={reactionEmojis}
- handleReactionPress={handleReactionPress}
- openEmojiSelector={openEmojiSelector}
- />
- {renderSelectedMessage()}
- <EmojiSelectorModal
- visible={emojiSelectorVisible}
- selectedMessage={selectedMessage}
- addReaction={addReaction}
- closeEmojiSelector={closeEmojiSelector}
- />
- </TouchableOpacity>
- </BlurView>
- </ReactModal>
- <WarningModal
- isVisible={modalInfo.visible}
- onClose={closeModal}
- type={modalInfo.type}
- message={modalInfo.message}
- buttonTitle={modalInfo.buttonTitle}
- title={modalInfo.title}
- action={() => {
- modalInfo.action();
- closeModal();
- }}
- />
- <AttachmentsModal />
- <ReactionsListModal />
- <GroupStatusModal />
- </GestureHandlerRootView>
- <View
- style={{
- height: insets.bottom,
- backgroundColor: insetsColor
- }}
- />
- </SafeAreaView>
- );
- };
- export default GroupChatScreen;
|