Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean dynamic scaling #331

Merged
merged 2 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 57 additions & 27 deletions src/components/PlayerTiles/AdditionTile/AdditionTile.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { useEffect, useState } from 'react';
import { StyleSheet, Dimensions, LayoutChangeEvent } from 'react-native';
import { StyleSheet, LayoutChangeEvent } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withDelay,
} from 'react-native-reanimated';

import { animationDuration, calcPlayerFontSize, calcScoreLengthRatio } from './Helpers';
import { animationDuration, calculateFontSize } from './Helpers';
import ScoreBefore from './ScoreBefore';
import ScoreAfter from './ScoreAfter';
import ScoreRound from './ScoreRound';
Expand All @@ -31,60 +31,90 @@ const AdditionTile: React.FunctionComponent<Props> = ({
maxHeight,
index
}) => {
const [w, setW] = useState(1);
const [h, setH] = useState(1);
// Tile width and height
const [tileWidth, setTileWidth] = useState(1);
const [tileHeight, setTileHeight] = useState(1);

// Animation values
const sharedScale = useSharedValue(1);
const sharedOpacity = useSharedValue(0);

// Animation styles for resizing due to text changes
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [{ scale: sharedScale.value }],
opacity: sharedOpacity.value,
};
});


// Update tile width and height on layout change
const layoutHandler = (e: LayoutChangeEvent) => {
const { width, height } = e.nativeEvent.layout;
setH(height);
setW(width);
setTileHeight(height);
setTileWidth(width);
};

