vbea 1 jaar geleden
bovenliggende
commit
b32abbf040

+ 9 - 0
Strides-APP/app/skeleton/hooks/index.js

@@ -0,0 +1,9 @@
+export { useGetBones } from './useGetBones';
+export { useGetGradientEndDirection } from './useGetGradientEndDirection';
+export { useGetGradientSize } from './useGetGradientSize';
+export { useRenderBone } from './useRenderBone';
+export { useLayout } from './useLayout';
+export { useGetBoneStyles } from './useGetBoneStyles';
+export { useGetGradientTransform } from './useGetGradientTransform';
+export { useGetPositionRange } from './useGetPositionRange';
+export { useGetBoneDimensions } from './useGetBoneDimensions';

+ 21 - 0
Strides-APP/app/skeleton/hooks/useGetBoneDimensions.ts

@@ -0,0 +1,21 @@
+import { useCallback } from 'react';
+import type { ICustomViewStyle, IComponentSize } from '../constants';
+
+export const useGetBoneDimensions = (componentSize: IComponentSize) =>
+  useCallback(
+    (boneLayout: ICustomViewStyle) => {
+      'worklet';
+
+      return {
+        width:
+          typeof boneLayout.width === 'string'
+            ? componentSize.width
+            : boneLayout.width ?? 0,
+        height:
+          typeof boneLayout.height === 'string'
+            ? componentSize.height
+            : boneLayout.height ?? 0,
+      } as { width: number; height: number };
+    },
+    [componentSize],
+  );

+ 56 - 0
Strides-APP/app/skeleton/hooks/useGetBoneStyles.ts

@@ -0,0 +1,56 @@
+import { useCallback } from 'react';
+import { useGetBoneDimensions } from '../hooks';
+import {
+  ICustomViewStyle,
+  ISkeletonProps,
+  IComponentSize,
+  DEFAULT_CONFIG,
+} from '../constants';
+
+type UseGetBoneStylesProps = Pick<
+  ISkeletonProps,
+  'animationType' | 'animationDirection' | 'boneColor'
+> & {
+  boneLayout: ICustomViewStyle;
+};
+
+export const useGetBoneStyles = (componentSize: IComponentSize) => {
+  const getBoneDimensions = useGetBoneDimensions(componentSize);
+
+  return useCallback(
+    ({
+      animationDirection,
+      animationType,
+      boneLayout,
+      boneColor,
+    }: UseGetBoneStylesProps) => {
+      const { backgroundColor, borderRadius } = boneLayout;
+      const { width, height } = getBoneDimensions(boneLayout);
+
+      const boneStyle: ICustomViewStyle = {
+        width,
+        height,
+        borderRadius: borderRadius || DEFAULT_CONFIG.BORDER_RADIUS,
+        ...boneLayout,
+      };
+
+      if (animationType !== 'pulse') {
+        boneStyle.overflow = 'hidden';
+        boneStyle.backgroundColor = backgroundColor || boneColor;
+      }
+
+      if (
+        animationDirection === 'diagonalDownRight' ||
+        animationDirection === 'diagonalDownLeft' ||
+        animationDirection === 'diagonalTopRight' ||
+        animationDirection === 'diagonalTopLeft'
+      ) {
+        boneStyle.justifyContent = 'center';
+        boneStyle.alignItems = 'center';
+      }
+
+      return boneStyle;
+    },
+    [getBoneDimensions],
+  );
+};

+ 92 - 0
Strides-APP/app/skeleton/hooks/useGetBones.tsx

