Fix reaction burst skin tone variations

This commit is contained in:
ayumi-signal 2024-04-24 16:08:43 -07:00 committed by GitHub
parent ae4fa04e95
commit 1834c09b28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 45 deletions

View file

@ -18,7 +18,7 @@ import { CallReactionBurstEmoji } from './CallReactionBurstEmoji';
const LIFETIME = 3000; const LIFETIME = 3000;
export type CallReactionBurstType = { export type CallReactionBurstType = {
value: string; values: Array<string>;
}; };
type CallReactionBurstStateType = CallReactionBurstType & { type CallReactionBurstStateType = CallReactionBurstType & {
@ -124,10 +124,10 @@ export function CallReactionBurstProvider({
<CallReactionBurstContext.Provider value={contextValue}> <CallReactionBurstContext.Provider value={contextValue}>
{createPortal( {createPortal(
<div className="CallReactionBursts"> <div className="CallReactionBursts">
{bursts.map(({ value, key }) => ( {bursts.map(({ values, key }) => (
<CallReactionBurstEmoji <CallReactionBurstEmoji
key={key} key={key}
value={value} values={values}
onAnimationEnd={() => hideBurst(key)} onAnimationEnd={() => hideBurst(key)}
/> />
))} ))}

View file

@ -8,7 +8,7 @@ import { v4 as uuid } from 'uuid';
import { Emojify } from './conversation/Emojify'; import { Emojify } from './conversation/Emojify';
export type PropsType = { export type PropsType = {
value: string; values: Array<string>;
onAnimationEnd?: () => unknown; onAnimationEnd?: () => unknown;
}; };
@ -25,14 +25,17 @@ type AnimationConfig = {
velocity: number; velocity: number;
}; };
export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element { // values is an array of emojis, which is useful when bursting multi skin tone set of
// emojis to get the correct representation
export function CallReactionBurstEmoji({ values }: PropsType): JSX.Element {
const [toY, setToY] = React.useState<number>(0); const [toY, setToY] = React.useState<number>(0);
const fromY = -50; const fromY = -50;
const generateEmojiProps = React.useCallback(() => { const generateEmojiProps = React.useCallback(
(index: number) => {
return { return {
key: uuid(), key: uuid(),
value, value: values[index % values.length],
springConfig: { springConfig: {
mass: random(10, 20), mass: random(10, 20),
tension: random(45, 60), tension: random(45, 60),
@ -49,7 +52,9 @@ export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element {
fromRotate: random(-45, 45), fromRotate: random(-45, 45),
toRotate: random(-45, 45), toRotate: random(-45, 45),
}; };
}, [fromY, toY, value]); },
[fromY, toY, values]
);
// Calculate target Y position before first render. Emojis need to animate Y upwards // Calculate target Y position before first render. Emojis need to animate Y upwards
// by the value of the container's top, plus the emoji's maximum height. // by the value of the container's top, plus the emoji's maximum height.
@ -59,12 +64,12 @@ export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element {
const { top } = containerRef.current.getBoundingClientRect(); const { top } = containerRef.current.getBoundingClientRect();
const calculatedToY = -top; const calculatedToY = -top;
setToY(calculatedToY); setToY(calculatedToY);
setEmojis([{ ...generateEmojiProps(), toY: calculatedToY }]); setEmojis([{ ...generateEmojiProps(0), toY: calculatedToY }]);
} }
}, [generateEmojiProps]); }, [generateEmojiProps]);
const [emojis, setEmojis] = React.useState<Array<AnimatedEmojiProps>>([ const [emojis, setEmojis] = React.useState<Array<AnimatedEmojiProps>>([
generateEmojiProps(), generateEmojiProps(0),
]); ]);
React.useEffect(() => { React.useEffect(() => {
@ -74,14 +79,14 @@ export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element {
if (emojiCount + 1 >= NUM_EMOJIS) { if (emojiCount + 1 >= NUM_EMOJIS) {
clearInterval(timer); clearInterval(timer);
} }
return [...curEmojis, generateEmojiProps()]; return [...curEmojis, generateEmojiProps(emojiCount)];
}); });
}, DELAY_BETWEEN_EMOJIS); }, DELAY_BETWEEN_EMOJIS);
return () => { return () => {
clearInterval(timer); clearInterval(timer);
}; };
}, [fromY, toY, value, generateEmojiProps]); }, [fromY, toY, values, generateEmojiProps]);
return ( return (
<div className="CallReactionBurstEmoji" ref={containerRef}> <div className="CallReactionBurstEmoji" ref={containerRef}>

View file

@ -653,9 +653,9 @@ export function GroupCallReactions(): JSX.Element {
}) })
); );
const activeCall = useReactionsEmitter( const activeCall = useReactionsEmitter({
props.activeCall as ActiveGroupCallType activeCall: props.activeCall as ActiveGroupCallType,
); });
return <CallScreen {...props} activeCall={activeCall} />; return <CallScreen {...props} activeCall={activeCall} />;
} }
@ -670,11 +670,30 @@ export function GroupCallReactionsSpam(): JSX.Element {
}) })
); );
const activeCall = useReactionsEmitter( const activeCall = useReactionsEmitter({
props.activeCall as ActiveGroupCallType, activeCall: props.activeCall as ActiveGroupCallType,
250 frequency: 250,
});
return <CallScreen {...props} activeCall={activeCall} />;
}
export function GroupCallReactionsSkinTones(): JSX.Element {
const remoteParticipants = allRemoteParticipants.slice(0, 3);
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants,
viewMode: CallViewMode.Overflow,
})
); );
const activeCall = useReactionsEmitter({
activeCall: props.activeCall as ActiveGroupCallType,
frequency: 500,
emojis: ['👍', '👍🏻', '👍🏼', '👍🏽', '👍🏾', '👍🏿', '❤️', '😂', '😮', '😢'],
});
return <CallScreen {...props} activeCall={activeCall} />; return <CallScreen {...props} activeCall={activeCall} />;
} }
@ -701,11 +720,17 @@ export function GroupCallReactionsManyInOrder(): JSX.Element {
return <CallScreen {...props} />; return <CallScreen {...props} />;
} }
function useReactionsEmitter( function useReactionsEmitter({
activeCall: ActiveGroupCallType, activeCall,
frequency = 2000, frequency = 2000,
removeAfter = 5000 removeAfter = 5000,
) { emojis = DEFAULT_PREFERRED_REACTION_EMOJI,
}: {
activeCall: ActiveGroupCallType;
frequency?: number;
removeAfter?: number;
emojis?: Array<string>;
}) {
const [call, setCall] = React.useState(activeCall); const [call, setCall] = React.useState(activeCall);
React.useEffect(() => { React.useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
@ -725,7 +750,7 @@ function useReactionsEmitter(
{ {
timestamp: timeNow, timestamp: timeNow,
demuxId, demuxId,
value: sample(DEFAULT_PREFERRED_REACTION_EMOJI) as string, value: sample(emojis) as string,
}, },
]; ];
@ -736,7 +761,7 @@ function useReactionsEmitter(
}); });
}, frequency); }, frequency);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [frequency, removeAfter, call]); }, [emojis, frequency, removeAfter, call]);
return call; return call;
} }

