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'},
});
@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