@@ -0,0 +1,92 @@
+import React, { Children, useCallback } from 'react';
+import { View } from 'react-native';
+import { useRenderBone } from './useRenderBone';
+import StaticBone from '../StaticBone';
+import ShiverBone from '../ShiverBone';
+import type {
+  ICustomViewStyle,
+  IGeneralStyles,
+  IComponentSize,
+} from '../constants';
+
+interface UseGetBonesProps {
+  bonesLayout: ICustomViewStyle[];
+  children: React.ReactNode;
+  prefix?: string | number;
+  generalStyles: IGeneralStyles;
+}
+
+/**
+ * This hook is used to get the bones based on the layout prop.
+ * @componentSize is the size of the component.
+ */
+export const useGetBones = (componentSize: IComponentSize) => {
+  const renderBone = useRenderBone(componentSize);
+
+  const renderNestedBones = useCallback(
+    (
+      bones: ICustomViewStyle[],
+      prefix: string | number | undefined,
+      generalStyles: IGeneralStyles,
+    ) => {
+      return bones.map((bone, index) => {
+        const keyIndex = prefix ? `${prefix}_${index}` : index;
+
+        const { children: childBones, ...layoutStyle } = bone;
+
+        if (childBones?.length) {
+          return (
+            <View key={keyIndex} style={layoutStyle}>
+              {renderNestedBones(childBones, keyIndex, generalStyles)}
+            </View>
+          );
+        }
+
+        return renderBone({
+          generalStyles,
+          bonesLayout: bones,
+          index,
+          keyIndex,
+        });
+      });
+    },
+    [renderBone],
+  );
+
+  return useCallback(
+    ({ bonesLayout, children, prefix, generalStyles }: UseGetBonesProps) => {
+      if (bonesLayout && bonesLayout.length > 0) {
+        return renderNestedBones(bonesLayout, prefix, generalStyles);
+      }
+
+      return Children.map(children, (child, i) => {
+        const styling = child.props.style || {};
+
+        if (
+          generalStyles.animationType === 'pulse' ||
+          generalStyles.animationType === 'none'
+        ) {
+          return (
+            <StaticBone
+              key={prefix ? `${prefix}_${i}` : i}
+              index={i}
+              boneLayout={styling}
+              componentSize={componentSize}
+              {...generalStyles}
+            />
+          );
+        }
+
+        return (
+          <ShiverBone
+            index={prefix ? `${prefix}_${i}` : i}
+            componentSize={componentSize}
+            boneLayout={styling}
+            {...generalStyles}
+          />
+        );
+      });
+    },
+    [componentSize, renderNestedBones],
+  );
+};

+ 53 - 0
Strides-APP/app/skeleton/hooks/useGetGradientEndDirection.ts

@@ -0,0 +1,53 @@
+import { useCallback } from 'react';
+import { useGetBoneDimensions } from './useGetBoneDimensions';
+import type {
+  ISkeletonProps,
+  ICustomViewStyle,
+  IComponentSize,
+} from '../constants';
+
+type UseGetGradientEndDirectionProps = Pick<
+  ISkeletonProps,
+  'animationType' | 'animationDirection'
+> & {
+  boneLayout: ICustomViewStyle;
+};
+
+export const useGetGradientEndDirection = (componentSize: IComponentSize) => {
+  const getBoneDimensions = useGetBoneDimensions(componentSize);
+
+  return useCallback(
+    ({
+      animationType,
+      animationDirection,
+      boneLayout,
+    }: UseGetGradientEndDirectionProps) => {
+      let direction = { x: 0, y: 0 };
+
+      if (animationType === 'shiver') {
+        switch (animationDirection) {
+          case 'horizontalLeft':
+          case 'horizontalRight':
+            direction = { x: 1, y: 0 };
+            break;
+          case 'verticalTop':
+          case 'verticalDown':
+            direction = { x: 0, y: 1 };
+            break;
+          case 'diagonalTopRight':
+          case 'diagonalDownRight':
+          case 'diagonalDownLeft':
+          case 'diagonalTopLeft': {
+            const { height, width } = getBoneDimensions(boneLayout);
+            return width && height && width > height
+              ? { x: 0, y: 1 }
+              : { x: 1, y: 0 };
+          }
+        }
+      }
+
+      return direction;
+    },
+    [getBoneDimensions],
+  );
+};

+ 41 - 0
Strides-APP/app/skeleton/hooks/useGetGradientSize.ts

@@ -0,0 +1,41 @@
+import { useCallback } from 'react';
+import { useGetBoneDimensions } from './useGetBoneDimensions';
+import type {
+  ICustomViewStyle,
+  ISkeletonProps,
+  IComponentSize,
+} from '../constants';
+
+type UseGetGradientSizeProps = Pick<ISkeletonProps, 'animationDirection'> & {
+  boneLayout: ICustomViewStyle;
+};
+
+export const useGetGradientSize = (componentSize: IComponentSize) => {
+  const getBoneDimensions = useGetBoneDimensions(componentSize);
+
+  return useCallback(
+    ({ animationDirection, boneLayout }: UseGetGradientSizeProps) => {
+      const { width, height } = getBoneDimensions(boneLayout);
+      const gradientStyle: ICustomViewStyle = {};
+
+      if (
+        animationDirection === 'diagonalDownRight' ||
+        animationDirection === 'diagonalDownLeft' ||
+        animationDirection === 'diagonalTopRight' ||
+        animationDirection === 'diagonalTopLeft'
+      ) {
+        gradientStyle.width = width as number;
+        gradientStyle.height = height as number;
+
+        if (height >= width) {
+          gradientStyle.height *= 1.5;
+        } else {
+          gradientStyle.width *= 1.5;
+        }
+      }
+
+      return gradientStyle;
+    },
+    [getBoneDimensions],
+  );
+};

