Skip to content

Instantly share code, notes, and snippets.

@vishalnarkhede
Last active November 1, 2020 09:39
Show Gist options
  • Save vishalnarkhede/73d136ba481deae0f0559f8778fa2675 to your computer and use it in GitHub Desktop.
Save vishalnarkhede/73d136ba481deae0f0559f8778fa2675 to your computer and use it in GitHub Desktop.
// src/components/MessageFooter.js
import React from 'react';
import {StyleSheet, View, TouchableOpacity, Text} from 'react-native';
import {SVGIcon} from './SVGIcon';
import {useTheme} from '@react-navigation/native';
import {ReactionPicker} from './ReactionPicker';
export const MessageFooter = (props) => {
const {dark} = useTheme();
const {openReactionPicker} = props;
return (
<View style={styles.reactionListContainer}>
{props.message.latest_reactions &&
props.message.latest_reactions.length > 0 &&
renderReactions(
props.message.latest_reactions,
props.message.own_reactions,
props.supportedReactions,
props.message.reaction_counts,
props.handleReaction,
)}
<ReactionPicker {...props} />
{props.message.latest_reactions &&
props.message.latest_reactions.length > 0 && (
<TouchableOpacity
onPress={openReactionPicker}
style={[
styles.reactionPickerContainer,
{
backgroundColor: dark ? '#313538' : '#F0F0F0',
},
]}>
<SVGIcon height="18" width="18" type="emoji" />
</TouchableOpacity>
)}
</View>
);
};
export const renderReactions = (
reactions,
ownReactions = [],
supportedReactions,
reactionCounts,
handleReaction,
) => {
const reactionsByType = {};
const ownReactionTypes = ownReactions.map((or) => or.type);
reactions &&
reactions.forEach((item) => {
if (reactions[item.type] === undefined) {
return (reactionsByType[item.type] = [item]);
} else {
return (reactionsByType[item.type] = [
...(reactionsByType[item.type] || []),
item,
]);
}
});
const emojiDataByType = {};
supportedReactions.forEach((e) => (emojiDataByType[e.id] = e));
const reactionTypes = supportedReactions.map((e) => e.id);
return Object.keys(reactionsByType).map((type, index) =>
reactionTypes.indexOf(type) > -1 ? (
<ReactionItem
key={index}
type={type}
handleReaction={handleReaction}
reactionCounts={reactionCounts}
emojiDataByType={emojiDataByType}
ownReactionTypes={ownReactionTypes}
/>
) : null,
);
};
const ReactionItem = ({
type,
handleReaction,
reactionCounts,
emojiDataByType,
ownReactionTypes,
}) => {
const {dark} = useTheme();
const isOwnReaction = ownReactionTypes.indexOf(type) > -1;
return (
<TouchableOpacity
onPress={() => {
handleReaction(type);
}}
key={type}
style={[
styles.reactionItemContainer,
{
borderColor: dark
? isOwnReaction
? '#313538'
: '#1E1D21'
: isOwnReaction
? '#0064e2'
: 'transparent',
backgroundColor: dark
? isOwnReaction
? '#194B8A'
: '#1E1D21'
: isOwnReaction
? '#d6ebff'
: '#F0F0F0',
},
]}>
<Text
style={[
styles.reactionItem,
{
color: dark ? '#CFD4D2' : '#0064c2',
},
]}>
{emojiDataByType[type].icon} {reactionCounts[type]}
</Text>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
reactionListContainer: {
flexDirection: 'row',
alignSelf: 'flex-start',
alignItems: 'center',
marginTop: 5,
marginBottom: 10,
marginLeft: 10,
flexWrap: 'wrap',
},
reactionItemContainer: {
borderWidth: 1,
padding: 4,
paddingLeft: 8,
paddingRight: 8,
borderRadius: 17,
marginRight: 6,
marginTop: 5,
},
reactionItem: {
fontSize: 16,
},
reactionPickerContainer: {
padding: 4,
paddingLeft: 8,
paddingRight: 6,
borderRadius: 10,
},
reactionPickerIcon: {
width: 19,
height: 19,
},
});
// src/components/ReactionPicker
import {useTheme} from '@react-navigation/native';
import React, {useEffect, useRef} from 'react';
import {
Modal,
View,
Text,
Animated,
TouchableOpacity,
SectionList,
StyleSheet,
} from 'react-native';
import {SCText} from './SCText';
import ReactNativeHaptic from 'react-native-haptic';
import {groupedSupportedReactions} from '../utils/supportedReactions';
export const ReactionPicker = (props) => {
const {dismissReactionPicker, handleReaction, reactionPickerVisible} = props;
const {colors} = useTheme();
const slide = useRef(new Animated.Value(-600)).current;
const reactionPickerExpanded = useRef(false);
const _dismissReactionPicker = () => {
reactionPickerExpanded.current = false;
Animated.timing(slide, {
toValue: -600,
duration: 100,
useNativeDriver: false,
}).start(() => {
dismissReactionPicker();
});
};
const _handleReaction = (type) => {
ReactNativeHaptic && ReactNativeHaptic.generate('impact');
reactionPickerExpanded.current = false;
Animated.timing(slide, {
toValue: -600,
duration: 100,
useNativeDriver: false,
}).start(() => {
handleReaction(type);
});
};
useEffect(() => {
if (reactionPickerVisible) {
ReactNativeHaptic && ReactNativeHaptic.generate('impact');
setTimeout(() => {
Animated.timing(slide, {
toValue: -300,
duration: 100,
useNativeDriver: false,
}).start();
}, 200);
}
});
if (!reactionPickerVisible) {
return null;
}
return (
<Modal
animationType="fade"
onRequestClose={_dismissReactionPicker}
testID="reaction-picker"
transparent
visible>
<TouchableOpacity
style={styles.overlay}
activeOpacity={1}
leftAlign
onPress={() => {
_dismissReactionPicker();
}}
/>
<Animated.View
style={[
{
bottom: slide,
},
styles.animatedContainer,
]}
activeOpacity={1}
leftAlign>
<View
style={[
{
backgroundColor: colors.background,
},
styles.pickerContainer,
]}>
<View style={styles.listCOntainer}>
<SectionList
onScrollBeginDrag={() => {
reactionPickerExpanded.current = true;
Animated.timing(slide, {
toValue: 0,
duration: 300,
useNativeDriver: false,
}).start();
}}
style={{height: 600, width: '100%'}}
onScroll={(event) => {
if (!reactionPickerExpanded.current) {
return;
}
if (event.nativeEvent.contentOffset.y <= 0) {
reactionPickerExpanded.current = false;
Animated.timing(slide, {
toValue: -300,
duration: 300,
useNativeDriver: false,
}).start();
}
}}
sections={groupedSupportedReactions}
renderSectionHeader={({section: {title}}) => (
<SCText
style={[
{
backgroundColor: colors.background,
},
styles.groupTitle,
]}>
{title}
</SCText>
)}
renderItem={({item}) => {
return (
<View style={styles.reactionsRow}>
{item.map(({icon, id}) => {
return (
<View
key={id}
testID={id}
style={styles.reactionsItemContainer}>
<Text
onPress={() => _handleReaction(id)}
testID={`${id}-reaction`}
style={styles.reactionsItem}>
{icon}
</Text>
</View>
);
})}
</View>
);
}}
/>
</View>
</View>
</Animated.View>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: {
width: '100%',
height: '100%',
alignSelf: 'flex-end',
alignItems: 'flex-start',
backgroundColor: 'rgba(0,0,0,0.7)',
},
animatedContainer: {
position: 'absolute',
backgroundColor: 'transparent',
width: '100%',
},
pickerContainer: {
flexDirection: 'column',
borderRadius: 15,
paddingHorizontal: 10,
},
listContainer: {
width: '100%',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
marginBottom: 20,
},
groupTitle: {
padding: 10,
paddingLeft: 13,
fontWeight: '200',
},
reactionsRow: {
width: '100%',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
marginTop: 3,
},
reactionsItemContainer: {
alignItems: 'center',
marginTop: -5,
},
reactionsItem: {
fontSize: 35,
margin: 5,
marginVertical: 5,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment