Skip to content

Instantly share code, notes, and snippets.

@myckhel
Created March 25, 2021 14:43
Show Gist options
  • Save myckhel/d88af0d6a328f61b8023b48fe25639fe to your computer and use it in GitHub Desktop.
Save myckhel/d88af0d6a328f61b8023b48fe25639fe to your computer and use it in GitHub Desktop.
react native gifted chat swipe to reply
import React, {useRef, useMemo, useState, useCallback} from 'react';
import {Vibration, View, StyleSheet} from 'react-native';
import {Button, Text, Row} from '../../theme';
import {Send as SendIcon} from '../../Icons';
import {useMessage, useConversationEventType} from '../../../redux/msg/hooks';
import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
import {SwipeRow} from 'react-native-swipe-list-view';
import {
Reply,
PlayCircle as PlayIcon,
DisabledAlt,
Sent,
Clock,
DoubleCheck,
} from '../../Icons';
import FastImage from '../../theme/FastImage';
import LinearGradient from 'react-native-linear-gradient';
import {fastMemo} from '../../../func';
import DateTime from '../../DateTime';
import {ImageOrNot} from '../../../func/helpers';
export const Send = ({onPress}) => (
<Button onPress={onPress} iconProp={{fill: '#fff'}} Icon={SendIcon} />
);
/* Message Views */
export default fastMemo(
({
conversationId,
setChatState,
id,
nextSenderId,
showMenu,
userId,
selecting,
}) => {
const ref = useRef();
const {
message: {
system,
image,
downloaded,
videos,
created_at,
sending,
reply_type,
message,
isSender,
reply,
conversation_id,
user_id,
trashed,
} = {},
} = useMessage(
{conversationId, messageId: id},
({
system,
image,
downloaded,
videos,
created_at,
sending,
reply_type,
message,
isSender,
reply,
conversation_id,
user_id,
id,
trashed,
} = {}) =>
system
? {message, system}
: trashed
? {trashed, created_at, isSender}
: videos?.length
? {
videos,
conversation_id,
id,
isSender,
sending,
created_at,
}
: image
? {
image,
sending,
downloaded,
created_at,
isSender,
conversation_id,
id,
}
: {
message,
user_id,
isSender,
conversation_id,
id,
reply,
reply_type,
sending,
created_at,
},
);
const onLongPress = useCallback(() => {
const pressed = {id, message: message};
if (user_id === userId) {
pressed.user = {_id: userId};
}
showMenu(ref, pressed);
}, [ref, user_id, id]);
const onPress = useCallback(() => {
if (videos || image) {
navigate('MediaView', {media: {videos, image, id}});
} else if (reply_type === 'App\\Models\\Story') {
navigate('StoriesGroup', {
story: {...reply, user: {id: reply.user_id, name: reply?.user_name}},
single: true,
});
}
}, [reply_type, videos, image, id]);
const onLeftAction = useCallback(
({isActivated}) => {
if (isActivated) {
setChatState({replyId: id}, (s, p) => ({...s, ...p}));
Vibration.vibrate(50);
}
},
[id],
);
const x = useSharedValue(-26.11 + -30);
const transformStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: x.value,
},
],
};
});
const onSwipeValueChange = useCallback(
({value}) => {
const valueX = value + (-26.11 + -30);
if (valueX < 50) {
x.value = valueX;
}
},
[x],
);
return (
<SwipeRow
onSwipeValueChange={onSwipeValueChange}
useNativeDriver
onLeftActionStatusChange={onLeftAction}
disableLeftSwipe
disableRightSwipe={!!(system || trashed || selecting)}
leftActivationValue={90}
leftActionValue={0}
style={styles.swipe}
swipeKey={id + ''}>
{system || trashed || selecting ? (
<></>
) : (
<Animated.View style={[styles.reply, transformStyle]}>
<Reply width={wp(26.11)} height={hp(21.76)} fill="#000" />
</Animated.View>
)}
<Button
renderToHardwareTextureAndroid={true}
collapsable={false}
disabled={system || trashed || selecting}
color={
system
? '#EBEBEB'
: message
? isSender
? '#0C67F0'
: '#fff'
: undefined
}
left={isSender ? 'auto' : undefined} // crashable if edit
ref={ref}
onPress={onPress}
onLongPress={onLongPress}
style={[
system ? styles.sysMsg : styles.message,
user_id === nextSenderId && styles.nextSender,
]}>
{trashed ? (
<DeletedMessage {...{created_at, isSender}} />
) : (
<RenderContent
{...{
system,
image,
downloaded,
videos,
created_at,
sending,
reply_type,
message,
user_id,
isSender,
reply,
conversation_id,
}}
/>
)}
</Button>
</SwipeRow>
);
},
);
const SystemMessage = ({message}) => {
return (
<Text color="#5F5F5F" lineHeight={15} center fontSize={13}>
{message}
</Text>
);
};
export const MessageStatus = fastMemo(
({sending, conversationId, created_at}) => {
const eventType = useConversationEventType(conversationId, created_at);
return (
<View left={wp(5)}>
{eventType ? (
eventType === 'read' ? (
<DoubleCheck fill="transparent" stroke="#4CC5F7" />
) : (
<DoubleCheck fill="transparent" stroke="#ACACAC" />
)
) : sending ? (
<Clock fill="grey" />
) : (
<Sent />
)}
</View>
);
},
);
export const DeletedText = ({color, style}) => (
<Row style={style}>
<DisabledAlt fill="#C9C9C9" top={2} width={wp(15)} />
<Text
langId="msg_deleted"
left={10}
fontSize={20}
italic
color={'#C9C9C9' || '#C9C9C9'}
style={styles.deltext}
/>
</Row>
);
const RenderContent = fastMemo(
({
system,
image,
downloaded,
videos,
created_at,
sending,
reply_type,
message,
isSender,
reply,
conversation_id,
user_id,
}) => {
const type = useMemo(
() =>
system ? 'system' : videos?.length ? 'video' : image ? 'image' : 'text',
[!!videos?.length, image, system],
);
const MView = useMemo(() => {
switch (type) {
case 'system':
return SystemMessage;
case 'video':
return MessageVideos;
case 'image':
return MessageImage;
default:
return MessageText;
}
}, [type]);
const MViewProps = useMemo(() => {
switch (type) {
case 'system':
return {message};
case 'video':
return {
videos,
};
case 'image':
return {
image,
sending,
downloaded,
};
default:
return {
message,
isSender,
};
}
}, [
type,
videos,
image,
system,
conversation_id,
isSender,
sending,
created_at,
downloaded,
reply,
reply_type,
user_id,
message,
]);
const MFooterProps = useMemo(() => {
switch (type) {
case 'video':
case 'image':
case 'text':
return {
isSender,
sending,
created_at,
conversationId: conversation_id,
};
default:
return {created_at};
}
}, [type, isSender, sending, created_at, conversation_id]);
const _Footer = useCallback(
({style}) => <Footer style={style} {...MFooterProps} />,
[MFooterProps],
);
const replyProps = useMemo(() => {
if (reply) {
const {message, user_name, text, image, videos} = reply;
return {
style: {marginHorizontal: wp(5)},
message,
user_name: user_id !== reply.user_id && user_name,
text,
image,
videos,
type: reply_type?.split('\\').pop(),
user: user_id === reply.user_id,
};
} else {
return {style: {marginHorizontal: wp(5)}};
}
}, [reply, reply_type, user_id]);
return (
<>
{reply && <ReplyRapper {...replyProps} />}
<MView {...MViewProps} Footer={_Footer} />
</>
);
},
);
export const ReplyRapper = fastMemo(
({
message,
text,
user_name = 'User',
image,
videos,
type,
style,
user,
replyUserStyle,
imageStyle,
numberOfLines,
}) => {
const replyText =
message || text || (image ? 'Photo' : videos ? 'Video' : '');
return (
<View style={[styles.replyWrapper, style]}>
{image ? (
<FastImage
source={ImageOrNot(image, 'thumb')}
style={[styles.replyImage, imageStyle]}
/>
) : null}
<View style={[styles.replyText, image && styles.replyImgText]}>
<Text
langId={
type === 'Story'
? user
? 'self_story'
: 'user_story'
: user
? 'txt_you'
: '_value'
}
values={{_name: user_name, _value: user_name}}
fontSize={20}
bold
color={'#0C67F0'}
style={[styles.replyUser, replyUserStyle]}
/>
<Text
numberOfLines={numberOfLines || 2}
fontSize={18}
color="#000"
style={styles.replyText}>
{replyText}
</Text>
</View>
</View>
);
},
);
export const MessageText = fastMemo(({message, isSender, Footer}) => {
return (
<Row
flexWrap={message?.length > 20 ? 'wrap' : undefined} //Wrapsettings
style={styles.msg}>
<Text bottom={6} color={isSender ? '#fff' : '#000000'} fontSize={19}>
{message}
</Text>
<Footer style={styles.textFoot} />
</Row>
);
});
export const MessageVideos = fastMemo(({videos, Footer}) => {
const video = useMemo(() => videos[0], [videos]);
return (
<FastImage
retry
id={video?.id}
source={{uri: video?.thumb || video?.uri}}
style={styles.image}>
<LinearGradient style={styles.linear} colors={linears} />
<PlayIcon width={40} height={40} style={styles.playIcon} />
<Footer style={styles.imageRight} />
</FastImage>
);
});
const Footer = ({style, isSender, sending, created_at, conversationId}) => {
return (
<Row
align="center"
left="auto"
style={[styles.right, style, !isSender && styles.rightSender]}>
<DateTime
style={styles.time}
data={created_at}
render={({datetime}) => (
<Text fontSize={15} color={isSender ? '#C3C3C3' : '#C3C3C3'}>
{datetime}
</Text>
)}
/>
{isSender && (
<MessageStatus
{...{
sending,
conversationId,
created_at,
}}
/>
)}
</Row>
);
};
const linears = [
'rgba(0, 0, 0, 0)',
'rgba(0, 0, 0, 0.2)',
'rgba(0, 0, 0, 0.3)',
'rgba(0, 0, 0, 0.8)',
];
export const MessageImage = fastMemo(({image, sending, downloaded, Footer}) => {
// const dispatch = useDispatch();
const [loadFull, setLoadFull] = useState(downloaded);
const thumb = sending && image ? null : image?.thumb || '';
const url = sending && image ? image : image?.url || '';
const flagImageLoaded = () => {
// dispatch(updateMsg({msg: {...msg, id, downloaded: true}}));
};
return (
<FastImage
onLoadEnd={flagImageLoaded}
loadFull={loadFull}
onDownload={setLoadFull}
style={styles.image}
thumbSrc={{uri: thumb}}
source={{uri: url}}>
<LinearGradient style={styles.linear} colors={linears} />
<Footer style={styles.imageRight} />
</FastImage>
);
});
const DeletedMessage = fastMemo(({style, color, created_at, isSender}) => (
<Row style={[styles.msg, style]}>
<DeletedText color={color} />
<Footer
style={styles.textFoot}
{...{
created_at,
}}
/>
</Row>
));
const SENDER_MAX_WIDTH = '85%';
export const IMG_HEIGHT = hp(254);
const styles = StyleSheet.create({
deltext: {fontStyle: 'italic'},
msg: {flex: 1, padding: 10, paddingVertical: hp(8)},
replyUser: {marginBottom: 10},
message: {
flex: 1,
alignSelf: 'flex-start',
marginVertical: 6,
backgroundColor: '#F0F0F0',
maxWidth: '80%',
borderRadius: 5,
marginHorizontal: 10,
},
linear: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: hp(30),
},
swipe: {flex: 1},
replyWrapper: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#C8D9F4',
borderRadius: 5,
marginBottom: 12,
marginTop: 5,
overflow: 'hidden',
},
reply: {
marginTop: 'auto',
marginBottom: 'auto',
marginLeft: wp(10),
backgroundColor: '#C9C9C9',
height: hp(43),
width: wp(43),
alignItems: 'center',
justifyContent: 'center',
borderRadius: 30,
},
replyImgText: {width: '90%'},
replyImage: {width: hp(53), height: '100%'},
replyText: {
padding: 5,
},
rightSender: {marginLeft: 0},
right: {
alignSelf: 'flex-end',
},
imageRight: {
flexDirection: 'row',
alignItems: 'center',
marginLeft: 'auto',
marginTop: 'auto',
marginRight: 20,
marginBottom: 5,
},
sysMsg: {
alignItems: 'center',
justifyContent: 'center',
marginVertical: 20,
maxWidth: SENDER_MAX_WIDTH,
alignSelf: 'center',
padding: wp(10),
paddingHorizontal: wp(10),
borderRadius: 5,
},
image: {
flex: 1,
width: wp(267),
height: IMG_HEIGHT,
borderRadius: 5,
backgroundColor: '#F0F0F0',
justifyContent: 'center',
alignItems: 'center',
},
textFoot: {
marginLeft: 'auto',
},
time: {
flexDirection: 'row',
bottom: 2,
paddingLeft: 25,
},
playIcon: {
position: 'absolute',
left: 'auto',
top: 'auto',
},
});
import React, {
useCallback,
useMemo,
useRef,
useEffect,
useLayoutEffect,
} from 'react';
// redux
import {useDispatch} from 'react-redux';
import {
useReduxState,
getState as getReduxState,
useMemoSelector,
useSetState,
} from 'use-redux-state-hook';
// actions
import {addMsg, updateMsgs} from '../../../redux/actions';
import {useNavigation, useRoute} from '@react-navigation/native';
import queue from 'react-native-job-queue';
import {TextInput, StyleSheet, Clipboard, View, Platform} from 'react-native';
// icon
import {
GalleryIcon,
Camera,
Pointer as SendIcon,
Reply,
Copy,
X as CancelIcon,
DisabledAlt,
DropArrow as ScrolldownIcon,
Check,
} from '../../Icons';
// async
import {multiDeleteMessage} from '../../../func/async/message';
// selectors
import {selectMessage} from '../../../redux/selectors';
import {useConversation} from '../../../redux/msg/hooks';
import {
useSelected,
useMergeState,
usePicker,
useCameraEvent,
} from '../../../redux/app/hooks';
// func
import {Toastify, toVideo, mediaType} from '../../../func/helpers';
import {fastMemo} from '../../../func';
// components
import {Text, Button, Row, Menu} from '../../theme';
import Popover from 'react-native-popover-view';
import Day from '../../Day';
import Message, {ReplyRapper} from './Message';
import {GiftedChat} from 'react-native-gifted-chat';
// package
import {keys, values} from 'lodash';
export default fastMemo(({style, userId, conversationId, otherUserId}) => {
const dispatch = useDispatch();
const {params} = useRoute();
const lastConversationId = useRef(conversationId);
const previousState = useRef(null);
useLayoutEffect(() => {
if (lastConversationId.current !== conversationId) {
// dispatch state move
previousState.current = getReduxState(
store,
`conversations.${lastConversationId.current}`,
);
lastConversationId.current = conversationId;
}
}, [conversationId]);
const {messages, nextable, loading, next} = useConversation({
otherUserId,
conversationId,
});
const {
setState: setChatState,
cleanup: cleanupConversationState,
} = useReduxState({
name: `conversations.${conversationId}`,
state: {
text: '',
image: null,
replyId: params?.replyId,
},
reducer: (s, p) => {
if (previousState.current) {
const prevState = previousState.current;
previousState.current = null;
return prevState;
} else {
return {...p, ...s, replyId: p.replyId};
}
},
});
const [state, setState] = useMergeState({
isVisible: false,
fromView: null,
pressed: {},
showDeleteMenu: false,
});
const {isVisible, fromView, pressed, showDeleteMenu} = state;
const {select, cleanup, getState: getSelectState, reset} = useSelected({
name: 'chat-screen-selects',
unmount: true,
});
useEffect(() => {
return () => {
if (!conversationId) {
cleanupConversationState();
}
cleanup();
};
}, [conversationId]);
const onRequestClose = useCallback(() => setState({isVisible: false}), []);
const showMenu = useCallback(
(ref, pressed) =>
setState(({isVisible}) => ({
pressed,
isVisible: !isVisible,
fromView: ref,
})),
[],
);
const toggleDeleteMenu = useCallback(
bool => setState({showDeleteMenu: bool}),
[],
);
const onCopy = useCallback(() => {
if (pressed?.message) {
Clipboard.setString(`${pressed.message}`);
Toastify('Message Copied to Clipboard');
}
onRequestClose();
}, [onRequestClose, pressed]);
const onReply = useCallback(() => {
setChatState({replyId: pressed.id});
onRequestClose();
}, [onRequestClose]);
const onDelete = useCallback(() => {
onRequestClose();
select({key: pressed.id, value: !!pressed.user});
}, [select, pressed]);
const onDeleteMultiMessage = useCallback(
async everyone => {
try {
setState({showDeleteMenu: false});
const events = await multiDeleteMessage({
everyone,
messages: getSelectState(state => keys(state || {})),
});
reset();
const msgs = events?.map(({made_id, type, created_at}) => ({
conversation_id: conversationId,
id: made_id,
trashed: {
made_id,
type,
created_at,
},
}));
dispatch(updateMsgs({msgs}));
} catch (e) {
reset();
console.log({e});
}
},
[conversationId],
);
const renderMessage = useCallback(
d => {
const {currentMessage, nextMessage, previousMessage} = d;
const {_id, system, trashed, user_id} = currentMessage;
const {_id: id} = d.user;
return (
<>
<SelectableMessage
conversationId={conversationId}
setChatState={setChatState}
showMenu={showMenu}
_id={_id}
userId={id}
msgUserId={user_id}
trashed={!!trashed}
system={system}
nextSenderId={nextMessage?.user?._id}
/>
{!system && (
<Day
containerStyle={styles.dayTime}
textStyle={styles.dayTimeText}
previousMessageAt={previousMessage?.created_at}
currentMessageAt={currentMessage?.created_at}
/>
)}
</>
);
},
[conversationId],
);
const renderComposer = useCallback(
() => (
<ChatBottom
conversationId={conversationId}
otherUserId={otherUserId}
userId={userId}
toggleDeleteMenu={toggleDeleteMenu}
/>
),
[otherUserId, conversationId, userId],
);
const scrollToBottomComponent = useCallback(
() => <ScrolldownIcon width={wp(18)} />,
[],
);
const renderChatFooter = useCallback(
() => <ChatFooter conversationId={conversationId} />,
[conversationId],
);
return (
<View accessible accessibilityLabel="main" style={[styles.messages, style]}>
<GiftedChat
scrollToBottomStyle={styles.scrollToBottomStyle}
scrollToBottomComponent={scrollToBottomComponent}
renderChatFooter={renderChatFooter}
scrollToBottom
bottomOffset={-20}
infiniteScroll
loadEarlier={nextable}
onLoadEarlier={next}
isLoadingEarlier={loading}
// renderLoading
// renderChatEmpty
renderMessage={renderMessage}
renderComposer={renderComposer}
minComposerHeight={hp(40)}
maxComposerHeight={hp(100)}
messages={messages}
user={{_id: userId}}
// isTyping
/>
<MessageMenu
onDelete={onDelete}
onCopy={onCopy}
onReply={onReply}
canCopy={!!pressed?.message}
isVisible={isVisible}
fromView={fromView}
onRequestClose={onRequestClose}
/>
<DeleteMenu
getSelectState={getSelectState}
deleteMessage={onDeleteMultiMessage}
toggleMenu={toggleDeleteMenu}
isVisible={showDeleteMenu}
/>
</View>
);
});
const SelectableMessage = fastMemo(
({
conversationId,
setChatState,
showMenu,
_id,
userId,
nextSenderId,
trashed,
system,
msgUserId,
}) => {
const {select, useIsSelected, useIsSelecting} = useSelected({
name: 'chat-screen-selects',
id: _id,
cleanup: true,
unmount: true,
});
const selected = useIsSelected();
const selecting = useIsSelecting();
return (
<Button
disabled={!selecting}
flexDirection="row"
onPress={() => select({value: userId === msgUserId})}>
{!trashed && !system && selecting && <Select selected={selected} />}
<Message
conversationId={conversationId}
setChatState={setChatState}
showMenu={showMenu}
id={_id}
selecting={selecting}
userId={userId}
nextSenderId={nextSenderId}
/>
</Button>
);
},
);
const Select = ({selected}) => (
<View style={[styles.select, selected && styles.selected]}>
{selected && <Check fill="#fff" />}
</View>
);
export const useMediaType = ({latest_media, image, videos}) => {
return useMemo(() => {
return (
latest_media?.name || (image && 'image') || (videos?.length && 'videos')
);
}, [latest_media, image, videos]);
};
export const MessageMenu = fastMemo(
({
isVisible,
fromView,
onRequestClose,
onReply,
onDelete,
onCopy,
canCopy,
}) => {
return (
<Popover
useNativeDriver
isVisible={!!isVisible}
popoverStyle={styles.messageMenuPop}
arrowStyle={{width: wp(40)}}
from={fromView}
onRequestClose={onRequestClose}>
<MessageMenuItem
opacity="0.35"
line
onPress={onReply}
text="Reply"
Icon={Reply}
/>
{canCopy && (
<MessageMenuItem line onPress={onCopy} text="Copy" Icon={Copy} />
)}
<MessageMenuItem
opacity="0.35"
onPress={onDelete}
text="Delete"
Icon={DisabledAlt}
/>
</Popover>
);
},
);
const ChatFooter = ({conversationId}) => {
const replyId = useMemoSelector(
`conversations.${conversationId}`,
s => s?.replyId,
);
return <View style={[styles.footer, replyId && styles.replyFooter]} />;
};
const ChatBottom = fastMemo(
({toggleDeleteMenu, conversationId, otherUserId, userId}) => {
const {useIsSelecting, reset, useMemoSelector, selector} = useSelected({
name: 'chat-screen-selects',
unmount: true,
});
const selecting = useIsSelecting();
const _count = useMemoSelector(
selector,
state => Object.keys(state).length,
);
if (selecting) {
return (
<Row flex={1} p={wp(20)} justify="space-between">
<Button
textProp={{
langId: 'txt_delete',
color: '#FF1010',
fontSize: 21,
bold: true,
}}
onPress={() => toggleDeleteMenu(true)}
/>
<Text fontSize={21} langId="count_selecetd" values={{_count}} />
<Button
textProp={{langId: 'reg.cancel', color: '#106FFF', fontSize: 21}}
onPress={() => reset()}
/>
</Row>
);
} else {
return (
<MessageInput
conversationId={conversationId}
otherUserId={otherUserId}
userId={userId}
/>
);
}
},
);
export const MessageInput = fastMemo(
({userId, conversationId, otherUserId}) => {
const ref = useRef();
const dispatch = useDispatch();
const setState = useSetState(`conversations.${conversationId}`);
const state = useMemoSelector(`conversations.${conversationId}`) || {};
const {text, replyId} = state;
const setText = text => setState({text});
const onSend = useCallback(
msg => {
setState({
replyId: null,
text: undefined,
image: null,
});
dispatch(addMsg({msg, otherUserId, conversationId}));
queue.addJob('message.upload', {message: msg});
},
[otherUserId, conversationId],
);
const sendMessage = ({image, message, videos, ...params} = {}) => {
const {text} = state;
if (empty([videos, message, text, image], 'and')) {
return;
}
ref.current?.clear();
const rand = `pending-${Math.random()}`;
onSend({
...params,
videos,
image,
reply_id: replyId && replyId,
reply: replyId && {
...store?.getState().msg.msgs[replyId],
},
reply_type: replyId ? 'App\\Models\\Message' : undefined,
id: rand,
_id: rand,
message: message || text,
isSender: true,
created_at: new Date(),
conversation_id: conversationId || `${userId}-${otherUserId}`,
notSent: true,
sending: true,
user_id: userId,
other_user_id: otherUserId,
user: {_id: userId},
});
};
const onCancel = useCallback(() => setState({replyId: null}), [setState]);
return (
<View style={styles.chatInput}>
{replyId && (
<InputReply
conversationId={conversationId}
reply={replyId}
userId={userId}
onCancel={onCancel}
style={styles.InputReply}
rapperStyle={styles.rapperStyle}
/>
)}
<Row style={styles.MessageInput}>
<TextInput
ref={ref}
onChangeText={setText}
value={text}
multiline
placeholderTextColor="#9C9696"
style={styles.input}
placeholder="Type a message..."
/>
{text?.length > 0 ? (
<Button
color="#106FFF"
onPress={() => sendMessage()}
style={styles.sendBtn}>
<SendIcon width={22.38} height={22.38} fill="#fff" />
</Button>
) : (
<>
<CameraBtn sendMessage={sendMessage} />
<GalleryBtn sendMessage={sendMessage} />
</>
)}
</Row>
</View>
);
},
);
const CameraBtn = ({sendMessage}) => {
const {pop} = useNavigation();
const onBackEventName = useCameraEvent(({media}) => {
pop(2);
sendMessage(splitMedia([media]));
});
const openCamera = async () => navigate('Camera', {onBackEventName});
return (
<Button
iconProp={{stroke: '#A8A8A8', fill: '#fff'}}
Icon={Camera}
onPress={openCamera}
width={wp(41)}
height={wp(41)}
curve={20}
color="#fff"
align="center"
justify="center"
/>
);
};
const GalleryBtn = ({sendMessage}) => {
const {pick} = usePicker({
mediaType: 'photo',
});
const openMedia = useCallback(async () => {
try {
const medias = await pick();
sendMessage(splitMedia(medias));
} catch (e) {}
}, []);
return (
<Button
iconProp={{stroke: '#A8A8A8', fill: '#fff'}}
Icon={GalleryIcon}
width={wp(41)}
height={wp(41)}
curve={20}
color="#fff"
align="center"
justify="center"
onPress={() => openMedia()}
/>
);
};
const splitMedia = medias => {
const mediaCont = {
videos: [],
};
medias.map(media => {
switch (mediaType(media)) {
case 'video':
mediaCont.videos.push(toVideo(media));
break;
case 'image':
mediaCont.image = `data:${media.mime};base64,${media.data}`;
break;
}
});
return mediaCont;
};
const InputReply = ({
style,
userId,
rapperStyle,
onCancel,
conversationId,
reply,
}) => {
const {message, sender, user_id, text, image, videos, type} = useMemoSelector(
state => selectMessage(state, conversationId, reply),
({
message,
text,
image,
videos,
type,
user_id,
sender: {name} = {},
} = {}) => ({
message,
text,
image,
videos,
type,
user_id,
sender: {name},
}),
);
return (
<View style={[style]}>
<ReplyRapper
numberOfLines={1}
type={message ? 'Message' : 'Story'}
user={userId === user_id}
user_name={userId !== user_id && sender?.name}
imageStyle={styles.replyImg}
replyUserStyle={styles.replyUser}
style={rapperStyle}
{...{message, text, image, videos, type}}
/>
<Button
p={wp(5)}
Icon={CancelIcon}
onPress={onCancel}
style={styles.CancelIcon}
/>
</View>
);
};
/* Menus */
const getDeleteMenus = (func, mine = true) => {
let menu = [
{text: 'Delete for everyone', color: '#FF0000', onPress: () => func(true)},
{text: 'Delete for me', color: '#FF0000', onPress: () => func()},
];
if (!mine) {
menu = [{text: 'Delete for me', color: '#FF0000', onPress: () => func()}];
}
return menu;
};
export const DeleteMenu = ({
isVisible,
getSelectState,
toggleMenu,
deleteMessage,
}) => {
const menus = useMemo(
() =>
// select if messages doesnt contain other user message
getDeleteMenus(
deleteMessage,
getSelectState(s => values(s)?.find(sender => !sender)),
),
[isVisible],
);
return (
<Menu
isVisible={isVisible}
cancelColor="#51A4F7"
menus={menus}
toggleMenu={toggleMenu}
/>
);
};
const MessageMenuItem = ({onPress, opacity, Icon, text, line}) => (
<Button
p={wp(15)}
flexDirection="row"
onPress={onPress}
align="center"
style={[line && styles.line]}>
<Icon width={wp(25)} height={hp(25)} fill="#000" opacity={opacity} />
<Text left={5} fontSize={17}>
{text}
</Text>
</Button>
);
const styles = StyleSheet.create({
selected: {
backgroundColor: '#0C67F0',
borderWidth: 0,
},
select: {
width: wp(25),
height: wp(25),
borderRadius: 25,
marginLeft: wp(5),
borderWidth: 1,
borderColor: '#707070',
alignItems: 'center',
justifyContent: 'center',
},
replyUser: {marginBottom: 10},
replyImg: {marginRight: 10},
messages: {
flex: 1,
},
messageMenuPop: {borderRadius: 10, minWidth: wp(138)},
chatInput: {flex: 1},
InputReply: {
flex: 1,
flexDirection: 'row',
borderLeftWidth: 10,
borderLeftColor: colors.primary,
backgroundColor: '#f0f0f0',
minHeight: hp(64),
// fontSize: 14,
},
rapperStyle: {
backgroundColor: '#f0f0f0',
},
CancelIcon: {
marginLeft: 'auto',
margin: 10,
},
MessageInput: {
flex: 1,
alignItems: 'center',
backgroundColor: '#F7F7F7',
padding: 10,
},
sendBtn: {
width: 38,
height: 38,
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center',
},
line: {
borderBottomWidth: 1,
borderBottomColor: 'rgba(112, 112, 112, 0.16)',
},
input: {
flex: 1,
fontSize: 15,
color: '#382C2C',
paddingVertical: hp(10),
paddingHorizontal: wp(10),
// borderWidth: 0.1,
// borderColor: '#707070',
borderRadius: wp(29),
backgroundColor: '#fff',
minHeight: Platform.select({ios: hp(40)}),
height: 'auto',
maxHeight: hp(100),
},
dayTimeText: {
color: '#000',
backgroundColor: '#E6F3F6',
textAlign: 'center',
fontSize: 12,
padding: 5,
fontFamily: 'HelveticaNeue',
borderRadius: 5,
},
dayTime: {},
footer: {marginTop: hp(20)},
replyFooter: {marginTop: Platform.select({ios: hp(120), android: hp(130)})},
scrollToBottomStyle: {opacity: 1, backgroundColor: '#F0F0F0'},
});
@myckhel
Copy link
Author