+ 135 - 0
Strides-APP/app/skeleton/hooks/useGetGradientTransform.ts

@@ -0,0 +1,135 @@
+import {
+  SharedValue,
+  interpolate,
+  useDerivedValue,
+} from 'react-native-reanimated';
+import { useGetPositionRange } from './useGetPositionRange';
+import { useGetBoneDimensions } from './useGetBoneDimensions';
+import type {
+  ICustomViewStyle,
+  IComponentSize,
+  ISkeletonProps,
+} from '../constants';
+
+type UseGetGradientTransformProps = Pick<
+  ISkeletonProps,
+  'animationDirection'
+> & {
+  componentSize: IComponentSize;
+  boneLayout: ICustomViewStyle;
+  animationValue: SharedValue<number>;
+};
+
+/**
+ * Provides the animation values for the gradient transform
+ * @componentSize is the size of the component
+ * @boneLayout is the layout of the bone
+ * @animationDirection is the direction of the animation
+ * @animationValue is the value of the animation
+ */
+export const useGetGradientTransform = ({
+  componentSize,
+  boneLayout,
+  animationDirection,
+  animationValue,
+}: UseGetGradientTransformProps): SharedValue<{
+  translateX?: number;
+  translateY?: number;
+  rotate?: string;
+}> => {
+  const getBoneDimensions = useGetBoneDimensions(componentSize);
+  const getPositionRange = useGetPositionRange(componentSize);
+
+  return useDerivedValue(() => {
+    let transform = {};
+    const { width, height } = getBoneDimensions(boneLayout);
+
+    if (
+      animationDirection === 'verticalTop' ||
+      animationDirection === 'verticalDown' ||
+      animationDirection === 'horizontalLeft' ||
+      animationDirection === 'horizontalRight'
+    ) {
+      const interpolatedPosition = interpolate(
+        animationValue.value,
+        [0, 1],
+        getPositionRange({ animationDirection, boneLayout }),
+      );
+
+      if (
+        animationDirection === 'verticalTop' ||
+        animationDirection === 'verticalDown'
+      ) {
+        transform = { translateY: interpolatedPosition };
+      } else {
+        transform = { translateX: interpolatedPosition };
+      }
+    } else if (
+      animationDirection === 'diagonalDownRight' ||
+      animationDirection === 'diagonalTopRight' ||
+      animationDirection === 'diagonalDownLeft' ||
+      animationDirection === 'diagonalTopLeft'
+    ) {
+      const diagonal = Math.sqrt(height * height + width * width);
+      const mainDimension = Math.max(height, width);
+      const oppositeDimension = mainDimension === width ? height : width;
+      const diagonalAngle = Math.acos(mainDimension / diagonal);
+      let rotateAngle =
+        animationDirection === 'diagonalDownRight' ||
+        animationDirection === 'diagonalTopLeft'
+          ? Math.PI / 2 - diagonalAngle
+          : Math.PI / 2 + diagonalAngle;
+      const additionalRotate =
+        animationDirection === 'diagonalDownRight' ||
+        animationDirection === 'diagonalTopLeft'
+          ? 2 * diagonalAngle
+          : -2 * diagonalAngle;
+      const distanceFactor = (diagonal + oppositeDimension) / 2;
+      if (mainDimension === width && width !== height)
+        rotateAngle += additionalRotate;
+      const sinComponent = Math.sin(diagonalAngle) * distanceFactor;
+      const cosComponent = Math.cos(diagonalAngle) * distanceFactor;
+      let xOutputRange: number[];
+      let yOutputRange: number[];
+      if (
+        animationDirection === 'diagonalDownRight' ||
+        animationDirection === 'diagonalTopLeft'
+      ) {
+        xOutputRange =
+          animationDirection === 'diagonalDownRight'
+            ? [-sinComponent, sinComponent]
+            : [sinComponent, -sinComponent];
+        yOutputRange =
+          animationDirection === 'diagonalDownRight'
+            ? [-cosComponent, cosComponent]
+            : [cosComponent, -cosComponent];
+      } else {
+        xOutputRange =
+          animationDirection === 'diagonalDownLeft'
+            ? [-sinComponent, sinComponent]
+            : [sinComponent, -sinComponent];
+        yOutputRange =
+          animationDirection === 'diagonalDownLeft'
+            ? [cosComponent, -cosComponent]
+            : [-cosComponent, cosComponent];
+        if (mainDimension === height && width !== height) {
+          xOutputRange.reverse();
+          yOutputRange.reverse();
+        }
+      }
+
+      let translateX = interpolate(animationValue.value, [0, 1], xOutputRange);
+      let translateY = interpolate(animationValue.value, [0, 1], yOutputRange);
+
+      // swapping the translates if width is the main dim
+      if (mainDimension === width) {
+        [translateX, translateY] = [translateY, translateX];
+      }
+
+      const rotate = `${rotateAngle}rad`;
+      transform = { translateX, translateY, rotate };
+    }
+
+    return transform;
+  });
+};