View file

@ -87,6 +87,7 @@ import {
} from './CallReactionBurst'; } from './CallReactionBurst';
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall'; import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
import { assertDev } from '../util/assert'; import { assertDev } from '../util/assert';
import { emojiToData } from './emoji/lib';
export type PropsType = { export type PropsType = {
activeCall: ActiveCallType; activeCall: ActiveCallType;
@ -1048,7 +1049,13 @@ function useReactionsToast(props: UseReactionsToastType): void {
const reactionsShown = useRef< const reactionsShown = useRef<
Map< Map<
string, string,
{ value: string; isBursted: boolean; expireAt: number; demuxId: number } {
value: string;
originalValue: string;
isBursted: boolean;
expireAt: number;
demuxId: number;
}
> >
>(new Map()); >(new Map());
const burstsShown = useRef<Map<string, number>>(new Map()); const burstsShown = useRef<Map<string, number>>(new Map());
@ -1094,8 +1101,13 @@ function useReactionsToast(props: UseReactionsToastType): void {
recentBurstTime && recentBurstTime &&
recentBurstTime + REACTIONS_BURST_TRAILING_WINDOW > time recentBurstTime + REACTIONS_BURST_TRAILING_WINDOW > time
); );
// Normalize skin tone emoji to calculate burst threshold, but save original
// value to show in the burst animation
const emojiData = emojiToData(value);
const normalizedValue = emojiData?.unified ?? value;
reactionsShown.current.set(key, { reactionsShown.current.set(key, {
value, value: normalizedValue,
originalValue: value,
isBursted, isBursted,
expireAt: timestamp + REACTIONS_BURST_WINDOW, expireAt: timestamp + REACTIONS_BURST_WINDOW,
demuxId, demuxId,
@ -1158,6 +1170,7 @@ function useReactionsToast(props: UseReactionsToastType): void {
} }
burstsShown.current.set(value, time); burstsShown.current.set(value, time);
const values: Array<string> = [];
reactionKeys.forEach(key => { reactionKeys.forEach(key => {
const reactionShown = reactionsShown.current.get(key); const reactionShown = reactionsShown.current.get(key);
if (!reactionShown) { if (!reactionShown) {
@ -1165,8 +1178,9 @@ function useReactionsToast(props: UseReactionsToastType): void {
} }
reactionShown.isBursted = true; reactionShown.isBursted = true;
values.push(reactionShown.originalValue);
}); });
showBurst({ value }); showBurst({ values });
if (burstsShown.current.size >= REACTIONS_BURST_MAX_IN_SHORT_WINDOW) { if (burstsShown.current.size >= REACTIONS_BURST_MAX_IN_SHORT_WINDOW) {
break; break;