|
@@ -1,315 +1,397 @@
|
|
|
|
|
+import { Images } from '@/constants/images';
|
|
|
|
|
+import ServiceWallet from '@/services/wallet';
|
|
|
|
|
+import Service from '@/services/weal';
|
|
|
|
|
+import { Ionicons } from '@expo/vector-icons';
|
|
|
import { Image } from 'expo-image';
|
|
import { Image } from 'expo-image';
|
|
|
import { useRouter } from 'expo-router';
|
|
import { useRouter } from 'expo-router';
|
|
|
-import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
|
-import {
|
|
|
|
|
- Animated,
|
|
|
|
|
- Dimensions,
|
|
|
|
|
- ImageBackground,
|
|
|
|
|
- ScrollView,
|
|
|
|
|
- StatusBar,
|
|
|
|
|
- StyleSheet,
|
|
|
|
|
- Text,
|
|
|
|
|
- TouchableOpacity,
|
|
|
|
|
- View,
|
|
|
|
|
-} from 'react-native';
|
|
|
|
|
|
|
+import React, { useEffect, useRef, useState } from 'react';
|
|
|
|
|
+import { Alert, Dimensions, ImageBackground, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
|
|
|
+import Animated, {
|
|
|
|
|
+ cancelAnimation,
|
|
|
|
|
+ Easing,
|
|
|
|
|
+ useAnimatedStyle,
|
|
|
|
|
+ useSharedValue,
|
|
|
|
|
+ withRepeat,
|
|
|
|
|
+ withSequence,
|
|
|
|
|
+ withTiming
|
|
|
|
|
+} from 'react-native-reanimated';
|
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
|
|
+import { CatchRuleModal, CatchRuleModalRef } from './components/CatchRuleModal';
|
|
|
|
|
+import { DollPrizeModal, DollPrizeModalRef } from './components/DollPrizeModal';
|
|
|
|
|
+import { DollResultModal, DollResultModalRef } from './components/DollResultModal';
|
|
|
|
|
+import { LackMolibModal, LackMolibModalRef } from './components/LackMolibModal';
|
|
|
|
|
+import { PressSureModal, PressSureModalRef } from './components/PressSureModal';
|
|
|
|
|
+import { WinRecordModal, WinRecordModalRef } from './components/WinRecordModal';
|
|
|
|
|
+
|
|
|
|
|
+const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
|
|
|
+
|
|
|
|
|
+const BALL_COUNT = 20; // Optimized count
|
|
|
|
|
+const BALL_SIZE = 66;
|
|
|
|
|
+const BALL_CONTAINER_WIDTH = 275;
|
|
|
|
|
+const BALL_CONTAINER_HEIGHT = 278;
|
|
|
|
|
+
|
|
|
|
|
+// Separate Ball Component for Performance
|
|
|
|
|
+const Ball = React.memo(({ index }: { index: number }) => {
|
|
|
|
|
+ const x = useSharedValue(Math.random() * (BALL_CONTAINER_WIDTH - BALL_SIZE));
|
|
|
|
|
+ const y = useSharedValue(Math.random() * (BALL_CONTAINER_HEIGHT - BALL_SIZE));
|
|
|
|
|
+ const rotate = useSharedValue(Math.random() * 360);
|
|
|
|
|
|
|
|
-import { get } from '@/services/http';
|
|
|
|
|
-
|
|
|
|
|
-const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
|
|
|
-const CDN_BASE = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart';
|
|
|
|
|
-
|
|
|
|
|
-const catchDollImages = {
|
|
|
|
|
- bg: `${CDN_BASE}/common/commonBg.png`,
|
|
|
|
|
- dollBox: `${CDN_BASE}/welfare/qijiWelfareDollBox.png`,
|
|
|
|
|
- dollBall: `${CDN_BASE}/welfare/qijiWelfareDollBall.png`,
|
|
|
|
|
- dollOne: `${CDN_BASE}/welfare/qijiWelfareDollOne.png`,
|
|
|
|
|
- dollFive: `${CDN_BASE}/welfare/qijiWelfareDollFive.png`,
|
|
|
|
|
- recordBg: `${CDN_BASE}/welfare/qijiWelfareRecordBg.png`,
|
|
|
|
|
- ruleBtn: `${CDN_BASE}/welfare/catchDollRule.png`,
|
|
|
|
|
- molibiBoxBtn: `${CDN_BASE}/welfare/molibiBoxBtn.png`,
|
|
|
|
|
- opening: `${CDN_BASE}/welfare/opening.png`,
|
|
|
|
|
- dollBi1: `${CDN_BASE}/welfare/qijiWelfareDollBi1.png`,
|
|
|
|
|
- dollBi5: `${CDN_BASE}/welfare/qijiWelfareDollBi5.png`,
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-interface GoodsItem {
|
|
|
|
|
- id: string;
|
|
|
|
|
- cover: string;
|
|
|
|
|
- name: string;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ // Generate a sequence of random moves to simulate "chaos" on UI thread
|
|
|
|
|
+ const xMoves = Array.from({ length: 10 }).map(() =>
|
|
|
|
|
+ withTiming(Math.random() * (BALL_CONTAINER_WIDTH - BALL_SIZE), {
|
|
|
|
|
+ duration: 2000 + Math.random() * 1500,
|
|
|
|
|
+ easing: Easing.linear
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+ const yMoves = Array.from({ length: 10 }).map(() =>
|
|
|
|
|
+ withTiming(Math.random() * (BALL_CONTAINER_HEIGHT - BALL_SIZE), {
|
|
|
|
|
+ duration: 2000 + Math.random() * 1500,
|
|
|
|
|
+ easing: Easing.linear
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+ const rMoves = Array.from({ length: 10 }).map(() =>
|
|
|
|
|
+ withTiming(Math.random() * 360, {
|
|
|
|
|
+ duration: 2000 + Math.random() * 1500,
|
|
|
|
|
+ easing: Easing.linear
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // @ts-ignore: spread argument for withSequence
|
|
|
|
|
+ x.value = withRepeat(withSequence(...xMoves), -1, true);
|
|
|
|
|
+ // @ts-ignore
|
|
|
|
|
+ y.value = withRepeat(withSequence(...yMoves), -1, true);
|
|
|
|
|
+ // @ts-ignore
|
|
|
|
|
+ rotate.value = withRepeat(withSequence(...rMoves), -1, true);
|
|
|
|
|
+
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ cancelAnimation(x);
|
|
|
|
|
+ cancelAnimation(y);
|
|
|
|
|
+ cancelAnimation(rotate);
|
|
|
|
|
+ };
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ const animatedStyle = useAnimatedStyle(() => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ transform: [
|
|
|
|
|
+ { translateX: x.value },
|
|
|
|
|
+ { translateY: y.value },
|
|
|
|
|
+ { rotate: `${rotate.value}deg` } // Reanimated handles string interpolation
|
|
|
|
|
+ ]
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Animated.View style={[styles.ball, animatedStyle]}>
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.qijiWelfareDollBall }} style={styles.fullSize} />
|
|
|
|
|
+ </Animated.View>
|
|
|
|
|
+ );
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
export default function CatchDollScreen() {
|
|
export default function CatchDollScreen() {
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
const insets = useSafeAreaInsets();
|
|
const insets = useSafeAreaInsets();
|
|
|
- const [goodsList, setGoodsList] = useState<GoodsItem[]>([]);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // State
|
|
|
const [molibi, setMolibi] = useState(0);
|
|
const [molibi, setMolibi] = useState(0);
|
|
|
- const [balls] = useState(() =>
|
|
|
|
|
- Array.from({ length: 50 }, (_, i) => ({
|
|
|
|
|
- id: i,
|
|
|
|
|
- bottom: Math.random() * 80,
|
|
|
|
|
- left: Math.random() * 172,
|
|
|
|
|
- }))
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ const [luckWheelGoodsList, setLuckWheelGoodsList] = useState<any[]>([]);
|
|
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
|
|
+ const [lotteryFlag, setLotteryFlag] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ // Reanimated Values for interactions
|
|
|
|
|
+ const switchRotateVal = useSharedValue(0);
|
|
|
|
|
+ const dropScale = useSharedValue(0);
|
|
|
|
|
+ const dropOpacity = useSharedValue(0);
|
|
|
|
|
+
|
|
|
|
|
+ // Modals
|
|
|
|
|
+ const ruleRef = useRef<CatchRuleModalRef>(null);
|
|
|
|
|
+ const winRecordRef = useRef<WinRecordModalRef>(null);
|
|
|
|
|
+ const resultRef = useRef<DollResultModalRef>(null);
|
|
|
|
|
+ const prizeRef = useRef<DollPrizeModalRef>(null);
|
|
|
|
|
+ const pressSureRef = useRef<PressSureModalRef>(null);
|
|
|
|
|
+ const lackMolibRef = useRef<LackMolibModalRef>(null);
|
|
|
|
|
|
|
|
- // 动画
|
|
|
|
|
- const ballAnimations = useRef(balls.map(() => new Animated.ValueXY({ x: 0, y: 0 }))).current;
|
|
|
|
|
- const ballRotations = useRef(balls.map(() => new Animated.Value(0))).current;
|
|
|
|
|
-
|
|
|
|
|
- const loadData = useCallback(async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await get('/api/luckWheel/detail');
|
|
|
|
|
- if (res.data) {
|
|
|
|
|
- setGoodsList(res.data.luckWheelGoodsList || []);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('加载扭蛋机数据失败:', error);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ initData();
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
- const loadMolibi = useCallback(async () => {
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await get('/api/wallet/info', { type: 'MAGIC_POWER_COIN' });
|
|
|
|
|
- if (res.data) {
|
|
|
|
|
- setMolibi(res.data.balance || 0);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('加载源力币失败:', error);
|
|
|
|
|
|
|
+ const initData = async () => {
|
|
|
|
|
+ getMolibi();
|
|
|
|
|
+ getDetail();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const getMolibi = async () => {
|
|
|
|
|
+ const res = await ServiceWallet.info('MAGIC_POWER_COIN');
|
|
|
|
|
+ if (res) {
|
|
|
|
|
+ setMolibi(res.balance);
|
|
|
}
|
|
}
|
|
|
- }, []);
|
|
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- useEffect(() => {
|
|
|
|
|
- loadData();
|
|
|
|
|
- loadMolibi();
|
|
|
|
|
- }, [loadData, loadMolibi]);
|
|
|
|
|
-
|
|
|
|
|
- const animateBalls = () => {
|
|
|
|
|
- const animations = balls.map((_, i) => {
|
|
|
|
|
- const randomX = (Math.random() - 0.5) * 50;
|
|
|
|
|
- const randomY = (Math.random() - 0.5) * 100;
|
|
|
|
|
- const randomRotate = Math.random() * 360;
|
|
|
|
|
-
|
|
|
|
|
- return Animated.parallel([
|
|
|
|
|
- Animated.sequence([
|
|
|
|
|
- Animated.timing(ballAnimations[i], {
|
|
|
|
|
- toValue: { x: randomX, y: randomY },
|
|
|
|
|
- duration: 200,
|
|
|
|
|
- useNativeDriver: true,
|
|
|
|
|
- }),
|
|
|
|
|
- Animated.timing(ballAnimations[i], {
|
|
|
|
|
- toValue: { x: randomX * 0.5, y: randomY * 0.5 },
|
|
|
|
|
- duration: 300,
|
|
|
|
|
- useNativeDriver: true,
|
|
|
|
|
- }),
|
|
|
|
|
- Animated.timing(ballAnimations[i], {
|
|
|
|
|
- toValue: { x: 0, y: 0 },
|
|
|
|
|
- duration: 300,
|
|
|
|
|
- useNativeDriver: true,
|
|
|
|
|
- }),
|
|
|
|
|
- ]),
|
|
|
|
|
- Animated.timing(ballRotations[i], {
|
|
|
|
|
- toValue: randomRotate,
|
|
|
|
|
- duration: 800,
|
|
|
|
|
- useNativeDriver: true,
|
|
|
|
|
- }),
|
|
|
|
|
- ]);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- Animated.parallel(animations).start(() => {
|
|
|
|
|
- ballRotations.forEach((rot) => rot.setValue(0));
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const getDetail = async () => {
|
|
|
|
|
+ const res = await Service.catchDollDetail();
|
|
|
|
|
+ if (res.code === '0') {
|
|
|
|
|
+ setLuckWheelGoodsList(res.data.luckWheelGoodsList);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Alert.alert('提示', res.msg);
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const handlePress = (count: number) => {
|
|
|
|
|
- if (molibi < count) {
|
|
|
|
|
- // TODO: 显示源力币不足弹窗
|
|
|
|
|
|
|
+ const handleBack = () => router.back();
|
|
|
|
|
+
|
|
|
|
|
+ const handlePress1 = () => {
|
|
|
|
|
+ if (lotteryFlag) {
|
|
|
|
|
+ Alert.alert('提示', '请不要重复点击');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- animateBalls();
|
|
|
|
|
- // TODO: 调用抽奖接口
|
|
|
|
|
|
|
+ if (molibi === 0) {
|
|
|
|
|
+ lackMolibRef.current?.show();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pressSureRef.current?.show(1);
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- return (
|
|
|
|
|
- <View style={styles.container}>
|
|
|
|
|
- <StatusBar barStyle="light-content" />
|
|
|
|
|
- <ImageBackground source={{ uri: catchDollImages.bg }} style={styles.background} resizeMode="cover">
|
|
|
|
|
- {/* 固定头部 */}
|
|
|
|
|
- <View style={[styles.header, { paddingTop: insets.top }]}>
|
|
|
|
|
- <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
|
|
|
|
|
- <Text style={styles.backText}>←</Text>
|
|
|
|
|
- </TouchableOpacity>
|
|
|
|
|
- <Text style={styles.title}>扭蛋机</Text>
|
|
|
|
|
- <View style={styles.placeholder} />
|
|
|
|
|
- </View>
|
|
|
|
|
|
|
+ const handlePress5 = () => {
|
|
|
|
|
+ if (lotteryFlag) {
|
|
|
|
|
+ Alert.alert('提示', '请不要重复点击');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (molibi < 5) {
|
|
|
|
|
+ lackMolibRef.current?.show();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pressSureRef.current?.show(5);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- {/* 规则按钮 - 固定位置 */}
|
|
|
|
|
- <TouchableOpacity style={[styles.ruleBtn, { top: insets.top + 200 }]}>
|
|
|
|
|
- <Image source={{ uri: catchDollImages.ruleBtn }} style={styles.ruleBtnImg} contentFit="contain" />
|
|
|
|
|
- </TouchableOpacity>
|
|
|
|
|
|
|
+ const onConfirmPress = (quantity: number) => {
|
|
|
|
|
+ playLottery(quantity);
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- {/* 中奖记录按钮 - 固定位置 */}
|
|
|
|
|
- <TouchableOpacity style={[styles.recordBtn, { top: insets.top + 120 }]}>
|
|
|
|
|
- <ImageBackground source={{ uri: catchDollImages.recordBg }} style={styles.recordBtnBg} resizeMode="contain">
|
|
|
|
|
- <Text style={styles.recordText}>中</Text>
|
|
|
|
|
- <Text style={styles.recordText}>奖</Text>
|
|
|
|
|
- <Text style={styles.recordText}>记</Text>
|
|
|
|
|
- <Text style={styles.recordText}>录</Text>
|
|
|
|
|
- </ImageBackground>
|
|
|
|
|
|
|
+ const playLottery = async (quantity: number) => {
|
|
|
|
|
+ setLotteryFlag(true);
|
|
|
|
|
+ const res = await Service.dollLottery({ quantity });
|
|
|
|
|
+
|
|
|
|
|
+ if (res.code === '0') {
|
|
|
|
|
+ getMolibi();
|
|
|
|
|
+ // Animate Switch
|
|
|
|
|
+ switchRotateVal.value = withSequence(
|
|
|
|
|
+ withTiming(90, { duration: 500, easing: Easing.inOut(Easing.ease) }),
|
|
|
|
|
+ withTiming(0, { duration: 500, easing: Easing.inOut(Easing.ease) })
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ // Animate Drop
|
|
|
|
|
+ dropOpacity.value = 1;
|
|
|
|
|
+ dropScale.value = 1;
|
|
|
|
|
+ dropOpacity.value = withTiming(0, { duration: 2000 });
|
|
|
|
|
+ dropScale.value = withTiming(0, { duration: 2000 });
|
|
|
|
|
+ }, 800);
|
|
|
|
|
+
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ resultRef.current?.show(res.data);
|
|
|
|
|
+ setLotteryFlag(false);
|
|
|
|
|
+ }, 2000);
|
|
|
|
|
+
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Alert.alert('提示', res.msg);
|
|
|
|
|
+ setLotteryFlag(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const switchStyle = useAnimatedStyle(() => ({
|
|
|
|
|
+ transform: [{ rotate: `${switchRotateVal.value}deg` }]
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ const dropStyle = useAnimatedStyle(() => ({
|
|
|
|
|
+ opacity: dropOpacity.value,
|
|
|
|
|
+ transform: [{ scale: dropScale.value }]
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <ImageBackground source={{ uri: Images.common.commonBg }} style={styles.container}>
|
|
|
|
|
+ {/* Header */}
|
|
|
|
|
+ <View style={[styles.header, { paddingTop: insets.top, minHeight: 44 + insets.top }]}>
|
|
|
|
|
+ <TouchableOpacity onPress={handleBack} style={styles.backBtn}>
|
|
|
|
|
+ <Ionicons name="chevron-back" size={24} color="#fff" />
|
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
|
|
|
+ <Text style={styles.title}>扭蛋机</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ {/* Fixed Record Button */}
|
|
|
|
|
+ <TouchableOpacity onPress={() => winRecordRef.current?.show()} style={styles.recordBtn}>
|
|
|
|
|
+ <ImageBackground source={{ uri: Images.welfare.qijiWelfareRecordBg }} style={styles.recordBg}>
|
|
|
|
|
+ <Text style={styles.recordText}>中奖记录</Text>
|
|
|
|
|
+ </ImageBackground>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+
|
|
|
|
|
+ <ScrollView contentContainerStyle={styles.scrollContent}>
|
|
|
|
|
+ <View style={styles.content}>
|
|
|
|
|
+ {/* Rule Button */}
|
|
|
|
|
+ <TouchableOpacity onPress={() => ruleRef.current?.show()} style={styles.ruleBtn}>
|
|
|
|
|
+ <ImageBackground source={{ uri: Images.welfare.catchDollRule }} style={styles.fullSize} />
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
|
|
|
- <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
|
|
|
|
|
- <View style={{ height: insets.top + 36 }} />
|
|
|
|
|
-
|
|
|
|
|
- {/* 扭蛋机主体 */}
|
|
|
|
|
- <View style={styles.machineWrapper}>
|
|
|
|
|
- <ImageBackground source={{ uri: catchDollImages.dollBox }} style={styles.machineImg} resizeMode="contain">
|
|
|
|
|
- {/* 奖品列表 */}
|
|
|
|
|
- <View style={styles.goodsListWrapper}>
|
|
|
|
|
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.goodsScroll}>
|
|
|
|
|
- {goodsList.map((item, index) => (
|
|
|
|
|
- <View key={item.id || index} style={styles.goodsItem}>
|
|
|
|
|
- <Image source={{ uri: item.cover }} style={styles.goodsImg} contentFit="cover" />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ {/* Machine */}
|
|
|
|
|
+ <View style={styles.machineBox}>
|
|
|
|
|
+ <ImageBackground source={{ uri: Images.welfare.qijiWelfareDollBox }} style={styles.machineBg} resizeMode="stretch">
|
|
|
|
|
+ {/* Prizes Scroll */}
|
|
|
|
|
+ <View style={styles.prizesScrollBox}>
|
|
|
|
|
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
|
|
|
+ {luckWheelGoodsList.map((item, index) => (
|
|
|
|
|
+ <View key={index} style={styles.prizeItem}>
|
|
|
|
|
+ <Image source={{ uri: item.cover }} style={styles.prizeImg} />
|
|
|
</View>
|
|
</View>
|
|
|
))}
|
|
))}
|
|
|
</ScrollView>
|
|
</ScrollView>
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
- {/* 扭蛋球区域 */}
|
|
|
|
|
- <View style={styles.ballsBox}>
|
|
|
|
|
- {balls.map((ball, i) => (
|
|
|
|
|
- <Animated.View
|
|
|
|
|
- key={ball.id}
|
|
|
|
|
- style={[
|
|
|
|
|
- styles.ball,
|
|
|
|
|
- {
|
|
|
|
|
- bottom: ball.bottom,
|
|
|
|
|
- left: ball.left,
|
|
|
|
|
- transform: [
|
|
|
|
|
- { translateX: ballAnimations[i].x },
|
|
|
|
|
- { translateY: ballAnimations[i].y },
|
|
|
|
|
- {
|
|
|
|
|
- rotate: ballRotations[i].interpolate({
|
|
|
|
|
- inputRange: [0, 360],
|
|
|
|
|
- outputRange: ['0deg', '360deg'],
|
|
|
|
|
- }),
|
|
|
|
|
- },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
- ]}
|
|
|
|
|
- >
|
|
|
|
|
- <Image source={{ uri: catchDollImages.dollBall }} style={styles.ballImg} contentFit="contain" />
|
|
|
|
|
- </Animated.View>
|
|
|
|
|
- ))}
|
|
|
|
|
- </View>
|
|
|
|
|
-
|
|
|
|
|
- {/* 源力币信息框 */}
|
|
|
|
|
|
|
+ {/* Molibi Count & Add */}
|
|
|
<View style={styles.molibiBox}>
|
|
<View style={styles.molibiBox}>
|
|
|
- <Text style={styles.molibiLabel}>
|
|
|
|
|
- 源力币:<Text style={styles.molibiNum}>{molibi}</Text> 个
|
|
|
|
|
- </Text>
|
|
|
|
|
- <TouchableOpacity style={styles.molibiBtn} onPress={() => router.push('/award' as any)}>
|
|
|
|
|
- <Image source={{ uri: catchDollImages.molibiBoxBtn }} style={styles.molibiBtnImg} contentFit="contain" />
|
|
|
|
|
|
|
+ <Text style={styles.molibiText}>源力币:<Text style={styles.molibiNum}>{molibi}</Text> 个</Text>
|
|
|
|
|
+ <TouchableOpacity onPress={() => router.push('/box' as any)} style={styles.addMolibiBtn}>
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.molibiBoxBtn }} style={styles.fullSize} resizeMode="contain" />
|
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
- {/* 开口动画区域 */}
|
|
|
|
|
- <View style={styles.openingBox}>
|
|
|
|
|
- <Image source={{ uri: catchDollImages.opening }} style={styles.openingImg} contentFit="contain" />
|
|
|
|
|
|
|
+ {/* Switch */}
|
|
|
|
|
+ <Animated.View style={[styles.switchBox, switchStyle]}>
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.qijiWelfareDollBi1 }} style={styles.fullSize} />
|
|
|
|
|
+ </Animated.View>
|
|
|
|
|
+ <Animated.View style={[styles.switchBoxRight, switchStyle]}>
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.qijiWelfareDollBi5 }} style={styles.fullSize} />
|
|
|
|
|
+ </Animated.View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Balls */}
|
|
|
|
|
+ <View style={styles.ballsContainer}>
|
|
|
|
|
+ {Array.from({ length: BALL_COUNT }).map((_, index) => (
|
|
|
|
|
+ <Ball key={index} index={index} />
|
|
|
|
|
+ ))}
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
- {/* 扭蛋把手 */}
|
|
|
|
|
- <TouchableOpacity style={[styles.switchBox, styles.switchBox1]} onPress={() => handlePress(1)}>
|
|
|
|
|
- <Image source={{ uri: catchDollImages.dollBi1 }} style={styles.switchImg} contentFit="contain" />
|
|
|
|
|
|
|
+ {/* Dropping Ball */}
|
|
|
|
|
+ <Animated.View style={[styles.droppingBall, dropStyle]}>
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.qijiWelfareDollBall }} style={styles.fullSize} />
|
|
|
|
|
+ </Animated.View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Opening hole image */}
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.opening }} style={styles.opening} />
|
|
|
|
|
+
|
|
|
|
|
+ {/* Buttons */}
|
|
|
|
|
+ <TouchableOpacity onPress={handlePress1} style={[styles.playBtn, styles.playBtn1]}>
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.qijiWelfareDollOne }} style={styles.fullSize} resizeMode="contain" />
|
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
|
- <TouchableOpacity style={[styles.switchBox, styles.switchBox5]} onPress={() => handlePress(5)}>
|
|
|
|
|
- <Image source={{ uri: catchDollImages.dollBi5 }} style={styles.switchImg} contentFit="contain" />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <TouchableOpacity onPress={handlePress5} style={[styles.playBtn, styles.playBtn5]}>
|
|
|
|
|
+ <Image source={{ uri: Images.welfare.qijiWelfareDollFive }} style={styles.fullSize} resizeMode="contain" />
|
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
|
|
|
+
|
|
|
</ImageBackground>
|
|
</ImageBackground>
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
- {/* 底部按钮 */}
|
|
|
|
|
- <View style={styles.bottomBtns}>
|
|
|
|
|
- <TouchableOpacity style={styles.submitBtn} onPress={() => handlePress(1)}>
|
|
|
|
|
- <Image source={{ uri: catchDollImages.dollOne }} style={styles.submitBtnImg} contentFit="contain" />
|
|
|
|
|
- </TouchableOpacity>
|
|
|
|
|
- <TouchableOpacity style={styles.submitBtn} onPress={() => handlePress(5)}>
|
|
|
|
|
- <Image source={{ uri: catchDollImages.dollFive }} style={styles.submitBtnImg} contentFit="contain" />
|
|
|
|
|
- </TouchableOpacity>
|
|
|
|
|
- </View>
|
|
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </ScrollView>
|
|
|
|
|
|
|
|
- <View style={{ height: 100 }} />
|
|
|
|
|
- </ScrollView>
|
|
|
|
|
- </ImageBackground>
|
|
|
|
|
- </View>
|
|
|
|
|
|
|
+ {/* Modals */}
|
|
|
|
|
+ <CatchRuleModal ref={ruleRef} />
|
|
|
|
|
+ <WinRecordModal ref={winRecordRef} />
|
|
|
|
|
+ <DollResultModal ref={resultRef} />
|
|
|
|
|
+ <DollPrizeModal ref={prizeRef} />
|
|
|
|
|
+ <PressSureModal ref={pressSureRef} onPress={onConfirmPress} />
|
|
|
|
|
+ <LackMolibModal ref={lackMolibRef} />
|
|
|
|
|
+
|
|
|
|
|
+ </ImageBackground>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
const styles = StyleSheet.create({
|
|
|
- container: { flex: 1, backgroundColor: '#1a1a2e' },
|
|
|
|
|
- background: { flex: 1 },
|
|
|
|
|
|
|
+ container: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ height: '100%',
|
|
|
|
|
+ },
|
|
|
header: {
|
|
header: {
|
|
|
|
|
+ // height: 44, // Removing fixed height to allow dynamic override
|
|
|
flexDirection: 'row',
|
|
flexDirection: 'row',
|
|
|
alignItems: 'center',
|
|
alignItems: 'center',
|
|
|
- justifyContent: 'space-between',
|
|
|
|
|
- paddingHorizontal: 10,
|
|
|
|
|
- paddingBottom: 10,
|
|
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ zIndex: 100,
|
|
|
|
|
+ },
|
|
|
|
|
+ backBtn: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ left: 10,
|
|
|
|
|
+ bottom: 10,
|
|
|
|
|
+ zIndex: 101,
|
|
|
|
|
+ },
|
|
|
|
|
+ title: {
|
|
|
|
|
+ color: '#fff',
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold',
|
|
|
|
|
+ },
|
|
|
|
|
+ scrollContent: {
|
|
|
|
|
+ flexGrow: 1,
|
|
|
|
|
+ paddingBottom: 50,
|
|
|
|
|
+ paddingTop: 40, // Push entire content down to avoid header overlap
|
|
|
|
|
+ },
|
|
|
|
|
+ content: {
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
|
|
+ position: 'relative',
|
|
|
|
|
+ height: 750,
|
|
|
|
|
+ },
|
|
|
|
|
+ fullSize: {
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ height: '100%',
|
|
|
|
|
+ },
|
|
|
|
|
+ ruleBtn: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ left: 13,
|
|
|
|
|
+ top: 422,
|
|
|
|
|
+ zIndex: 10,
|
|
|
|
|
+ width: 62,
|
|
|
|
|
+ height: 20,
|
|
|
|
|
+ },
|
|
|
|
|
+ recordBtn: {
|
|
|
position: 'absolute',
|
|
position: 'absolute',
|
|
|
- top: 0,
|
|
|
|
|
- left: 0,
|
|
|
|
|
right: 0,
|
|
right: 0,
|
|
|
- zIndex: 100,
|
|
|
|
|
|
|
+ top: 200, // Fixed position from top of screen
|
|
|
|
|
+ zIndex: 99,
|
|
|
},
|
|
},
|
|
|
- backBtn: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center' },
|
|
|
|
|
- backText: { color: '#fff', fontSize: 20 },
|
|
|
|
|
- title: { color: '#fff', fontSize: 15, fontWeight: 'bold' },
|
|
|
|
|
- placeholder: { width: 40 },
|
|
|
|
|
- scrollView: { flex: 1 },
|
|
|
|
|
-
|
|
|
|
|
- // 规则按钮
|
|
|
|
|
- ruleBtn: { position: 'absolute', left: 13, zIndex: 99 },
|
|
|
|
|
- ruleBtnImg: { width: 62, height: 20 },
|
|
|
|
|
-
|
|
|
|
|
- // 中奖记录按钮
|
|
|
|
|
- recordBtn: { position: 'absolute', right: 0, zIndex: 99 },
|
|
|
|
|
- recordBtnBg: {
|
|
|
|
|
|
|
+ recordBg: {
|
|
|
width: 26,
|
|
width: 26,
|
|
|
- height: 70,
|
|
|
|
|
|
|
+ height: 80, // Increased from 70 to fit 4 chars
|
|
|
justifyContent: 'center',
|
|
justifyContent: 'center',
|
|
|
alignItems: 'center',
|
|
alignItems: 'center',
|
|
|
- paddingVertical: 9,
|
|
|
|
|
|
|
+ paddingTop: 5, // Reduced padding slightly
|
|
|
},
|
|
},
|
|
|
recordText: {
|
|
recordText: {
|
|
|
- color: '#fff',
|
|
|
|
|
fontSize: 12,
|
|
fontSize: 12,
|
|
|
|
|
+ color: '#fff',
|
|
|
fontWeight: 'bold',
|
|
fontWeight: 'bold',
|
|
|
|
|
+ width: 12,
|
|
|
|
|
+ textAlign: 'center',
|
|
|
textShadowColor: '#6C3200',
|
|
textShadowColor: '#6C3200',
|
|
|
textShadowOffset: { width: 1, height: 1 },
|
|
textShadowOffset: { width: 1, height: 1 },
|
|
|
textShadowRadius: 1,
|
|
textShadowRadius: 1,
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- // 扭蛋机主体
|
|
|
|
|
- machineWrapper: {
|
|
|
|
|
- width: SCREEN_WIDTH,
|
|
|
|
|
|
|
+ machineBox: {
|
|
|
|
|
+ marginTop: 20, // Reverted to 20, spacing handled by scrollContent padding
|
|
|
|
|
+ width: '100%',
|
|
|
alignItems: 'center',
|
|
alignItems: 'center',
|
|
|
},
|
|
},
|
|
|
- machineImg: {
|
|
|
|
|
- width: SCREEN_WIDTH,
|
|
|
|
|
- height: SCREEN_WIDTH * 1.88,
|
|
|
|
|
|
|
+ machineBg: {
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ height: 706,
|
|
|
position: 'relative',
|
|
position: 'relative',
|
|
|
|
|
+ // resizeMode: 'stretch' // Applied in component prop
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- // 奖品列表
|
|
|
|
|
- goodsListWrapper: {
|
|
|
|
|
- position: 'absolute',
|
|
|
|
|
- top: SCREEN_WIDTH * 0.29,
|
|
|
|
|
- left: 0,
|
|
|
|
|
- right: 0,
|
|
|
|
|
- alignItems: 'center',
|
|
|
|
|
- },
|
|
|
|
|
- goodsScroll: {
|
|
|
|
|
- paddingHorizontal: SCREEN_WIDTH * 0.18,
|
|
|
|
|
|
|
+ prizesScrollBox: {
|
|
|
|
|
+ width: 250,
|
|
|
|
|
+ alignSelf: 'center',
|
|
|
|
|
+ marginTop: 110,
|
|
|
|
|
+ height: 50,
|
|
|
},
|
|
},
|
|
|
- goodsItem: {
|
|
|
|
|
|
|
+ prizeItem: {
|
|
|
width: 46,
|
|
width: 46,
|
|
|
height: 46,
|
|
height: 46,
|
|
|
borderRadius: 4,
|
|
borderRadius: 4,
|
|
@@ -317,88 +399,96 @@ const styles = StyleSheet.create({
|
|
|
borderWidth: 2.5,
|
|
borderWidth: 2.5,
|
|
|
borderColor: '#8687E4',
|
|
borderColor: '#8687E4',
|
|
|
marginRight: 5,
|
|
marginRight: 5,
|
|
|
- overflow: 'hidden',
|
|
|
|
|
- },
|
|
|
|
|
- goodsImg: { width: '100%', height: '100%' },
|
|
|
|
|
-
|
|
|
|
|
- // 扭蛋球区域
|
|
|
|
|
- ballsBox: {
|
|
|
|
|
- position: 'absolute',
|
|
|
|
|
- top: SCREEN_WIDTH * 0.33,
|
|
|
|
|
- left: SCREEN_WIDTH * 0.115,
|
|
|
|
|
- width: SCREEN_WIDTH * 0.73,
|
|
|
|
|
- height: SCREEN_WIDTH * 0.74,
|
|
|
|
|
- overflow: 'hidden',
|
|
|
|
|
|
|
+ justifyContent: 'center',
|
|
|
|
|
+ alignItems: 'center',
|
|
|
},
|
|
},
|
|
|
- ball: {
|
|
|
|
|
- position: 'absolute',
|
|
|
|
|
- width: 66,
|
|
|
|
|
- height: 66,
|
|
|
|
|
|
|
+ prizeImg: {
|
|
|
|
|
+ width: '100%',
|
|
|
|
|
+ height: '100%',
|
|
|
},
|
|
},
|
|
|
- ballImg: { width: '100%', height: '100%' },
|
|
|
|
|
-
|
|
|
|
|
- // 源力币信息框
|
|
|
|
|
molibiBox: {
|
|
molibiBox: {
|
|
|
position: 'absolute',
|
|
position: 'absolute',
|
|
|
- top: SCREEN_WIDTH * 1.24,
|
|
|
|
|
|
|
+ top: 465,
|
|
|
left: 42,
|
|
left: 42,
|
|
|
width: 120,
|
|
width: 120,
|
|
|
height: 67,
|
|
height: 67,
|
|
|
backgroundColor: '#1E1C5B',
|
|
backgroundColor: '#1E1C5B',
|
|
|
borderRadius: 8,
|
|
borderRadius: 8,
|
|
|
- paddingTop: 5,
|
|
|
|
|
alignItems: 'center',
|
|
alignItems: 'center',
|
|
|
|
|
+ paddingTop: 5,
|
|
|
},
|
|
},
|
|
|
- molibiLabel: {
|
|
|
|
|
|
|
+ molibiText: {
|
|
|
color: '#7982CB',
|
|
color: '#7982CB',
|
|
|
fontSize: 12,
|
|
fontSize: 12,
|
|
|
},
|
|
},
|
|
|
molibiNum: {
|
|
molibiNum: {
|
|
|
color: '#FF8400',
|
|
color: '#FF8400',
|
|
|
fontSize: 18,
|
|
fontSize: 18,
|
|
|
- fontWeight: 'bold',
|
|
|
|
|
|
|
+ fontWeight: '400',
|
|
|
},
|
|
},
|
|
|
- molibiBtn: {
|
|
|
|
|
- marginTop: 5,
|
|
|
|
|
- },
|
|
|
|
|
- molibiBtnImg: {
|
|
|
|
|
|
|
+ addMolibiBtn: {
|
|
|
width: 105,
|
|
width: 105,
|
|
|
height: 30,
|
|
height: 30,
|
|
|
|
|
+ marginTop: 5,
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- // 开口动画区域
|
|
|
|
|
- openingBox: {
|
|
|
|
|
|
|
+ opening: {
|
|
|
position: 'absolute',
|
|
position: 'absolute',
|
|
|
- top: SCREEN_WIDTH * 1.21,
|
|
|
|
|
|
|
+ top: 455,
|
|
|
right: 42,
|
|
right: 42,
|
|
|
width: 133,
|
|
width: 133,
|
|
|
height: 82,
|
|
height: 82,
|
|
|
},
|
|
},
|
|
|
- openingImg: { width: '100%', height: '100%' },
|
|
|
|
|
-
|
|
|
|
|
- // 扭蛋把手
|
|
|
|
|
switchBox: {
|
|
switchBox: {
|
|
|
position: 'absolute',
|
|
position: 'absolute',
|
|
|
- top: SCREEN_WIDTH * 1.49,
|
|
|
|
|
|
|
+ top: 560,
|
|
|
|
|
+ left: 42,
|
|
|
width: 65,
|
|
width: 65,
|
|
|
height: 65,
|
|
height: 65,
|
|
|
|
|
+ zIndex: 3,
|
|
|
},
|
|
},
|
|
|
- switchBox1: { left: 42 },
|
|
|
|
|
- switchBox5: { right: 42 },
|
|
|
|
|
- switchImg: { width: '100%', height: '100%' },
|
|
|
|
|
-
|
|
|
|
|
- // 底部按钮
|
|
|
|
|
- bottomBtns: {
|
|
|
|
|
- flexDirection: 'row',
|
|
|
|
|
- justifyContent: 'center',
|
|
|
|
|
- marginTop: -SCREEN_WIDTH * 0.24,
|
|
|
|
|
- paddingHorizontal: 20,
|
|
|
|
|
|
|
+ switchBoxRight: { // switchBox5
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ top: 560,
|
|
|
|
|
+ right: 42,
|
|
|
|
|
+ width: 65,
|
|
|
|
|
+ height: 65,
|
|
|
|
|
+ zIndex: 3,
|
|
|
|
|
+ },
|
|
|
|
|
+ ballsContainer: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ top: 125,
|
|
|
|
|
+ left: 43,
|
|
|
|
|
+ width: 275,
|
|
|
|
|
+ height: 278,
|
|
|
|
|
+ zIndex: 9,
|
|
|
|
|
+ overflow: 'hidden',
|
|
|
|
|
+ },
|
|
|
|
|
+ ball: {
|
|
|
|
|
+ width: BALL_SIZE,
|
|
|
|
|
+ height: BALL_SIZE,
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ left: 0,
|
|
|
|
|
+ top: 0,
|
|
|
},
|
|
},
|
|
|
- submitBtn: {
|
|
|
|
|
- marginHorizontal: SCREEN_WIDTH * 0.08,
|
|
|
|
|
|
|
+ droppingBall: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ top: 475,
|
|
|
|
|
+ right: 87,
|
|
|
|
|
+ width: 49,
|
|
|
|
|
+ height: 48,
|
|
|
|
|
+ zIndex: 3,
|
|
|
},
|
|
},
|
|
|
- submitBtnImg: {
|
|
|
|
|
|
|
+ playBtn: {
|
|
|
|
|
+ position: 'absolute',
|
|
|
|
|
+ bottom: 90, // Match Vue: 179rpx / 2 = 89.5
|
|
|
|
|
+ zIndex: 10,
|
|
|
width: 73,
|
|
width: 73,
|
|
|
height: 50,
|
|
height: 50,
|
|
|
},
|
|
},
|
|
|
|
|
+ playBtn1: {
|
|
|
|
|
+ left: 98,
|
|
|
|
|
+ },
|
|
|
|
|
+ playBtn5: {
|
|
|
|
|
+ right: 98,
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|