useEffect(() => {
const hs = maxWidth / w;
const vs = maxHeight / h;
const scoreLengthRatio = calcScoreLengthRatio(totalScore.toString().length);
const widthRatio = (900 + maxWidth) / (900 + Dimensions.get("window").width);

if (Math.min(hs, vs) > 0) {
const s = Math.min(widthRatio * scoreLengthRatio * hs, widthRatio * scoreLengthRatio * vs);
sharedScale.value = withDelay(animationDuration, withTiming(
Math.min(s, 3), { duration: animationDuration }
));
/*
* Calculate ratio of tile content to max width/height
* determined by the parent container
*/
const horizontalScaleRatio = maxWidth / tileWidth;
const verticalScaleRatio = maxHeight / tileHeight;

// Allow for padding by not scaling to full width/height
const maxTileCoverage = 0.8; // 80%

const minimumScale = Math.min(
horizontalScaleRatio * maxTileCoverage,
verticalScaleRatio * maxTileCoverage
);

if (minimumScale > 0) {
sharedScale.value = withDelay(
animationDuration,
withTiming(
minimumScale, { duration: animationDuration }
)
);
}

sharedOpacity.value = withDelay(100 + index * animationDuration / 2, withTiming(
1, { duration: animationDuration * 2 }
));
// Delay opacity animation to allow for scale animation to finish
// and to allow for the previous tile to finish animating for effect
const animationDelay = index * animationDuration / 2;

sharedOpacity.value = withDelay(
animationDelay,
withTiming(
1,
{ duration: animationDuration * 2 }
)
);
});

const playerNameFontSize = calcPlayerFontSize(playerName.length) * .8;
const playerNameFontSize = calculateFontSize(maxWidth, playerName.length);

const containerWidth = Math.min(maxWidth, maxHeight);

const dynamicPlayerStyles = {
fontSize: playerNameFontSize,
color: fontColor,
};

return (
<Animated.View style={[animatedStyles, { justifyContent: 'center' }]}
onLayout={layoutHandler}>
<Animated.Text style={[styles.name, { fontSize: playerNameFontSize, color: fontColor }]}
numberOfLines={1} >
<Animated.View style={[animatedStyles, { justifyContent: 'center' }]} onLayout={layoutHandler}>
<Animated.Text style={[styles.name, dynamicPlayerStyles]} numberOfLines={1}>
{playerName}
</Animated.Text>
<Animated.View
style={styles.scoreLineOne} >
<ScoreBefore roundScore={roundScore} totalScore={totalScore}
<ScoreBefore containerWidth={containerWidth} roundScore={roundScore} totalScore={totalScore}
fontColor={fontColor} />
<ScoreRound roundScore={roundScore} totalScore={totalScore}
<ScoreRound containerWidth={containerWidth} roundScore={roundScore} totalScore={totalScore}
fontColor={fontColor} />
</Animated.View>
<ScoreAfter roundScore={roundScore} totalScore={totalScore}
<ScoreAfter containerWidth={containerWidth} roundScore={roundScore} totalScore={totalScore}
fontColor={fontColor} />
</Animated.View>
);
Expand Down
80 changes: 31 additions & 49 deletions src/components/PlayerTiles/AdditionTile/Helpers.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,47 @@
import { ZoomIn, ZoomOut } from 'react-native-reanimated';
import { Layout, Easing } from 'react-native-reanimated';
import { withTiming } from 'react-native-reanimated';

/**
* The duration of the animation in milliseconds.
*/
export const animationDuration = 200;

/**
* The duration of the entering animation in milliseconds.
*/
export const enteringAnimation = ZoomIn.duration(animationDuration);

/**
* The duration of the exiting animation in milliseconds.
*/
export const exitingAnimation = ZoomOut.duration(animationDuration);

/**
* The easing and duration of the layout animation.
*/
export const layoutAnimation = Layout.easing(Easing.ease).duration(animationDuration);

import { withTiming } from 'react-native-reanimated';
/**
* Calculates the font size based on the maximum width and length of the text.
* @param containerWidth The maximum width of the text.
* @param stringLength The number of characters in the text.
* @returns The calculated font size.
*/
export const calculateFontSize = (containerWidth: number, stringLength: number) => {
const baseScale: number = Math.min(1 / stringLength * 200, 100);

export const calcPlayerFontSize = (length: number) => {
if (length <= 3) {
return 50;
} else if (length <= 4) {
return 40;
} else if (length <= 5) {
return 40;
} else if (length <= 6) {
return 46;
} else if (length <= 7) {
return 43;
} else if (length <= 8) {
return 40;
} else if (length <= 8) {
return 37;
} else {
return 34;
}
};
let widthFactor: number = containerWidth / 200;

export const calcFontSize = (length: number) => {
if (length <= 3) {
return 50 * calcScoreLengthRatio(length);
} else if (length <= 4) {
return 40 * calcScoreLengthRatio(length);
} else if (length <= 5) {
return 40 * calcScoreLengthRatio(length);
} else if (length <= 6) {
return 46 * calcScoreLengthRatio(length);
} else if (length <= 7) {
return 43 * calcScoreLengthRatio(length);
} else if (length <= 8) {
return 40 * calcScoreLengthRatio(length);
} else if (length <= 8) {
return 37 * calcScoreLengthRatio(length);
} else {
return 34 * calcScoreLengthRatio(length);
}
};
if (Number.isNaN(widthFactor)) { widthFactor = 1; }

export const calcScoreLengthRatio = (length: number) => {
if (length <= 3) {
return .8;
} else if (length <= 4) {
return .75;
} else if (length <= 5) {
return .7;
} else {
return .6;
}
return baseScale * widthFactor;
};

/**
* The ZoomOutFadeOut animation.
* @returns The initial values and animations for the ZoomOutFadeOut animation.
*/
export const ZoomOutFadeOut = () => {
'worklet';
const animations = {
Expand Down
11 changes: 6 additions & 5 deletions src/components/PlayerTiles/AdditionTile/ScoreAfter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import {
} from 'react-native-reanimated';
import { StyleSheet } from 'react-native';

import { calcFontSize, animationDuration, enteringAnimation, ZoomOutFadeOut } from './Helpers';
import { calculateFontSize, animationDuration, enteringAnimation, ZoomOutFadeOut } from './Helpers';

interface Props {
roundScore: number;
totalScore: number;
fontColor: string;
containerWidth: number;
}

const ScoreAfter: React.FunctionComponent<Props> = ({ roundScore, totalScore, fontColor }) => {
const fontSize = useSharedValue(calcFontSize(totalScore.toString().length));
const ScoreAfter: React.FunctionComponent<Props> = ({ containerWidth, roundScore, totalScore, fontColor }) => {
const fontSize = useSharedValue(calculateFontSize(containerWidth, totalScore.toString().length));
const opacity = useSharedValue(1);

const animatedStyles = useAnimatedStyle(() => {
Expand All @@ -28,12 +29,12 @@ const ScoreAfter: React.FunctionComponent<Props> = ({ roundScore, totalScore, fo

useEffect(() => {
fontSize.value = withTiming(
roundScore == 0 ? 1 : calcFontSize(totalScore.toString().length), { duration: animationDuration },
roundScore == 0 ? 1 : calculateFontSize(containerWidth, totalScore.toString().length), { duration: animationDuration },
);
opacity.value = withTiming(
roundScore == 0 ? 0 : 1, { duration: animationDuration },
);
}, [roundScore]);
}, [roundScore, containerWidth]);

return (
<Animated.View entering={enteringAnimation} exiting={ZoomOutFadeOut}>
Expand Down
36 changes: 25 additions & 11 deletions src/components/PlayerTiles/AdditionTile/ScoreBefore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,50 @@ import {
withTiming
} from 'react-native-reanimated';

import { calcFontSize, animationDuration, enteringAnimation } from './Helpers';
import { calculateFontSize, animationDuration, enteringAnimation } from './Helpers';

interface Props {
roundScore: number;
totalScore: number;
fontColor: string;
containerWidth: number;
}

const ScoreBefore: React.FunctionComponent<Props> = ({ roundScore, totalScore, fontColor }) => {
const firstRowLength = (roundScore == 0 ? 0 : roundScore.toString().length + 3) + totalScore.toString().length;
const d = totalScore - roundScore;
const ScoreBefore: React.FunctionComponent<Props> = ({
containerWidth,
roundScore,
totalScore,
fontColor
}) => {
// Determine the length of the first row of the score
const firstRowLength = (
roundScore == 0 ? 0 : roundScore.toString().length + 3
) + totalScore.toString().length;

const fontSize = useSharedValue(calcFontSize(firstRowLength));
const scoreBefore = totalScore - roundScore;

const fontSize = useSharedValue(calculateFontSize(containerWidth, firstRowLength));
const fontOpacity = useSharedValue(100);

const animatedStyles = useAnimatedStyle(() => {
return {
fontSize: roundScore == 0 ? fontSize.value : fontSize.value * .8,
fontSize: fontSize.value,
fontWeight: roundScore == 0 ? 'bold' : 'normal',
opacity: fontOpacity.value / 100,
};
});

useEffect(() => {
fontSize.value = withTiming(
calcFontSize(firstRowLength), { duration: animationDuration }
calculateFontSize(containerWidth, firstRowLength),
{ duration: animationDuration }
);

fontOpacity.value = withTiming(
roundScore == 0 ? 100 : 75, { duration: animationDuration }
roundScore == 0 ? 100 : 75,
{ duration: animationDuration }
);
}, [roundScore]);
}, [roundScore, containerWidth]);

return (
<Animated.View entering={enteringAnimation}>
Expand All @@ -44,8 +58,8 @@ const ScoreBefore: React.FunctionComponent<Props> = ({ roundScore, totalScore, f
style={[animatedStyles, {
fontVariant: ['tabular-nums'],
color: fontColor,
}]} >
{d}
}]}>
{scoreBefore}
</Animated.Text>
</Animated.View>
);
Expand Down
12 changes: 7 additions & 5 deletions src/components/PlayerTiles/AdditionTile/ScoreRound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ import {
withTiming
} from 'react-native-reanimated';

import { calcFontSize, animationDuration } from './Helpers';
import { calculateFontSize, animationDuration } from './Helpers';

interface Props {
roundScore: number;
totalScore: number;
fontColor: string;
containerWidth: number;
}

const ScoreRound: React.FunctionComponent<Props> = ({ roundScore, totalScore, fontColor }) => {
const ScoreRound: React.FunctionComponent<Props> = ({ containerWidth, roundScore, totalScore, fontColor }) => {
const firstRowLength = (roundScore == 0 ? 0 : roundScore.toString().length + 3) + totalScore.toString().length;
const fontSizeRound = useSharedValue(calcFontSize(firstRowLength));
const fontSizeRound = useSharedValue(calculateFontSize(containerWidth, firstRowLength));
const animatedStyles = useAnimatedStyle(() => {
return {
fontSize: fontSizeRound.value,
Expand All @@ -27,9 +28,10 @@ const ScoreRound: React.FunctionComponent<Props> = ({ roundScore, totalScore, fo

useEffect(() => {
fontSizeRound.value = withTiming(
calcFontSize(firstRowLength) * .8, { duration: animationDuration }
calculateFontSize(containerWidth, firstRowLength), { duration: animationDuration }
);
}, [roundScore]);

}, [roundScore, containerWidth]);

if (roundScore == 0) {
return <></>;
Expand Down