+ 39 - 0
Strides-APP/app/skeleton/hooks/useGetPositionRange.ts

@@ -0,0 +1,39 @@
+import { useCallback } from 'react';
+import { useGetBoneDimensions } from './useGetBoneDimensions';
+import { ICustomViewStyle } from '../constants';
+import type { ISkeletonProps, IComponentSize } from '../constants';
+
+type UseGetPositionRangeProps = Pick<ISkeletonProps, 'animationDirection'> & {
+  boneLayout: ICustomViewStyle;
+};
+
+export const useGetPositionRange = (componentSize: IComponentSize) => {
+  const getBoneDimensions = useGetBoneDimensions(componentSize);
+
+  return useCallback(
+    ({ animationDirection, boneLayout }: UseGetPositionRangeProps) => {
+      'worklet';
+
+      const outputRange: number[] = [];
+      const { width, height } = getBoneDimensions(boneLayout);
+
+      switch (animationDirection) {
+        case 'horizontalRight':
+          outputRange.push(-width, +width);
+          break;
+        case 'horizontalLeft':
+          outputRange.push(+width, -width);
+          break;
+        case 'verticalDown':
+          outputRange.push(-height, +height);
+          break;
+        case 'verticalTop':
+          outputRange.push(+height, -height);
+          break;
+      }
+
+      return outputRange;
+    },
+    [getBoneDimensions],
+  );
+};

+ 20 - 0
Strides-APP/app/skeleton/hooks/useLayout.ts

@@ -0,0 +1,20 @@
+import { useState, useCallback } from 'react';
+import { LayoutChangeEvent } from 'react-native';
+
+type Size = {
+  width: number;
+  height: number;
+};
+
+type UseLayoutReturnType = [Size, (event: LayoutChangeEvent) => void];
+
+export const useLayout = (): UseLayoutReturnType => {
+  const [size, setSize] = useState({ width: 0, height: 0 });
+
+  const onLayout = useCallback((event: LayoutChangeEvent) => {
+    const { width, height } = event.nativeEvent.layout;
+    setSize({ width, height });
+  }, []);
+
+  return [size, onLayout];
+};

+ 49 - 0
Strides-APP/app/skeleton/hooks/useRenderBone.tsx

@@ -0,0 +1,49 @@
+import React, { useCallback } from 'react';
+import ShiverBone from '../ShiverBone';
+import StaticBone from '../StaticBone';
+import type {
+  ICustomViewStyle,
+  IGeneralStyles,
+  IComponentSize,
+} from '../constants';
+
+interface UseRenderBoneProps {
+  generalStyles: IGeneralStyles;
+  bonesLayout: ICustomViewStyle[];
+  keyIndex: string | number;
+  index: number;
+}
+
+/**
+ * Renders the bone based on the animation type.
+ * @componentSize is the size of the component.
+ */
+export const useRenderBone = (componentSize: IComponentSize) =>
+  useCallback(
+    ({ generalStyles, bonesLayout, keyIndex, index }: UseRenderBoneProps) => {
+      if (
+        generalStyles.animationType === 'pulse' ||
+        generalStyles.animationType === 'none'
+      ) {
+        return (
+          <StaticBone
+            key={keyIndex}
+            componentSize={componentSize}
+            index={index}
+            boneLayout={bonesLayout[index] ?? {}}
+            {...generalStyles}
+          />
+        );
+      }
+
+      return (
+        <ShiverBone
+          key={keyIndex}
+          componentSize={componentSize}
+          boneLayout={bonesLayout[index] ?? {}}
+          {...generalStyles}
+        />
+      );
+    },
+    [componentSize],
+  );