|
|
@@ -1,27 +1,35 @@
|
|
|
-import { Audio, AVPlaybackStatus, ResizeMode, Video } from 'expo-av';
|
|
|
-import { Image } from 'expo-image';
|
|
|
-import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
|
|
|
-import React, { useEffect, useRef, useState } from 'react';
|
|
|
-import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
|
-import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
+import { Audio, AVPlaybackStatus, ResizeMode, Video } from "expo-av";
|
|
|
+import { Image } from "expo-image";
|
|
|
+import { Stack, useLocalSearchParams, useRouter } from "expo-router";
|
|
|
+import React, { useEffect, useRef, useState } from "react";
|
|
|
+import {
|
|
|
+ Dimensions,
|
|
|
+ StyleSheet,
|
|
|
+ Text,
|
|
|
+ TouchableOpacity,
|
|
|
+ View,
|
|
|
+} from "react-native";
|
|
|
+import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
|
|
|
-import LotteryGrid from './components/LotteryGrid';
|
|
|
-import LotteryReel from './components/LotteryReel';
|
|
|
+import LotteryGrid from "./components/LotteryGrid";
|
|
|
+import LotteryReel from "./components/LotteryReel";
|
|
|
|
|
|
-import { Images } from '@/constants/images';
|
|
|
-import services from '@/services/api';
|
|
|
+import { Images } from "@/constants/images";
|
|
|
+import services from "@/services/api";
|
|
|
|
|
|
-const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
|
+const { width: SCREEN_WIDTH } = Dimensions.get("window");
|
|
|
|
|
|
// Sound URL from Uniapp config
|
|
|
-const SOUND_URL = 'https://cdn.acetoys.cn/kai_xin_ma_te/resource/magic/lottery.mp3';
|
|
|
-const DEFAULT_JACKPOT_VIDEO = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart/box/lottery/jackpot.mp4';
|
|
|
+const SOUND_URL =
|
|
|
+ "https://cdn.acefig.com/kai_xin_ma_te/resource/magic/lottery.mp3";
|
|
|
+const DEFAULT_JACKPOT_VIDEO =
|
|
|
+ "https://cdn.acefig.com/kai_xin_ma_te/supermart/box/lottery/jackpot.mp4";
|
|
|
|
|
|
export default function LotteryScreen() {
|
|
|
const router = useRouter();
|
|
|
const params = useLocalSearchParams();
|
|
|
const insets = useSafeAreaInsets();
|
|
|
-
|
|
|
+
|
|
|
const num = Number(params.num) || 1;
|
|
|
const isGrid = num >= 10;
|
|
|
|
|
|
@@ -33,21 +41,21 @@ export default function LotteryScreen() {
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
const [sound, setSound] = useState<Audio.Sound>();
|
|
|
const [animationFinished, setAnimationFinished] = useState(false);
|
|
|
-
|
|
|
+
|
|
|
// Video state
|
|
|
const [videoVisible, setVideoVisible] = useState(false);
|
|
|
- const [videoUrl, setVideoUrl] = useState('');
|
|
|
+ const [videoUrl, setVideoUrl] = useState("");
|
|
|
const videoRef = useRef<Video>(null);
|
|
|
-
|
|
|
+
|
|
|
// Timer Ref for cleanup
|
|
|
const timerRef = useRef<any>(null);
|
|
|
|
|
|
// Layout calculations
|
|
|
const singleItemWidth = 240;
|
|
|
- const itemWidth = (num === 1) ? singleItemWidth : 72;
|
|
|
- const maskWidth = (SCREEN_WIDTH - ((num === 1) ? singleItemWidth : 72)) / 2;
|
|
|
- const padding = SCREEN_WIDTH > 375 ? 32 : 0;
|
|
|
-
|
|
|
+ const itemWidth = num === 1 ? singleItemWidth : 72;
|
|
|
+ const maskWidth = (SCREEN_WIDTH - (num === 1 ? singleItemWidth : 72)) / 2;
|
|
|
+ const padding = SCREEN_WIDTH > 375 ? 32 : 0;
|
|
|
+
|
|
|
// Dynamic styles
|
|
|
const singleReelHeight = singleItemWidth * 1.35 + 20; // 324 + 20
|
|
|
|
|
|
@@ -58,235 +66,253 @@ export default function LotteryScreen() {
|
|
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
|
};
|
|
|
}, []);
|
|
|
-
|
|
|
+
|
|
|
// ... (rest of the file)
|
|
|
-
|
|
|
+
|
|
|
// Update render styles inline or via StyleSheet
|
|
|
// I will update the stylesheet usage in return and the StyleSheet definition below
|
|
|
|
|
|
-
|
|
|
const init = async () => {
|
|
|
- try {
|
|
|
- console.log('LotteryScreen Init: num', num, 'tradeNo', tradeNo, 'poolId', poolId);
|
|
|
- await Promise.all([loadData() /*, playSound()*/]);
|
|
|
- } catch (e) {
|
|
|
- console.error('LotteryScreen Init Error', e);
|
|
|
- } finally {
|
|
|
- setLoading(false);
|
|
|
- }
|
|
|
- }
|
|
|
+ try {
|
|
|
+ console.log(
|
|
|
+ "LotteryScreen Init: num",
|
|
|
+ num,
|
|
|
+ "tradeNo",
|
|
|
+ tradeNo,
|
|
|
+ "poolId",
|
|
|
+ poolId,
|
|
|
+ );
|
|
|
+ await Promise.all([loadData() /*, playSound()*/]);
|
|
|
+ } catch (e) {
|
|
|
+ console.error("LotteryScreen Init Error", e);
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
const playSound = async () => {
|
|
|
try {
|
|
|
- const { sound } = await Audio.Sound.createAsync(
|
|
|
- { uri: SOUND_URL }
|
|
|
- );
|
|
|
+ const { sound } = await Audio.Sound.createAsync({ uri: SOUND_URL });
|
|
|
setSound(sound);
|
|
|
await sound.playAsync();
|
|
|
} catch (error) {
|
|
|
- console.log('Error playing sound', error);
|
|
|
+ console.log("Error playing sound", error);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const loadData = async () => {
|
|
|
- if (tradeNo) {
|
|
|
- try {
|
|
|
- const res: any = await services.award.getApplyResult(tradeNo);
|
|
|
- const list = res?.data?.inventoryList || res?.inventoryList || [];
|
|
|
- setResults(list);
|
|
|
-
|
|
|
- // 2. Fetch Pool (Goods) if not passed
|
|
|
- if (poolId && !isGrid) {
|
|
|
- const detailRes = await services.award.getPoolDetail(poolId);
|
|
|
- const goods = detailRes?.luckGoodsList || [];
|
|
|
- setPool(goods);
|
|
|
- } else if (!isGrid) {
|
|
|
- setPool(list);
|
|
|
- }
|
|
|
-
|
|
|
- // Auto show result after animation
|
|
|
- if (timerRef.current) clearTimeout(timerRef.current);
|
|
|
-
|
|
|
- // Check for Level A video
|
|
|
- const hasLevelA = list.some((item: any) => ['A', 'a'].includes(item.level));
|
|
|
- // Also check res.video if available
|
|
|
- const playVideoUrl = res.video || (hasLevelA ? DEFAULT_JACKPOT_VIDEO : '');
|
|
|
-
|
|
|
- if (playVideoUrl) {
|
|
|
- console.log('Found Level A, preparing video:', playVideoUrl);
|
|
|
- setVideoUrl(playVideoUrl);
|
|
|
- setVideoVisible(true);
|
|
|
- // Do NOT set timer here, wait for video to finish
|
|
|
- } else {
|
|
|
- if (!isGrid) {
|
|
|
- // Reel mode uses timer
|
|
|
- timerRef.current = setTimeout(() => {
|
|
|
- handleFinish(list);
|
|
|
- }, 2800);
|
|
|
- }
|
|
|
- }
|
|
|
- // Grid mode handles finish via callback
|
|
|
- } catch (error) {
|
|
|
- console.error('Load Data Error', error);
|
|
|
- // Safety fallback?
|
|
|
- }
|
|
|
+ if (tradeNo) {
|
|
|
+ try {
|
|
|
+ const res: any = await services.award.getApplyResult(tradeNo);
|
|
|
+ const list = res?.data?.inventoryList || res?.inventoryList || [];
|
|
|
+ setResults(list);
|
|
|
+
|
|
|
+ // 2. Fetch Pool (Goods) if not passed
|
|
|
+ if (poolId && !isGrid) {
|
|
|
+ const detailRes = await services.award.getPoolDetail(poolId);
|
|
|
+ const goods = detailRes?.luckGoodsList || [];
|
|
|
+ setPool(goods);
|
|
|
+ } else if (!isGrid) {
|
|
|
+ setPool(list);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Auto show result after animation
|
|
|
+ if (timerRef.current) clearTimeout(timerRef.current);
|
|
|
+
|
|
|
+ // Check for Level A video
|
|
|
+ const hasLevelA = list.some((item: any) =>
|
|
|
+ ["A", "a"].includes(item.level),
|
|
|
+ );
|
|
|
+ // Also check res.video if available
|
|
|
+ const playVideoUrl =
|
|
|
+ res.video || (hasLevelA ? DEFAULT_JACKPOT_VIDEO : "");
|
|
|
+
|
|
|
+ if (playVideoUrl) {
|
|
|
+ console.log("Found Level A, preparing video:", playVideoUrl);
|
|
|
+ setVideoUrl(playVideoUrl);
|
|
|
+ setVideoVisible(true);
|
|
|
+ // Do NOT set timer here, wait for video to finish
|
|
|
+ } else {
|
|
|
+ if (!isGrid) {
|
|
|
+ // Reel mode uses timer
|
|
|
+ timerRef.current = setTimeout(() => {
|
|
|
+ handleFinish(list);
|
|
|
+ }, 2800);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Grid mode handles finish via callback
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Load Data Error", error);
|
|
|
+ // Safety fallback?
|
|
|
}
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const handleFinish = (finalResults?: any[]) => {
|
|
|
if (timerRef.current) {
|
|
|
- clearTimeout(timerRef.current);
|
|
|
- timerRef.current = null;
|
|
|
+ clearTimeout(timerRef.current);
|
|
|
+ timerRef.current = null;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (isGrid) {
|
|
|
- // Stop sound and mark finished, stay on page
|
|
|
- sound?.stopAsync();
|
|
|
- setAnimationFinished(true);
|
|
|
- return;
|
|
|
+ // Stop sound and mark finished, stay on page
|
|
|
+ sound?.stopAsync();
|
|
|
+ setAnimationFinished(true);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
// Navigate to result modal/screen
|
|
|
const data = finalResults || results;
|
|
|
// We can navigate to a result page or show a local modal components
|
|
|
// Assuming we have a result route
|
|
|
- router.replace({
|
|
|
- pathname: '/lottery/result' as any,
|
|
|
- params: {
|
|
|
- results: JSON.stringify(data),
|
|
|
- poolId
|
|
|
- }
|
|
|
+ router.replace({
|
|
|
+ pathname: "/lottery/result" as any,
|
|
|
+ params: {
|
|
|
+ results: JSON.stringify(data),
|
|
|
+ poolId,
|
|
|
+ },
|
|
|
});
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
const handleSkip = () => {
|
|
|
- if (videoVisible) {
|
|
|
- setVideoVisible(false);
|
|
|
- handleFinish();
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (videoVisible) {
|
|
|
+ setVideoVisible(false);
|
|
|
+ handleFinish();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- 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
|
|
|
- }
|
|
|
+ if (isGrid) {
|
|
|
+ if (animationFinished) {
|
|
|
+ // User clicked "Claim Prize", exit
|
|
|
+ router.back();
|
|
|
} else {
|
|
|
- sound?.stopAsync();
|
|
|
- handleFinish();
|
|
|
+ // 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 (
|
|
|
<View style={styles.container}>
|
|
|
- <Image source={{ uri: Images.mine.kaixinMineBg }} style={styles.bg} contentFit="cover" />
|
|
|
+ <Image
|
|
|
+ source={{ uri: Images.mine.kaixinMineBg }}
|
|
|
+ style={styles.bg}
|
|
|
+ contentFit="cover"
|
|
|
+ />
|
|
|
</View>
|
|
|
- );
|
|
|
+ );
|
|
|
|
|
|
return (
|
|
|
<View style={styles.container}>
|
|
|
<Stack.Screen options={{ headerShown: false }} />
|
|
|
- <Image source={{ uri: Images.mine.kaixinMineBg }} style={styles.bg} contentFit="cover" />
|
|
|
+ <Image
|
|
|
+ source={{ uri: Images.mine.kaixinMineBg }}
|
|
|
+ style={styles.bg}
|
|
|
+ contentFit="cover"
|
|
|
+ />
|
|
|
<View style={styles.maskPage} />
|
|
|
|
|
|
<View style={[styles.wrapper, { paddingTop: padding }]}>
|
|
|
- {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,
|
|
|
- num === 1 && { height: singleReelHeight, marginBottom: 0 }
|
|
|
- ]}
|
|
|
- >
|
|
|
- <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>
|
|
|
- ))}
|
|
|
+ {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,
|
|
|
+ num === 1 && { height: singleReelHeight, marginBottom: 0 },
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <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 ? 360 : 540,
|
|
|
- top: num === 1 ? -20 : -10,
|
|
|
- }
|
|
|
- ]}
|
|
|
- contentFit="contain"
|
|
|
- />
|
|
|
- </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 ? 360 : 540,
|
|
|
+ top: num === 1 ? -20 : -10,
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ contentFit="contain"
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
|
|
|
{/* Video Overlay */}
|
|
|
{videoVisible && videoUrl ? (
|
|
|
<View style={styles.videoContainer}>
|
|
|
- <Video
|
|
|
- ref={videoRef}
|
|
|
- key={videoUrl}
|
|
|
- source={{ uri: videoUrl }}
|
|
|
- style={styles.video}
|
|
|
- resizeMode={ResizeMode.COVER}
|
|
|
- shouldPlay
|
|
|
- isLooping={false}
|
|
|
- useNativeControls={false}
|
|
|
- onLoad={() => {
|
|
|
- videoRef.current?.playAsync();
|
|
|
- }}
|
|
|
- onPlaybackStatusUpdate={(status: AVPlaybackStatus) => {
|
|
|
- if (status.isLoaded && status.didJustFinish) {
|
|
|
- setVideoVisible(false);
|
|
|
- handleFinish();
|
|
|
- }
|
|
|
- }}
|
|
|
- onError={() => {
|
|
|
- setVideoVisible(false);
|
|
|
- handleFinish();
|
|
|
- }}
|
|
|
- />
|
|
|
+ <Video
|
|
|
+ ref={videoRef}
|
|
|
+ key={videoUrl}
|
|
|
+ source={{ uri: videoUrl }}
|
|
|
+ style={styles.video}
|
|
|
+ resizeMode={ResizeMode.COVER}
|
|
|
+ shouldPlay
|
|
|
+ isLooping={false}
|
|
|
+ useNativeControls={false}
|
|
|
+ onLoad={() => {
|
|
|
+ videoRef.current?.playAsync();
|
|
|
+ }}
|
|
|
+ onPlaybackStatusUpdate={(status: AVPlaybackStatus) => {
|
|
|
+ if (status.isLoaded && status.didJustFinish) {
|
|
|
+ setVideoVisible(false);
|
|
|
+ handleFinish();
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onError={() => {
|
|
|
+ setVideoVisible(false);
|
|
|
+ handleFinish();
|
|
|
+ }}
|
|
|
+ />
|
|
|
</View>
|
|
|
) : null}
|
|
|
|
|
|
<View style={styles.bottom}>
|
|
|
- <TouchableOpacity
|
|
|
- style={styles.skipBtn}
|
|
|
- onPress={handleSkip}
|
|
|
- >
|
|
|
- <Text style={styles.skipText}>
|
|
|
- {videoVisible ? '跳过动画' : (isGrid && animationFinished ? '收下奖品' : '跳过动画')}
|
|
|
- </Text>
|
|
|
+ <TouchableOpacity style={styles.skipBtn} onPress={handleSkip}>
|
|
|
+ <Text style={styles.skipText}>
|
|
|
+ {videoVisible
|
|
|
+ ? "跳过动画"
|
|
|
+ : isGrid && animationFinished
|
|
|
+ ? "收下奖品"
|
|
|
+ : "跳过动画"}
|
|
|
+ </Text>
|
|
|
</TouchableOpacity>
|
|
|
</View>
|
|
|
</View>
|
|
|
@@ -296,99 +322,99 @@ export default function LotteryScreen() {
|
|
|
const styles = StyleSheet.create({
|
|
|
container: {
|
|
|
flex: 1,
|
|
|
- backgroundColor: '#222335',
|
|
|
+ backgroundColor: "#222335",
|
|
|
},
|
|
|
bg: {
|
|
|
- position: 'absolute',
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
+ position: "absolute",
|
|
|
+ width: "100%",
|
|
|
+ height: "100%",
|
|
|
zIndex: -1,
|
|
|
},
|
|
|
maskPage: {
|
|
|
...StyleSheet.absoluteFillObject,
|
|
|
- backgroundColor: 'rgba(0,0,0,0.4)',
|
|
|
+ backgroundColor: "rgba(0,0,0,0.4)",
|
|
|
zIndex: 0,
|
|
|
},
|
|
|
wrapper: {
|
|
|
flex: 1,
|
|
|
- // justifyContent: 'center',
|
|
|
- alignItems: 'center',
|
|
|
+ // justifyContent: 'center',
|
|
|
+ alignItems: "center",
|
|
|
marginTop: 0, // Remove fixed margin, let flex center it or use justify center if needed
|
|
|
- justifyContent: 'center', // Center vertically
|
|
|
+ justifyContent: "center", // Center vertically
|
|
|
},
|
|
|
reelsContainer: {
|
|
|
- position: 'relative',
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'center',
|
|
|
+ position: "relative",
|
|
|
+ alignItems: "center",
|
|
|
+ justifyContent: "center",
|
|
|
// backgroundColor: 'rgba(255,0,0,0.1)', // Debug
|
|
|
},
|
|
|
reelRow: {
|
|
|
- overflow: 'hidden',
|
|
|
- width: SCREEN_WIDTH,
|
|
|
- alignItems: 'center',
|
|
|
- justifyContent: 'center',
|
|
|
- marginBottom: 12, // Gap between rows
|
|
|
- height: 94, // Height of one row (card height + tiny padding)
|
|
|
+ overflow: "hidden",
|
|
|
+ width: SCREEN_WIDTH,
|
|
|
+ alignItems: "center",
|
|
|
+ justifyContent: "center",
|
|
|
+ marginBottom: 12, // Gap between rows
|
|
|
+ height: 94, // Height of one row (card height + tiny padding)
|
|
|
},
|
|
|
height1: {
|
|
|
- height: 360, // Accommodate larger card (approx 325px height)
|
|
|
- justifyContent: 'center',
|
|
|
+ height: 360, // Accommodate larger card (approx 325px height)
|
|
|
+ justifyContent: "center",
|
|
|
},
|
|
|
height5: {
|
|
|
- height: 540, // 5 rows * (~100)
|
|
|
- justifyContent: 'center',
|
|
|
- paddingVertical: 10,
|
|
|
+ height: 540, // 5 rows * (~100)
|
|
|
+ justifyContent: "center",
|
|
|
+ paddingVertical: 10,
|
|
|
},
|
|
|
maskLeft: {
|
|
|
- position: 'absolute',
|
|
|
+ position: "absolute",
|
|
|
left: 0,
|
|
|
top: 0,
|
|
|
bottom: 0,
|
|
|
- backgroundColor: 'rgba(0,0,0,0.5)',
|
|
|
+ backgroundColor: "rgba(0,0,0,0.5)",
|
|
|
zIndex: 10,
|
|
|
},
|
|
|
maskRight: {
|
|
|
- position: 'absolute',
|
|
|
- right: 0,
|
|
|
- top: 0,
|
|
|
- bottom: 0,
|
|
|
- backgroundColor: 'rgba(0,0,0,0.5)',
|
|
|
- zIndex: 10,
|
|
|
+ position: "absolute",
|
|
|
+ right: 0,
|
|
|
+ top: 0,
|
|
|
+ bottom: 0,
|
|
|
+ backgroundColor: "rgba(0,0,0,0.5)",
|
|
|
+ zIndex: 10,
|
|
|
},
|
|
|
middleImage: {
|
|
|
- position: 'absolute',
|
|
|
- width: SCREEN_WIDTH,
|
|
|
- zIndex: 20,
|
|
|
- left: 0,
|
|
|
- // The image frame should be centered over the reels
|
|
|
- // top is handled dynamically or via flex centering in parent if possible
|
|
|
+ position: "absolute",
|
|
|
+ width: SCREEN_WIDTH,
|
|
|
+ zIndex: 20,
|
|
|
+ left: 0,
|
|
|
+ // The image frame should be centered over the reels
|
|
|
+ // top is handled dynamically or via flex centering in parent if possible
|
|
|
},
|
|
|
bottom: {
|
|
|
- position: 'absolute',
|
|
|
+ position: "absolute",
|
|
|
bottom: 50,
|
|
|
- width: '100%',
|
|
|
- alignItems: 'center',
|
|
|
- zIndex: 60,
|
|
|
+ width: "100%",
|
|
|
+ alignItems: "center",
|
|
|
+ zIndex: 60,
|
|
|
},
|
|
|
skipBtn: {
|
|
|
- backgroundColor: 'rgba(255,255,255,0.2)',
|
|
|
- paddingHorizontal: 30,
|
|
|
- paddingVertical: 10,
|
|
|
- borderRadius: 32,
|
|
|
- borderWidth: 1,
|
|
|
- borderColor: 'rgba(255,255,255,0.4)',
|
|
|
+ backgroundColor: "rgba(255,255,255,0.2)",
|
|
|
+ paddingHorizontal: 30,
|
|
|
+ paddingVertical: 10,
|
|
|
+ borderRadius: 32,
|
|
|
+ borderWidth: 1,
|
|
|
+ borderColor: "rgba(255,255,255,0.4)",
|
|
|
},
|
|
|
skipText: {
|
|
|
- color: '#fff',
|
|
|
- fontSize: 14,
|
|
|
+ color: "#fff",
|
|
|
+ fontSize: 14,
|
|
|
},
|
|
|
videoContainer: {
|
|
|
- ...StyleSheet.absoluteFillObject,
|
|
|
- backgroundColor: '#000',
|
|
|
- zIndex: 50,
|
|
|
+ ...StyleSheet.absoluteFillObject,
|
|
|
+ backgroundColor: "#000",
|
|
|
+ zIndex: 50,
|
|
|
},
|
|
|
video: {
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
+ width: "100%",
|
|
|
+ height: "100%",
|
|
|
},
|
|
|
});
|