floating-games-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +44 -0
- package/src/FloatingGames.tsx +25 -0
- package/src/assets/fonts/Creepster-Regular.ttf +0 -0
- package/src/assets/images/android-icon-background.png +0 -0
- package/src/assets/images/android-icon-foreground.png +0 -0
- package/src/assets/images/android-icon-monochrome.png +0 -0
- package/src/assets/images/blocks-icon.png +0 -0
- package/src/assets/images/bright-rubber-puzzles-blue.png +0 -0
- package/src/assets/images/close.png +0 -0
- package/src/assets/images/eriri.png +0 -0
- package/src/assets/images/favicon.png +0 -0
- package/src/assets/images/first-play.png +0 -0
- package/src/assets/images/game-over.gif +0 -0
- package/src/assets/images/game-pad.png +0 -0
- package/src/assets/images/game-pad2.png +0 -0
- package/src/assets/images/game-page-bg1.png +0 -0
- package/src/assets/images/hands-down-bg.png +0 -0
- package/src/assets/images/icon.png +0 -0
- package/src/assets/images/lets-play.gif +0 -0
- package/src/assets/images/lucky-day.gif +0 -0
- package/src/assets/images/maths.png +0 -0
- package/src/assets/images/not-playing.png +0 -0
- package/src/assets/images/number-puzzle-select.png +0 -0
- package/src/assets/images/number-puzzle-selector.png +0 -0
- package/src/assets/images/pause-clock.png +0 -0
- package/src/assets/images/pause-icon.png +0 -0
- package/src/assets/images/play-icon.png +0 -0
- package/src/assets/images/play-now.gif +0 -0
- package/src/assets/images/play-play.png +0 -0
- package/src/assets/images/play.png +0 -0
- package/src/assets/images/quit-game.png +0 -0
- package/src/assets/images/quit-icon.png +0 -0
- package/src/assets/images/quit.png +0 -0
- package/src/assets/images/review-win-selector.png +0 -0
- package/src/assets/images/select-bg.png +0 -0
- package/src/assets/images/select-logo.png +0 -0
- package/src/assets/images/spin-wheel-select.png +0 -0
- package/src/assets/images/spin-wheel-selector.png +0 -0
- package/src/assets/images/splash-icon.png +0 -0
- package/src/assets/images/start-button.png +0 -0
- package/src/assets/images/start-game.png +0 -0
- package/src/assets/images/stop-quit.png +0 -0
- package/src/assets/images/waiting-for-you.gif +0 -0
- package/src/assets/sounds/selector-music.mp3 +0 -0
- package/src/assets/sounds/timeup-sound.wav +0 -0
- package/src/components/atoms/Amount.tsx +48 -0
- package/src/components/atoms/FloatingMenu.tsx +119 -0
- package/src/components/atoms/ShimmerHR.tsx +64 -0
- package/src/components/atoms/UseAnimatedCounter.tsx +31 -0
- package/src/components/games/Chain.tsx +26 -0
- package/src/components/games/GameItem.tsx +38 -0
- package/src/components/games/GameSelectorScreen.tsx +221 -0
- package/src/components/games/GameSplash.tsx +44 -0
- package/src/components/games/HangingSign.tsx +75 -0
- package/src/components/games/SplashScreen.tsx +69 -0
- package/src/components/games/number-puzzle/DragTile.tsx +65 -0
- package/src/components/games/number-puzzle/GameScreen.tsx +597 -0
- package/src/components/games/number-puzzle/PuzzleSplash.tsx +50 -0
- package/src/components/games/number-puzzle/TileComponent.tsx +47 -0
- package/src/components/games/review-and-win/EmojiTracker.tsx +96 -0
- package/src/components/games/review-and-win/GameScreen.tsx +135 -0
- package/src/components/games/review-and-win/Tile.tsx +60 -0
- package/src/components/games/spin-wheel/SpinAndWin.tsx +507 -0
- package/src/components/payment/CheckoutScreen.tsx +50 -0
- package/src/components/payment/FundAccount.tsx +281 -0
- package/src/components/payment/Payment.tsx +45 -0
- package/src/components/reusables/AppSelect.tsx +70 -0
- package/src/components/reusables/CountdownTimer.tsx +116 -0
- package/src/components/reusables/FloatingButton.tsx +71 -0
- package/src/components/reusables/SoundPlayer.ts +28 -0
- package/src/components/utils/shuffle.ts +48 -0
- package/src/context/GameContext.tsx +15 -0
- package/src/index.tsx +5 -0
- package/src/modals/BottomSheetModal.tsx +38 -0
- package/src/modals/GameOverModal.tsx +76 -0
- package/src/modals/PauseModal.tsx +95 -0
- package/src/navigation/GameNavigator.tsx +48 -0
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "floating-games-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A React Native gaming SDK with 3 built-in games",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"types": "src/index.tsx",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"react-native",
|
|
9
|
+
"games",
|
|
10
|
+
"sdk",
|
|
11
|
+
"expo",
|
|
12
|
+
"puzzle",
|
|
13
|
+
"spin-wheel"
|
|
14
|
+
],
|
|
15
|
+
"author": "Olive Okechukwu <bmithzee@gmail.com>",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://gitlab.com/bmithzee/floating-games-sdk"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.0.0",
|
|
26
|
+
"@types/react": "*",
|
|
27
|
+
"@types/react-native": "*",
|
|
28
|
+
"react": "*",
|
|
29
|
+
"react-native": "*",
|
|
30
|
+
"react-native-gesture-handler": "*",
|
|
31
|
+
"expo-status-bar": "*"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": "*",
|
|
35
|
+
"react-native": "*",
|
|
36
|
+
"@react-navigation/native": "*",
|
|
37
|
+
"@react-navigation/native-stack": "*",
|
|
38
|
+
"expo-linear-gradient": "*",
|
|
39
|
+
"expo-status-bar": "*",
|
|
40
|
+
"react-native-gesture-handler": "*",
|
|
41
|
+
"react-native-safe-area-context": "*",
|
|
42
|
+
"@react-native-async-storage/async-storage": "*"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
NavigationContainer,
|
|
4
|
+
NavigationIndependentTree,
|
|
5
|
+
} from '@react-navigation/native';
|
|
6
|
+
|
|
7
|
+
import { GameProvider } from './context/GameContext';
|
|
8
|
+
import GameNavigator from './navigation/GameNavigator';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
name: string;
|
|
12
|
+
balance: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function FloatingGames({ name, balance }: Props) {
|
|
16
|
+
return (
|
|
17
|
+
<GameProvider name={name} balance={balance}>
|
|
18
|
+
<NavigationIndependentTree>
|
|
19
|
+
<NavigationContainer>
|
|
20
|
+
<GameNavigator />
|
|
21
|
+
</NavigationContainer>
|
|
22
|
+
</NavigationIndependentTree>
|
|
23
|
+
</GameProvider>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, Text, TextStyle, View } from 'react-native';
|
|
3
|
+
import { UseAnimatedCounter } from './UseAnimatedCounter';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
value: number;
|
|
7
|
+
color?: string;
|
|
8
|
+
fontSize: number;
|
|
9
|
+
fontWeight?: TextStyle['fontWeight'];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Amount({
|
|
13
|
+
value,
|
|
14
|
+
color = '#fff',
|
|
15
|
+
fontSize = 25,
|
|
16
|
+
fontWeight = 'bold',
|
|
17
|
+
}: Props) {
|
|
18
|
+
const animatedValue = UseAnimatedCounter(Number(value), 700);
|
|
19
|
+
|
|
20
|
+
const format = (num: number) =>
|
|
21
|
+
new Intl.NumberFormat().format(num);
|
|
22
|
+
const nairaFontSize = fontSize - 5
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View style={ styles.container }>
|
|
26
|
+
<Text style={ [styles.currencyText, { color, fontWeight, fontSize: fontSize - 5 }] }>
|
|
27
|
+
₦
|
|
28
|
+
</Text>
|
|
29
|
+
|
|
30
|
+
<Text style={ { color, fontSize, fontWeight } }>
|
|
31
|
+
{ format(animatedValue) }
|
|
32
|
+
</Text>
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const styles = StyleSheet.create({
|
|
38
|
+
container: {
|
|
39
|
+
flexDirection: 'row',
|
|
40
|
+
alignItems: 'center',
|
|
41
|
+
maxWidth: '100%',
|
|
42
|
+
},
|
|
43
|
+
currencyText: {
|
|
44
|
+
// fontSize: 15,
|
|
45
|
+
marginEnd: 2,
|
|
46
|
+
// fontWeight: 'bold',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
Animated,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Pressable, BackHandler, Alert,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
10
|
+
|
|
11
|
+
export default function FloatingMenu({ isPlaying, soundCtrl, paymentCtrl, CloseCtrl }: any) {
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
13
|
+
const rotation = useRef(new Animated.Value(0)).current;
|
|
14
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
15
|
+
|
|
16
|
+
const toggleMenu = () => {
|
|
17
|
+
const toValue = open ? 0 : 1;
|
|
18
|
+
|
|
19
|
+
Animated.parallel([
|
|
20
|
+
Animated.timing(rotation, {
|
|
21
|
+
toValue,
|
|
22
|
+
duration: 300,
|
|
23
|
+
useNativeDriver: true,
|
|
24
|
+
}),
|
|
25
|
+
Animated.timing(opacity, {
|
|
26
|
+
toValue,
|
|
27
|
+
duration: 200,
|
|
28
|
+
useNativeDriver: true,
|
|
29
|
+
}),
|
|
30
|
+
]).start();
|
|
31
|
+
|
|
32
|
+
setOpen(!open);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleExit = () => {
|
|
36
|
+
Alert.alert(
|
|
37
|
+
'Exit App',
|
|
38
|
+
'Are you sure you want to exit?',
|
|
39
|
+
[
|
|
40
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
41
|
+
{ text: 'Yes', onPress: () => BackHandler.exitApp() },
|
|
42
|
+
]
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const rotateInterpolate = rotation.interpolate({
|
|
47
|
+
inputRange: [0, 1],
|
|
48
|
+
outputRange: ['0deg', '45deg'], // rotates into an "X"
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View style={ StyleSheet.absoluteFill } pointerEvents="box-none">
|
|
53
|
+
{ /* Overlay (click outside to close) */ }
|
|
54
|
+
{ open && (
|
|
55
|
+
<Pressable style={ styles.overlay } onPress={ toggleMenu } />
|
|
56
|
+
) }
|
|
57
|
+
|
|
58
|
+
{ /* Menu items */ }
|
|
59
|
+
<Animated.View style={ [styles.menu, { opacity }] }>
|
|
60
|
+
<TouchableOpacity style={ styles.menuItem } onPress={ paymentCtrl }>
|
|
61
|
+
<Ionicons name="wallet" size={ 20 } color="#fff" />
|
|
62
|
+
</TouchableOpacity>
|
|
63
|
+
|
|
64
|
+
<TouchableOpacity style={ styles.menuItem } onPress={ soundCtrl }>
|
|
65
|
+
<Ionicons name={ isPlaying ? 'megaphone' : 'megaphone-outline' } size={ 20 } color="#fff" />
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
|
|
68
|
+
<TouchableOpacity style={ styles.menuItem } onPress={ handleExit }>
|
|
69
|
+
<Ionicons name="log-out" size={ 20 } color="#fff" />
|
|
70
|
+
</TouchableOpacity>
|
|
71
|
+
</Animated.View>
|
|
72
|
+
|
|
73
|
+
{ /* Floating Button */ }
|
|
74
|
+
<TouchableOpacity style={ styles.fab } onPress={ toggleMenu }>
|
|
75
|
+
<Animated.View style={ { transform: [{ rotate: rotateInterpolate }] } }>
|
|
76
|
+
<Ionicons name="add" size={ 28 } color="#fff" />
|
|
77
|
+
</Animated.View>
|
|
78
|
+
</TouchableOpacity>
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const styles = StyleSheet.create({
|
|
84
|
+
fab: {
|
|
85
|
+
position: 'absolute',
|
|
86
|
+
right: 20,
|
|
87
|
+
bottom: 40,
|
|
88
|
+
backgroundColor: '#343e57',
|
|
89
|
+
width: 50,
|
|
90
|
+
height: 50,
|
|
91
|
+
borderRadius: 30,
|
|
92
|
+
justifyContent: 'center',
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
elevation: 5,
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
menu: {
|
|
98
|
+
position: 'absolute',
|
|
99
|
+
right: 25,
|
|
100
|
+
bottom: 110,
|
|
101
|
+
alignItems: 'center',
|
|
102
|
+
gap: 12,
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
menuItem: {
|
|
106
|
+
backgroundColor: '#343e57',
|
|
107
|
+
width: 45,
|
|
108
|
+
height: 45,
|
|
109
|
+
borderRadius: 22,
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
elevation: 4,
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
overlay: {
|
|
116
|
+
...StyleSheet.absoluteFillObject,
|
|
117
|
+
backgroundColor: 'transparent',
|
|
118
|
+
},
|
|
119
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { View, StyleSheet, Dimensions } from 'react-native';
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withRepeat,
|
|
7
|
+
withTiming,
|
|
8
|
+
Easing,
|
|
9
|
+
} from 'react-native-reanimated';
|
|
10
|
+
|
|
11
|
+
const { width } = Dimensions.get('window');
|
|
12
|
+
|
|
13
|
+
export default function ShimmerHR() {
|
|
14
|
+
const x = useSharedValue(-width);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
x.value = withRepeat(
|
|
18
|
+
withTiming(width, {
|
|
19
|
+
duration: 3000,
|
|
20
|
+
easing: Easing.linear,
|
|
21
|
+
}),
|
|
22
|
+
-1,
|
|
23
|
+
true
|
|
24
|
+
);
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const shimmerStyle = useAnimatedStyle(() => {
|
|
28
|
+
return {
|
|
29
|
+
transform: [{ translateX: x.value }],
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View style={ styles.wrapper }>
|
|
35
|
+
<View style={ styles.line } />
|
|
36
|
+
<Animated.View style={ [styles.shimmer, shimmerStyle] } />
|
|
37
|
+
{ /*<Animated.View style={ [styles.shimmer, shimmerStyle] } />*/ }
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const styles = StyleSheet.create({
|
|
43
|
+
wrapper: {
|
|
44
|
+
height: 1,
|
|
45
|
+
width: '100%',
|
|
46
|
+
overflow: 'hidden',
|
|
47
|
+
backgroundColor: 'transparent',
|
|
48
|
+
position: 'relative',
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
line: {
|
|
52
|
+
position: 'absolute',
|
|
53
|
+
height: 1,
|
|
54
|
+
width: '100%',
|
|
55
|
+
backgroundColor: 'rgba(166,166,166,0.57)', // make sure it's visible
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
shimmer: {
|
|
59
|
+
position: 'absolute',
|
|
60
|
+
height: 1,
|
|
61
|
+
width: '50%',
|
|
62
|
+
backgroundColor: 'rgba(228,228,228,0.43)',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function UseAnimatedCounter(value: number, duration = 600) {
|
|
4
|
+
const [displayValue, setDisplayValue] = useState(value);
|
|
5
|
+
const startValue = useRef(value);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const start = startValue.current;
|
|
9
|
+
const end = value;
|
|
10
|
+
const startTime = Date.now();
|
|
11
|
+
|
|
12
|
+
const animate = () => {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const elapsed = now - startTime;
|
|
15
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
16
|
+
|
|
17
|
+
const current = Math.round(start + (end - start) * progress);
|
|
18
|
+
setDisplayValue(current);
|
|
19
|
+
|
|
20
|
+
if (progress < 1) {
|
|
21
|
+
requestAnimationFrame(animate);
|
|
22
|
+
} else {
|
|
23
|
+
startValue.current = end;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
requestAnimationFrame(animate);
|
|
28
|
+
}, [value, duration]);
|
|
29
|
+
|
|
30
|
+
return displayValue;
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// components/Chain.tsx
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Svg, {Ellipse} from 'react-native-svg';
|
|
4
|
+
import {View} from 'react-native';
|
|
5
|
+
|
|
6
|
+
export default function Chain() {
|
|
7
|
+
const links = Array.from({length: 4});
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<View style={{zIndex: 0}}>
|
|
11
|
+
{links.map((_, i) => (
|
|
12
|
+
<Svg key={i} height={8} width={8}>
|
|
13
|
+
<Ellipse
|
|
14
|
+
cx="4"
|
|
15
|
+
cy="6"
|
|
16
|
+
rx="3"
|
|
17
|
+
ry="5"
|
|
18
|
+
stroke="#999"
|
|
19
|
+
strokeWidth="2"
|
|
20
|
+
fill="none"
|
|
21
|
+
/>
|
|
22
|
+
</Svg>
|
|
23
|
+
))}
|
|
24
|
+
</View>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// components/games/GameItem.tsx
|
|
2
|
+
import React, {useEffect} from 'react';
|
|
3
|
+
import Animated, {Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming} from 'react-native-reanimated';
|
|
4
|
+
import HangingSign from './HangingSign';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
title: string
|
|
8
|
+
onPress: () => void
|
|
9
|
+
delay?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function GameItem({title, onPress, delay = 0}: Props) {
|
|
13
|
+
|
|
14
|
+
const floatY = useSharedValue(0);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
floatY.value = withRepeat(
|
|
19
|
+
withTiming(-10, {
|
|
20
|
+
duration: 2500,
|
|
21
|
+
easing: Easing.inOut(Easing.ease)
|
|
22
|
+
}),
|
|
23
|
+
-1,
|
|
24
|
+
true
|
|
25
|
+
);
|
|
26
|
+
}, delay);
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
30
|
+
transform: [{translateY: floatY.value}]
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Animated.View style={[{marginBottom: 35}, animatedStyle]}>
|
|
35
|
+
<HangingSign title={title} onPress={onPress}/>
|
|
36
|
+
</Animated.View>
|
|
37
|
+
);
|
|
38
|
+
}
|