From 86bd5c5072c27d8bffbc59c49f5bb95f2c1036ac Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 11 Mar 2022 09:08:02 -0300 Subject: [PATCH] [Gekidou] animated reactions (#6035) * Add animation for reaction counter * Preserve reaction order while the component is mounted --- .../post/body/reactions/reaction.tsx | 66 ++++++---- .../post/body/reactions/reactions.tsx | 41 +++--- package-lock.json | 121 ++++++++++++++++++ package.json | 1 + 4 files changed, 186 insertions(+), 43 deletions(-) diff --git a/app/components/post_list/post/body/reactions/reaction.tsx b/app/components/post_list/post/body/reactions/reaction.tsx index 3d7535f90..cf3dac1fe 100644 --- a/app/components/post_list/post/body/reactions/reaction.tsx +++ b/app/components/post_list/post/body/reactions/reaction.tsx @@ -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)]} > - - - {count} - + + + + + + ); }; diff --git a/app/components/post_list/post/body/reactions/reactions.tsx b/app/components/post_list/post/body/reactions/reactions.tsx index be8e22a0e..134d409fe 100644 --- a/app/components/post_list/post/body/reactions/reactions.tsx +++ b/app/components/post_list/post/body/reactions/reactions.tsx @@ -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()); return {reactionsByName, highlightedReactions}; - }; + }, [sortedReactions]); const handleAddReactionToPost = (emoji: string) => { addReaction(serverUrl, postId, emoji); @@ -148,11 +156,12 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled, return ( { - Array.from(reactionsByName.keys()).map((r) => { + Array.from(sortedReactions).map((r) => { + const reaction = reactionsByName.get(r); return ( =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", diff --git a/package.json b/package.json index adda418a3..3f11344a3 100644 --- a/package.json +++ b/package.json @@ -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",