|
@@ -5,6 +5,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
|
|
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
|
|
|
|
|
|
|
+import LotteryGrid from './components/LotteryGrid';
|
|
|
import LotteryReel from './components/LotteryReel';
|
|
import LotteryReel from './components/LotteryReel';
|
|
|
|
|
|
|
|
import { Images } from '@/constants/images';
|
|
import { Images } from '@/constants/images';
|
|
@@ -21,6 +22,8 @@ export default function LotteryScreen() {
|
|
|
const insets = useSafeAreaInsets();
|
|
const insets = useSafeAreaInsets();
|
|
|
|
|
|
|
|
const num = Number(params.num) || 1;
|
|
const num = Number(params.num) || 1;
|
|
|
|
|
+ const isGrid = num >= 10;
|
|
|
|
|
+
|
|
|
const tradeNo = params.tradeNo as string;
|
|
const tradeNo = params.tradeNo as string;
|
|
|
const poolId = params.poolId as string;
|
|
const poolId = params.poolId as string;
|
|
|
|
|
|
|
@@ -28,6 +31,7 @@ export default function LotteryScreen() {
|
|
|
const [pool, setPool] = useState<any[]>([]);
|
|
const [pool, setPool] = useState<any[]>([]);
|
|
|
const [loading, setLoading] = useState(true);
|
|
const [loading, setLoading] = useState(true);
|
|
|
const [sound, setSound] = useState<Audio.Sound>();
|
|
const [sound, setSound] = useState<Audio.Sound>();
|
|
|
|
|
+ const [animationFinished, setAnimationFinished] = useState(false);
|
|
|
|
|
|
|
|
// Timer Ref for cleanup
|
|
// Timer Ref for cleanup
|
|
|
const timerRef = useRef<any>(null);
|
|
const timerRef = useRef<any>(null);
|
|
@@ -78,22 +82,24 @@ export default function LotteryScreen() {
|
|
|
setResults(list);
|
|
setResults(list);
|
|
|
|
|
|
|
|
// 2. Fetch Pool (Goods) if not passed
|
|
// 2. Fetch Pool (Goods) if not passed
|
|
|
- // In a real app we might pass this via context or params if small
|
|
|
|
|
- // For now, fetch detail to get the goods list
|
|
|
|
|
- if (poolId) {
|
|
|
|
|
|
|
+ if (poolId && !isGrid) {
|
|
|
const detailRes = await services.award.getPoolDetail(poolId);
|
|
const detailRes = await services.award.getPoolDetail(poolId);
|
|
|
const goods = detailRes?.luckGoodsList || [];
|
|
const goods = detailRes?.luckGoodsList || [];
|
|
|
setPool(goods);
|
|
setPool(goods);
|
|
|
- } else {
|
|
|
|
|
- // Fallback if no poolId, just use result as pool (not ideal as it's small)
|
|
|
|
|
|
|
+ } else if (!isGrid) {
|
|
|
setPool(list);
|
|
setPool(list);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Auto show result after animation
|
|
// Auto show result after animation
|
|
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
|
- timerRef.current = setTimeout(() => {
|
|
|
|
|
- handleFinish(list);
|
|
|
|
|
- }, 2800); // 2000ms duration + buffer
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (!isGrid) {
|
|
|
|
|
+ // Reel mode uses timer
|
|
|
|
|
+ timerRef.current = setTimeout(() => {
|
|
|
|
|
+ handleFinish(list);
|
|
|
|
|
+ }, 2800);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Grid mode handles finish via callback
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Load Data Error', error);
|
|
console.error('Load Data Error', error);
|
|
|
// Safety fallback?
|
|
// Safety fallback?
|
|
@@ -106,6 +112,14 @@ export default function LotteryScreen() {
|
|
|
clearTimeout(timerRef.current);
|
|
clearTimeout(timerRef.current);
|
|
|
timerRef.current = null;
|
|
timerRef.current = null;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if (isGrid) {
|
|
|
|
|
+ // Stop sound and mark finished, stay on page
|
|
|
|
|
+ sound?.stopAsync();
|
|
|
|
|
+ setAnimationFinished(true);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Navigate to result modal/screen
|
|
// Navigate to result modal/screen
|
|
|
const data = finalResults || results;
|
|
const data = finalResults || results;
|
|
|
// We can navigate to a result page or show a local modal components
|
|
// We can navigate to a result page or show a local modal components
|
|
@@ -120,8 +134,21 @@ export default function LotteryScreen() {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleSkip = () => {
|
|
const handleSkip = () => {
|
|
|
- sound?.stopAsync();
|
|
|
|
|
- handleFinish();
|
|
|
|
|
|
|
+ if (isGrid) {
|
|
|
|
|
+ if (animationFinished) {
|
|
|
|
|
+ // User clicked "Claim Prize", exit
|
|
|
|
|
+ router.back();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // User clicked "Skip Animation", finish immediately
|
|
|
|
|
+ // Ideally LotteryGrid should expose a "finish" method or we force state
|
|
|
|
|
+ // But for now calling handleFinish stops navigation
|
|
|
|
|
+ handleFinish();
|
|
|
|
|
+ // Note: This doesn't force cards to flip instantly unless we implement that prop in LotteryGrid
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sound?.stopAsync();
|
|
|
|
|
+ handleFinish();
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
if (loading) return (
|
|
if (loading) return (
|
|
@@ -137,49 +164,61 @@ export default function LotteryScreen() {
|
|
|
<View style={styles.maskPage} />
|
|
<View style={styles.maskPage} />
|
|
|
|
|
|
|
|
<View style={[styles.wrapper, { paddingTop: padding }]}>
|
|
<View style={[styles.wrapper, { paddingTop: padding }]}>
|
|
|
- <View style={styles.reelsContainer}>
|
|
|
|
|
- {/* Left Column Masks */}
|
|
|
|
|
- <View style={[styles.maskLeft, { width: maskWidth }]} />
|
|
|
|
|
-
|
|
|
|
|
- {/* Reels */}
|
|
|
|
|
- <View style={num === 1 ? styles.height1 : styles.height5}>
|
|
|
|
|
- {results.map((item, index) => (
|
|
|
|
|
- <View key={index} style={styles.reelRow}>
|
|
|
|
|
- <LotteryReel
|
|
|
|
|
- key={`reel-${index}-${pool.length}-${results.length}`}
|
|
|
|
|
- pool={pool.length > 0 ? pool : results}
|
|
|
|
|
- result={item}
|
|
|
|
|
- width={SCREEN_WIDTH}
|
|
|
|
|
- itemWidth={itemWidth}
|
|
|
|
|
- index={index}
|
|
|
|
|
- delay={index * 30} // Very fast stagger
|
|
|
|
|
- duration={2000} // Faster spin (was 3000)
|
|
|
|
|
- />
|
|
|
|
|
- </View>
|
|
|
|
|
- ))}
|
|
|
|
|
- </View>
|
|
|
|
|
|
|
+ {isGrid ? (
|
|
|
|
|
+ <LotteryGrid
|
|
|
|
|
+ results={results}
|
|
|
|
|
+ onFinish={() => handleFinish()}
|
|
|
|
|
+ />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <View style={styles.reelsContainer}>
|
|
|
|
|
+ {/* Left Column Masks */}
|
|
|
|
|
+ <View style={[styles.maskLeft, { width: maskWidth }]} />
|
|
|
|
|
+
|
|
|
|
|
+ {/* Reels */}
|
|
|
|
|
+ <View style={num === 1 ? styles.height1 : styles.height5}>
|
|
|
|
|
+ {results.map((item, index) => (
|
|
|
|
|
+ <View key={index} style={styles.reelRow}>
|
|
|
|
|
+ <LotteryReel
|
|
|
|
|
+ key={`reel-${index}-${pool.length}-${results.length}`}
|
|
|
|
|
+ pool={pool.length > 0 ? pool : results}
|
|
|
|
|
+ result={item}
|
|
|
|
|
+ width={SCREEN_WIDTH}
|
|
|
|
|
+ itemWidth={itemWidth}
|
|
|
|
|
+ index={index}
|
|
|
|
|
+ delay={index * 30} // Very fast stagger
|
|
|
|
|
+ duration={2000} // Faster spin (was 3000)
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </View>
|
|
|
|
|
|
|
|
- {/* Right Column Masks */}
|
|
|
|
|
- <View style={[styles.maskRight, { width: maskWidth }]} />
|
|
|
|
|
-
|
|
|
|
|
- {/* Middle Frame */}
|
|
|
|
|
- <Image
|
|
|
|
|
- source={{ uri: num === 1 ? Images.resource.lottery_middle_s : Images.resource.lottery_middle_l }}
|
|
|
|
|
- style={[
|
|
|
|
|
- styles.middleImage,
|
|
|
|
|
- {
|
|
|
|
|
- height: num === 1 ? 200 : 540,
|
|
|
|
|
- top: num === 1 ? -35 : -10,
|
|
|
|
|
- }
|
|
|
|
|
- ]}
|
|
|
|
|
- contentFit="contain"
|
|
|
|
|
- />
|
|
|
|
|
- </View>
|
|
|
|
|
|
|
+ {/* Right Column Masks */}
|
|
|
|
|
+ <View style={[styles.maskRight, { width: maskWidth }]} />
|
|
|
|
|
+
|
|
|
|
|
+ {/* Middle Frame */}
|
|
|
|
|
+ <Image
|
|
|
|
|
+ source={{ uri: num === 1 ? Images.resource.lottery_middle_s : Images.resource.lottery_middle_l }}
|
|
|
|
|
+ style={[
|
|
|
|
|
+ styles.middleImage,
|
|
|
|
|
+ {
|
|
|
|
|
+ height: num === 1 ? 200 : 540,
|
|
|
|
|
+ top: num === 1 ? -35 : -10,
|
|
|
|
|
+ }
|
|
|
|
|
+ ]}
|
|
|
|
|
+ contentFit="contain"
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )}
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
<View style={styles.bottom}>
|
|
<View style={styles.bottom}>
|
|
|
- <TouchableOpacity style={styles.skipBtn} onPress={handleSkip}>
|
|
|
|
|
- <Text style={styles.skipText}>跳过动画</Text>
|
|
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ style={styles.skipBtn}
|
|
|
|
|
+ onPress={handleSkip}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text style={styles.skipText}>
|
|
|
|
|
+ {isGrid && animationFinished ? '收下奖品' : '跳过动画'}
|
|
|
|
|
+ </Text>
|
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
|
</View>
|
|
</View>
|
|
|
</View>
|
|
</View>
|