myckhel commented Mar 25, 2021

I hope you understand what i was doing.
shared Messages.js file because it contains the input which displays the reply.

@the-wrong-guy
Copy link

the-wrong-guy commented Jul 30, 2021

I hope you understand what i was doing.
shared Messages.js file because it contains the input which displays the reply.

hey, why do you have two message.js ? And can show the implementation cause other files are missing in the above message.js, also I saw your snack example but the right side swipe is not working

@myckhel
Copy link
Author

myckhel commented Jul 30, 2021

hey, why do you have two message.js ?

They are different
one is Messages.js and other is Message.js

other files are missing in the above message.js

Missing files are not necessarily needed.

snack example but the right side swipe is not working

right swipe should be working.
swiping right will reply to the message swiped on.

it will be better to go for the snack example.

@the-wrong-guy
Copy link

you can make right swipe to work only if you enable it.

But you didn't make any right swipe action, I think I have to make it myself

@myckhel
Copy link
Author

myckhel commented Jul 30, 2021

sorry i meant left action.
i will update my comment

@the-wrong-guy
Copy link

sorry i meant left action.
i will update my comment

If you can, can you explain how it works and why you have to use redux?

@fukemy
Copy link

fukemy commented Apr 19, 2022

hi bro can u provide more class? Thanks so much


import FastImage from '../../theme/FastImage';
import LinearGradient from 'react-native-linear-gradient';
import {fastMemo} from '../../../func';
import DateTime from '../../DateTime';
import {ImageOrNot} from '../../../func/helpers';
import {Button, Text, Row} from '../../theme';
import {Send as SendIcon} from '../../Icons';
import {useMessage, useConversationEventType} from '../../../redux/msg/hooks';

@myckhel
Copy link
Author

myckhel commented Apr 19, 2022

Oh i can't really.

The missing files are just ones you can create urself and import

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment