All files / app/(tabs)/forum/components/community CreatePostFAB.tsx

65% Statements 13/20
100% Branches 0/0
40% Functions 2/5
63.15% Lines 12/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                1x                       1x     1x 1x 1x   1x       1x         1x         1x                         1x                                           1x                             1x  
/**
 * CreatePostFAB - 创建帖子浮动按钮
 *
 * 底部右侧的悬浮创建按钮
 * 设计风格:渐变背景,阴影效果,弹性动画
 */
 
import React, { memo } from 'react';
import { Pressable, StyleSheet } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withSequence,
} from 'react-native-reanimated';
import { LinearGradient } from 'expo-linear-gradient';
import { Plus } from '@tamagui/lucide-icons';
import { styled, Stack } from 'tamagui';
import { useThemeColors } from '@/src/hooks/useThemeColors';
 
export interface CreatePostFABProps {
  onPress: () => void;
}
 
const FABContainer = styled(Stack, {
  name: 'FABContainer',
  width: 60,
  height: 60,
  borderRadius: 30,
  overflow: 'hidden',
  shadowColor: 'rgba(254, 190, 152, 0.4)',
  shadowOffset: { width: 0, height: 8 },
  shadowOpacity: 1,
  shadowRadius: 16,
});
 
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
 
function CreatePostFABComponent({ onPress }: CreatePostFABProps) {
  const colors = useThemeColors();
  const scale = useSharedValue(1);
  const rotation = useSharedValue(0);
 
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }, { rotate: `${rotation.value}deg` }],
  }));
 
  const handlePressIn = () => {
    scale.value = withSpring(0.88, { damping: 15, stiffness: 400 });
    rotation.value = withSpring(-15, { damping: 15, stiffness: 400 });
  };
 
  const handlePressOut = () => {
    scale.value = withSpring(1, { damping: 10, stiffness: 300 });
    rotation.value = withSpring(0, { damping: 10, stiffness: 300 });
  };
 
  const handlePress = () => {
    // 按下时的弹跳效果
    scale.value = withSequence(
      withSpring(1.1, { damping: 10, stiffness: 400 }),
      withSpring(1, { damping: 12, stiffness: 300 })
    );
    rotation.value = withSequence(
      withSpring(90, { damping: 15, stiffness: 400 }),
      withSpring(0, { damping: 10, stiffness: 300 })
    );
    onPress();
  };
 
  return (
    <AnimatedPressable
      testID="create-post-fab"
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={handlePress}
      style={[styles.pressable, animatedStyle]}
    >
      <FABContainer shadowColor={colors.primary as any}>
        <LinearGradient
          colors={[colors.primaryLight, colors.primary, colors.primaryDark]}
          start={{ x: 0, y: 0 }}
          end={{ x: 1, y: 1 }}
          style={styles.gradient}
        >
          <Plus size={28} color="#FFFFFF" strokeWidth={2.5} />
        </LinearGradient>
      </FABContainer>
    </AnimatedPressable>
  );
}
 
const styles = StyleSheet.create({
  pressable: {
    position: 'absolute',
    bottom: 32,
    right: 20,
  },
  gradient: {
    width: 60,
    height: 60,
    borderRadius: 30,
    alignItems: 'center',
    justifyContent: 'center',
  },
});
 
export const CreatePostFAB = memo(CreatePostFABComponent);