[Gekidou] animated reactions (#6035)
* Add animation for reaction counter * Preserve reaction order while the component is mounted
This commit is contained in:
parent
7001e29567
commit
86bd5c5072
4 changed files with 186 additions and 43 deletions
|
|
@ -2,11 +2,13 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {Platform, Text} from 'react-native';
|
||||
import {View} from 'react-native';
|
||||
import AnimatedNumbers from 'react-native-animated-numbers';
|
||||
import {TouchableOpacity} from 'react-native-gesture-handler';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type ReactionProps = {
|
||||
count: number;
|
||||
|
|
@ -20,23 +22,30 @@ type ReactionProps = {
|
|||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
count: {
|
||||
color: theme.linkColor,
|
||||
marginLeft: 6,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
},
|
||||
countContainer: {marginRight: 5},
|
||||
countHighlight: {
|
||||
color: theme.buttonBg,
|
||||
},
|
||||
customEmojiStyle: {color: '#000'},
|
||||
emoji: {marginHorizontal: 5},
|
||||
highlight: {
|
||||
backgroundColor: changeOpacity(theme.buttonBg, 0.08),
|
||||
borderColor: theme.buttonBg,
|
||||
borderWidth: 1,
|
||||
},
|
||||
customEmojiStyle: {marginHorizontal: 3},
|
||||
highlight: {backgroundColor: changeOpacity(theme.linkColor, 0.1)},
|
||||
reaction: {
|
||||
alignItems: 'center',
|
||||
borderRadius: 2,
|
||||
borderColor: changeOpacity(theme.linkColor, 0.4),
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
flexDirection: 'row',
|
||||
height: 30,
|
||||
marginRight: 6,
|
||||
marginBottom: 5,
|
||||
marginTop: 10,
|
||||
paddingHorizontal: 6,
|
||||
paddingBottom: Platform.select({android: 2}),
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
marginBottom: 12,
|
||||
marginRight: 8,
|
||||
minWidth: 50,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -55,19 +64,22 @@ const Reaction = ({count, emojiName, highlight, onPress, onLongPress, theme}: Re
|
|||
delayLongPress={350}
|
||||
style={[styles.reaction, (highlight && styles.highlight)]}
|
||||
>
|
||||
<Emoji
|
||||
emojiName={emojiName}
|
||||
size={20}
|
||||
textStyle={{color: '#000'}}
|
||||
customEmojiStyle={styles.customEmojiStyle}
|
||||
testID={`reaction.emoji.${emojiName}`}
|
||||
/>
|
||||
<Text
|
||||
style={styles.count}
|
||||
testID={`reaction.emoji.${emojiName}.count`}
|
||||
>
|
||||
{count}
|
||||
</Text>
|
||||
<View style={styles.emoji}>
|
||||
<Emoji
|
||||
emojiName={emojiName}
|
||||
size={20}
|
||||
textStyle={styles.customEmojiStyle}
|
||||
testID={`reaction.emoji.${emojiName}`}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.countContainer}>
|
||||
<AnimatedNumbers
|
||||
includeComma={false}
|
||||
fontStyle={[styles.count, (highlight && styles.countHighlight)]}
|
||||
animateToNumber={count}
|
||||
animationDuration={450}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useRef} from 'react';
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {TouchableOpacity} from 'react-native-gesture-handler';
|
||||
|
|
@ -36,23 +36,22 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
reaction: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 2,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.3),
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
flexDirection: 'row',
|
||||
height: 30,
|
||||
height: 32,
|
||||
marginBottom: 12,
|
||||
marginRight: 6,
|
||||
marginBottom: 5,
|
||||
marginTop: 10,
|
||||
paddingVertical: 2,
|
||||
paddingVertical: 4,
|
||||
paddingHorizontal: 6,
|
||||
width: 40,
|
||||
width: 36,
|
||||
},
|
||||
reactionsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
alignContent: 'flex-start',
|
||||
marginTop: 12,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -61,12 +60,21 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const pressed = useRef(false);
|
||||
if (!reactions) {
|
||||
return null;
|
||||
}
|
||||
const [sortedReactions, setSortedReactions] = useState(new Set(reactions.map((r) => r.emojiName)));
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const buildReactionsMap = () => {
|
||||
useEffect(() => {
|
||||
// This helps keep the reactions in the same position at all times until unmounted
|
||||
const rs = reactions.map((r) => r.emojiName);
|
||||
const sorted = new Set([...sortedReactions]);
|
||||
const added = rs.filter((r) => !sorted.has(r));
|
||||
added.forEach(sorted.add, sorted);
|
||||
const removed = [...sorted].filter((s) => !rs.includes(s));
|
||||
removed.forEach(sorted.delete, sorted);
|
||||
setSortedReactions(sorted);
|
||||
}, [reactions]);
|
||||
|
||||
const buildReactionsMap = useCallback(() => {
|
||||
const highlightedReactions: string[] = [];
|
||||
|
||||
const reactionsByName = reactions.reduce((acc, reaction) => {
|
||||
|
|
@ -86,7 +94,7 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
}, new Map<string, ReactionModel[]>());
|
||||
|
||||
return {reactionsByName, highlightedReactions};
|
||||
};
|
||||
}, [sortedReactions]);
|
||||
|
||||
const handleAddReactionToPost = (emoji: string) => {
|
||||
addReaction(serverUrl, postId, emoji);
|
||||
|
|
@ -148,11 +156,12 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
return (
|
||||
<View style={styles.reactionsContainer}>
|
||||
{
|
||||
Array.from(reactionsByName.keys()).map((r) => {
|
||||
Array.from(sortedReactions).map((r) => {
|
||||
const reaction = reactionsByName.get(r);
|
||||
return (
|
||||
<Reaction
|
||||
key={r}
|
||||
count={reactionsByName.get(r)!.length}
|
||||
count={reaction!.length}
|
||||
emojiName={r}
|
||||
highlight={highlightedReactions.includes(r)}
|
||||
onPress={handleReactionPress}
|
||||
|
|
|
|||
121
package-lock.json
generated
121
package-lock.json
generated
|
|
@ -53,6 +53,7 @@
|
|||
"react-intl": "5.24.6",
|
||||
"react-native": "0.67.3",
|
||||
"react-native-android-open-settings": "1.3.0",
|
||||
"react-native-animated-numbers": "0.4.1",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-button": "3.0.1",
|
||||
"react-native-calendars": "1.1278.0",
|
||||
|
|
@ -8895,6 +8896,14 @@
|
|||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"dependencies": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
|
||||
|
|
@ -11491,6 +11500,11 @@
|
|||
"ua-parser-js": "^0.7.30"
|
||||
}
|
||||
},
|
||||
"node_modules/fbjs-css-vars": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
|
||||
},
|
||||
"node_modules/fbjs/node_modules/isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
|
|
@ -19506,6 +19520,55 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-android-open-settings/-/react-native-android-open-settings-1.3.0.tgz",
|
||||
"integrity": "sha512-h4FTWRtTLRVNS7RK4Bg2gAXe8G5bFZL8Jtmfk2rG2a/N/wJR+v1rAY2BxRkC48lQTqwQCIAQRbWgLVkJ8XmaLQ=="
|
||||
},
|
||||
"node_modules/react-native-animated-numbers": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-animated-numbers/-/react-native-animated-numbers-0.4.1.tgz",
|
||||
"integrity": "sha512-3ZnJ5XBipdPeET4vWh8X4YinGaspb28Lp9Aw/n39r57+8ApWp+0pRFeNobG4FBo6UzBjRmMydD7q+fAC+5LZsA==",
|
||||
"dependencies": {
|
||||
"react-native-gesture-handler": "^1.10.3",
|
||||
"react-native-reanimated": "^2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-native": ">=0.59",
|
||||
"react-native-gesture-handler": "1.10.3",
|
||||
"react-native-reanimated": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-animated-numbers/node_modules/fbjs": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz",
|
||||
"integrity": "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "^3.1.5",
|
||||
"fbjs-css-vars": "^1.0.0",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.30"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-animated-numbers/node_modules/promise": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"dependencies": {
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-animated-numbers/node_modules/react-native-gesture-handler": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
|
||||
"integrity": "sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw==",
|
||||
"dependencies": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"fbjs": "^3.0.0",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-background-timer": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz",
|
||||
|
|
@ -30926,6 +30989,14 @@
|
|||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"requires": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
|
||||
|
|
@ -32945,6 +33016,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"fbjs-css-vars": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
|
||||
},
|
||||
"figgy-pudding": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
|
||||
|
|
@ -39296,6 +39372,51 @@
|
|||
"resolved": "https://registry.npmjs.org/react-native-android-open-settings/-/react-native-android-open-settings-1.3.0.tgz",
|
||||
"integrity": "sha512-h4FTWRtTLRVNS7RK4Bg2gAXe8G5bFZL8Jtmfk2rG2a/N/wJR+v1rAY2BxRkC48lQTqwQCIAQRbWgLVkJ8XmaLQ=="
|
||||
},
|
||||
"react-native-animated-numbers": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-animated-numbers/-/react-native-animated-numbers-0.4.1.tgz",
|
||||
"integrity": "sha512-3ZnJ5XBipdPeET4vWh8X4YinGaspb28Lp9Aw/n39r57+8ApWp+0pRFeNobG4FBo6UzBjRmMydD7q+fAC+5LZsA==",
|
||||
"requires": {
|
||||
"react-native-gesture-handler": "^1.10.3",
|
||||
"react-native-reanimated": "^2.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"fbjs": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz",
|
||||
"integrity": "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==",
|
||||
"requires": {
|
||||
"cross-fetch": "^3.1.5",
|
||||
"fbjs-css-vars": "^1.0.0",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.30"
|
||||
}
|
||||
},
|
||||
"promise": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"requires": {
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"react-native-gesture-handler": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
|
||||
"integrity": "sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw==",
|
||||
"requires": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"fbjs": "^3.0.0",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-native-background-timer": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
"react-intl": "5.24.6",
|
||||
"react-native": "0.67.3",
|
||||
"react-native-android-open-settings": "1.3.0",
|
||||
"react-native-animated-numbers": "0.4.1",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-button": "3.0.1",
|
||||
"react-native-calendars": "1.1278.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue