first commit
This commit is contained in:
parent
3058e27685
commit
0503230fd4
48
App.tsx
48
App.tsx
|
|
@ -1,45 +1,13 @@
|
||||||
/**
|
import React from 'react';
|
||||||
* Sample React Native App
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
* https://github.com/facebook/react-native
|
import BottomTabNavigator from './src/navigation/BottomTabNavigator';
|
||||||
*
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NewAppScreen } from '@react-native/new-app-screen';
|
|
||||||
import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native';
|
|
||||||
import {
|
|
||||||
SafeAreaProvider,
|
|
||||||
useSafeAreaInsets,
|
|
||||||
} from 'react-native-safe-area-context';
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const isDarkMode = useColorScheme() === 'dark';
|
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<NavigationContainer>
|
||||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
<BottomTabNavigator />
|
||||||
<AppContent />
|
</NavigationContainer>
|
||||||
</SafeAreaProvider>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function AppContent() {
|
|
||||||
const safeAreaInsets = useSafeAreaInsets();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<NewAppScreen
|
|
||||||
templateFileName="App.tsx"
|
|
||||||
safeAreaInsets={safeAreaInsets}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
2297
ios/Podfile.lock
Normal file
2297
ios/Podfile.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -9,6 +9,7 @@
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
0C80B921A6F3F58F76C31292 /* libPods-fortune.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-fortune.a */; };
|
0C80B921A6F3F58F76C31292 /* libPods-fortune.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-fortune.a */; };
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||||
|
4F08206F5132F769BA13651A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
|
||||||
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
|
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
|
||||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
@ -159,6 +160,7 @@
|
||||||
files = (
|
files = (
|
||||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
|
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||||
|
4F08206F5132F769BA13651A /* PrivacyInfo.xcprivacy in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -370,6 +372,10 @@
|
||||||
);
|
);
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
OTHER_CFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||||
|
);
|
||||||
OTHER_CPLUSPLUSFLAGS = (
|
OTHER_CPLUSPLUSFLAGS = (
|
||||||
"$(OTHER_CFLAGS)",
|
"$(OTHER_CFLAGS)",
|
||||||
"-DFOLLY_NO_CONFIG",
|
"-DFOLLY_NO_CONFIG",
|
||||||
|
|
@ -377,8 +383,13 @@
|
||||||
"-DFOLLY_USE_LIBCPP=1",
|
"-DFOLLY_USE_LIBCPP=1",
|
||||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||||
|
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||||
);
|
);
|
||||||
|
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||||
|
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||||
|
USE_HERMES = true;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -435,6 +446,10 @@
|
||||||
"\"$(inherited)\"",
|
"\"$(inherited)\"",
|
||||||
);
|
);
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
OTHER_CFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||||
|
);
|
||||||
OTHER_CPLUSPLUSFLAGS = (
|
OTHER_CPLUSPLUSFLAGS = (
|
||||||
"$(OTHER_CFLAGS)",
|
"$(OTHER_CFLAGS)",
|
||||||
"-DFOLLY_NO_CONFIG",
|
"-DFOLLY_NO_CONFIG",
|
||||||
|
|
@ -442,8 +457,12 @@
|
||||||
"-DFOLLY_USE_LIBCPP=1",
|
"-DFOLLY_USE_LIBCPP=1",
|
||||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||||
|
"-DRCT_REMOVE_LEGACY_ARCH=1",
|
||||||
);
|
);
|
||||||
|
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
|
||||||
|
USE_HERMES = true;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
|
||||||
10
ios/fortune.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
ios/fortune.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:fortune.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>NSAllowsLocalNetworking</key>
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
|
@ -36,6 +35,8 @@
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
|
<key>RCTNewArchEnabled</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
|
|
||||||
12342
package-lock.json
generated
Normal file
12342
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
|
@ -10,10 +10,18 @@
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native/new-app-screen": "0.84.0",
|
||||||
|
"@react-navigation/bottom-tabs": "^7.14.0",
|
||||||
|
"@react-navigation/native": "^7.1.28",
|
||||||
|
"@react-navigation/native-stack": "^7.13.0",
|
||||||
|
"lucide-react-native": "^0.574.0",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-native": "0.84.0",
|
"react-native": "0.84.0",
|
||||||
"@react-native/new-app-screen": "0.84.0",
|
"react-native-linear-gradient": "^2.8.3",
|
||||||
"react-native-safe-area-context": "^5.5.2"
|
"react-native-safe-area-context": "^5.6.2",
|
||||||
|
"react-native-screens": "^4.23.0",
|
||||||
|
"react-native-svg": "^15.15.3",
|
||||||
|
"react-native-vector-icons": "^10.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
|
|
||||||
80
src/components/CategoryPills.tsx
Normal file
80
src/components/CategoryPills.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
} from 'react-native';
|
||||||
|
import { COLORS, SPACING, SIZES } from '../theme/theme';
|
||||||
|
import { CATEGORY_FILTERS } from '../constants/data';
|
||||||
|
|
||||||
|
const CategoryPills: React.FC = () => {
|
||||||
|
const [activeId, setActiveId] = React.useState('1');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
>
|
||||||
|
{CATEGORY_FILTERS.map(filter => {
|
||||||
|
const isActive = filter.id === activeId;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={filter.id}
|
||||||
|
style={[styles.pill, isActive && styles.pillActive]}
|
||||||
|
onPress={() => setActiveId(filter.id)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={styles.pillIcon}>{filter.icon}</Text>
|
||||||
|
{filter.label ? (
|
||||||
|
<Text
|
||||||
|
style={[styles.pillLabel, isActive && styles.pillLabelActive]}
|
||||||
|
>
|
||||||
|
{filter.label}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingVertical: SPACING.xs,
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
gap: SPACING.sm,
|
||||||
|
},
|
||||||
|
pill: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.sm,
|
||||||
|
borderRadius: SIZES.pillRadius,
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
pillActive: {
|
||||||
|
backgroundColor: COLORS.surfaceLight,
|
||||||
|
},
|
||||||
|
pillIcon: {
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
pillLabel: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
pillLabelActive: {
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CategoryPills;
|
||||||
79
src/components/LeagueCarousel.tsx
Normal file
79
src/components/LeagueCarousel.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
Dimensions,
|
||||||
|
} from 'react-native';
|
||||||
|
import { COLORS, SPACING, SIZES } from '../theme/theme';
|
||||||
|
import { LEAGUES, League } from '../constants/data';
|
||||||
|
|
||||||
|
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
||||||
|
const CARD_WIDTH = (SCREEN_WIDTH - SPACING.md * 2 - SPACING.sm * 3) / 4;
|
||||||
|
|
||||||
|
const LeagueCarousel: React.FC = () => {
|
||||||
|
const renderLeague = (league: League) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={league.id}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
style={[styles.card, { backgroundColor: league.color }]}
|
||||||
|
>
|
||||||
|
<View style={styles.cardContent}>
|
||||||
|
<Text style={styles.leagueIcon}>{league.icon}</Text>
|
||||||
|
<Text style={styles.leagueName} numberOfLines={2}>
|
||||||
|
{league.name}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
snapToInterval={CARD_WIDTH + SPACING.sm}
|
||||||
|
decelerationRate="fast"
|
||||||
|
>
|
||||||
|
{LEAGUES.map(renderLeague)}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingVertical: SPACING.md,
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
gap: SPACING.sm,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
width: CARD_WIDTH,
|
||||||
|
height: CARD_WIDTH * 1.2,
|
||||||
|
borderRadius: SIZES.radius,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
cardContent: {
|
||||||
|
flex: 1,
|
||||||
|
padding: SPACING.sm,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
leagueIcon: {
|
||||||
|
fontSize: 28,
|
||||||
|
},
|
||||||
|
leagueName: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
lineHeight: 16,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LeagueCarousel;
|
||||||
233
src/components/LiveMatchCard.tsx
Normal file
233
src/components/LiveMatchCard.tsx
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
|
import { COLORS, SPACING, SIZES } from '../theme/theme';
|
||||||
|
import { Match } from '../constants/data';
|
||||||
|
|
||||||
|
interface LiveMatchCardProps {
|
||||||
|
match: Match;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveMatchCard: React.FC<LiveMatchCardProps> = ({ match }) => {
|
||||||
|
const [selectedOdd, setSelectedOdd] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
const getStatusColor = () => {
|
||||||
|
if (match.status.includes('Overtime')) {
|
||||||
|
return '#FF4D4D';
|
||||||
|
}
|
||||||
|
return '#FF4D4D';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.card}>
|
||||||
|
{/* Status Bar */}
|
||||||
|
<View style={styles.statusRow}>
|
||||||
|
<View style={styles.statusLeft}>
|
||||||
|
<View
|
||||||
|
style={[styles.statusDot, { backgroundColor: getStatusColor() }]}
|
||||||
|
/>
|
||||||
|
<Text style={styles.statusText}>{match.status}</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity activeOpacity={0.7}>
|
||||||
|
<Text style={styles.starIcon}>☆</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Teams */}
|
||||||
|
<View style={styles.teamsContainer}>
|
||||||
|
{/* Home Team */}
|
||||||
|
<View style={styles.teamRow}>
|
||||||
|
<View style={styles.teamInfo}>
|
||||||
|
<Text style={styles.teamIcon}>{match.homeTeam.icon}</Text>
|
||||||
|
<Text style={styles.teamName}>{match.homeTeam.name}</Text>
|
||||||
|
</View>
|
||||||
|
{match.homeTeam.score !== undefined && (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.score,
|
||||||
|
match.homeTeam.score > (match.awayTeam.score ?? 0)
|
||||||
|
? styles.scoreHighlight
|
||||||
|
: null,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{match.homeTeam.score}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Away Team */}
|
||||||
|
<View style={styles.teamRow}>
|
||||||
|
<View style={styles.teamInfo}>
|
||||||
|
<Text style={styles.teamIcon}>{match.awayTeam.icon}</Text>
|
||||||
|
<Text style={styles.teamName}>{match.awayTeam.name}</Text>
|
||||||
|
</View>
|
||||||
|
{match.awayTeam.score !== undefined && (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.score,
|
||||||
|
match.awayTeam.score > (match.homeTeam.score ?? 0)
|
||||||
|
? styles.scoreHighlight
|
||||||
|
: null,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{match.awayTeam.score}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Market Label */}
|
||||||
|
<View style={styles.marketLabelRow}>
|
||||||
|
<Text style={styles.marketLabel}>Result</Text>
|
||||||
|
<View style={styles.dotsRow}>
|
||||||
|
{Array.from({ length: match.marketCount }).map((_, i) => (
|
||||||
|
<View key={i} style={[styles.dot, i === 0 && styles.dotActive]} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Odds Buttons */}
|
||||||
|
<View style={styles.oddsRow}>
|
||||||
|
{match.markets.map(market => {
|
||||||
|
const isSelected = selectedOdd === market.label;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={market.label}
|
||||||
|
style={[styles.oddButton, isSelected && styles.oddButtonActive]}
|
||||||
|
onPress={() => setSelectedOdd(isSelected ? null : market.label)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={styles.oddLabel}>{market.label}</Text>
|
||||||
|
<Text
|
||||||
|
style={[styles.oddValue, isSelected && styles.oddValueActive]}
|
||||||
|
>
|
||||||
|
{market.value.toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
card: {
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
borderRadius: SIZES.radius,
|
||||||
|
marginHorizontal: SPACING.md,
|
||||||
|
marginBottom: SPACING.md,
|
||||||
|
padding: SPACING.md,
|
||||||
|
},
|
||||||
|
statusRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
statusLeft: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
statusDot: {
|
||||||
|
width: 4,
|
||||||
|
height: 16,
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
starIcon: {
|
||||||
|
fontSize: 20,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
teamsContainer: {
|
||||||
|
gap: 6,
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
teamRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
teamInfo: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10,
|
||||||
|
},
|
||||||
|
teamIcon: {
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
teamName: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
score: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
scoreHighlight: {
|
||||||
|
color: '#00D084',
|
||||||
|
},
|
||||||
|
marketLabelRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
marketLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
dotsRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 4,
|
||||||
|
},
|
||||||
|
dot: {
|
||||||
|
width: 5,
|
||||||
|
height: 5,
|
||||||
|
borderRadius: 3,
|
||||||
|
backgroundColor: COLORS.border,
|
||||||
|
},
|
||||||
|
dotActive: {
|
||||||
|
backgroundColor: COLORS.text,
|
||||||
|
},
|
||||||
|
oddsRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: SPACING.sm,
|
||||||
|
},
|
||||||
|
oddButton: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: COLORS.surfaceLight,
|
||||||
|
borderRadius: 8,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
},
|
||||||
|
oddButtonActive: {
|
||||||
|
backgroundColor: '#2A4A3A',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#00D084',
|
||||||
|
},
|
||||||
|
oddLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '500',
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
oddValue: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#00D084',
|
||||||
|
},
|
||||||
|
oddValueActive: {
|
||||||
|
color: '#00FF9D',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LiveMatchCard;
|
||||||
118
src/components/LiveMatchesSection.tsx
Normal file
118
src/components/LiveMatchesSection.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
|
import { COLORS, SPACING } from '../theme/theme';
|
||||||
|
import { Match } from '../constants/data';
|
||||||
|
import LiveMatchCard from './LiveMatchCard';
|
||||||
|
|
||||||
|
interface LiveMatchesSectionProps {
|
||||||
|
matches: Match[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveMatchesSection: React.FC<LiveMatchesSectionProps> = ({ matches }) => {
|
||||||
|
// Group matches by league
|
||||||
|
const grouped = matches.reduce((acc, match) => {
|
||||||
|
if (!acc[match.league]) {
|
||||||
|
acc[match.league] = {
|
||||||
|
icon: match.leagueIcon,
|
||||||
|
matches: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
acc[match.league].matches.push(match);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, { icon: string; matches: Match[] }>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{/* Section Header */}
|
||||||
|
<TouchableOpacity style={styles.sectionHeader} activeOpacity={0.7}>
|
||||||
|
<View style={styles.headerLeft}>
|
||||||
|
<View style={styles.liveIndicator}>
|
||||||
|
<View style={styles.liveDot} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.headerTitle}>Live Matches</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.headerArrow}>›</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Grouped Matches */}
|
||||||
|
{Object.entries(grouped).map(([league, data]) => (
|
||||||
|
<View key={league}>
|
||||||
|
{/* League Header */}
|
||||||
|
<View style={styles.leagueHeader}>
|
||||||
|
<Text style={styles.leagueIconText}>⚽</Text>
|
||||||
|
<Text style={styles.leagueFlag}>{data.icon}</Text>
|
||||||
|
<Text style={styles.leagueName}>{league}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Match Cards */}
|
||||||
|
{data.matches.map(match => (
|
||||||
|
<LiveMatchCard key={match.id} match={match} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingBottom: SPACING.xl,
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.md,
|
||||||
|
},
|
||||||
|
headerLeft: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10,
|
||||||
|
},
|
||||||
|
liveIndicator: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: 'rgba(255, 77, 77, 0.2)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
liveDot: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
borderRadius: 5,
|
||||||
|
backgroundColor: '#FF4D4D',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
headerArrow: {
|
||||||
|
fontSize: 24,
|
||||||
|
color: COLORS.text,
|
||||||
|
fontWeight: '300',
|
||||||
|
},
|
||||||
|
leagueHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.sm,
|
||||||
|
},
|
||||||
|
leagueIconText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
leagueFlag: {
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
leagueName: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LiveMatchesSection;
|
||||||
99
src/components/SportsHeader.tsx
Normal file
99
src/components/SportsHeader.tsx
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
} from 'react-native';
|
||||||
|
import { COLORS, SPACING } from '../theme/theme';
|
||||||
|
import { SPORTS, Sport } from '../constants/data';
|
||||||
|
|
||||||
|
const SportsHeader: React.FC = () => {
|
||||||
|
const [activeId, setActiveId] = React.useState('1');
|
||||||
|
|
||||||
|
const renderSport = (sport: Sport) => {
|
||||||
|
const isActive = sport.id === activeId;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={sport.id}
|
||||||
|
style={[styles.sportItem, isActive && styles.sportItemActive]}
|
||||||
|
onPress={() => setActiveId(sport.id)}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={styles.iconRow}>
|
||||||
|
<Text style={styles.sportIcon}>{sport.icon}</Text>
|
||||||
|
<Text
|
||||||
|
style={[styles.sportCount, isActive && styles.sportCountActive]}
|
||||||
|
>
|
||||||
|
{sport.count}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Text
|
||||||
|
style={[styles.sportName, isActive && styles.sportNameActive]}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{sport.name}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
>
|
||||||
|
{SPORTS.map(renderSport)}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingVertical: SPACING.sm,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
borderBottomColor: COLORS.border,
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
paddingHorizontal: SPACING.sm,
|
||||||
|
gap: SPACING.lg,
|
||||||
|
},
|
||||||
|
sportItem: {
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: SPACING.xs,
|
||||||
|
paddingHorizontal: SPACING.xs,
|
||||||
|
},
|
||||||
|
sportItemActive: {},
|
||||||
|
iconRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
sportIcon: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
sportCount: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
sportCountActive: {
|
||||||
|
color: COLORS.primary,
|
||||||
|
},
|
||||||
|
sportName: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
sportNameActive: {
|
||||||
|
color: COLORS.text,
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SportsHeader;
|
||||||
157
src/constants/data.ts
Normal file
157
src/constants/data.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Mock data for the sportsbook app
|
||||||
|
|
||||||
|
export interface Sport {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface League {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
color: string;
|
||||||
|
secondaryColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Team {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
score?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OddsMarket {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Match {
|
||||||
|
id: string;
|
||||||
|
league: string;
|
||||||
|
leagueIcon: string;
|
||||||
|
status: string;
|
||||||
|
homeTeam: Team;
|
||||||
|
awayTeam: Team;
|
||||||
|
markets: OddsMarket[];
|
||||||
|
marketCount: number;
|
||||||
|
isFavorite: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SPORTS: Sport[] = [
|
||||||
|
{ id: '1', name: 'Football', icon: '⚽', count: 1538 },
|
||||||
|
{ id: '2', name: 'Basketball', icon: '🏀', count: 280 },
|
||||||
|
{ id: '3', name: 'Tennis', icon: '🎾', count: 261 },
|
||||||
|
{ id: '4', name: 'Esports', icon: '🎮', count: 174 },
|
||||||
|
{ id: '5', name: 'Handball', icon: '🤾', count: 109 },
|
||||||
|
{ id: '6', name: 'Volleyball', icon: '🏐', count: 134 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LEAGUES: League[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'UEFA\nChampions...',
|
||||||
|
icon: '🏆',
|
||||||
|
color: '#1A3A7A',
|
||||||
|
secondaryColor: '#0D1F4A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'England.\nPremier Le...',
|
||||||
|
icon: '🦁',
|
||||||
|
color: '#2D6B3F',
|
||||||
|
secondaryColor: '#1A4028',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: 'Spain.\nLaLiga',
|
||||||
|
icon: '🏟️',
|
||||||
|
color: '#C9372C',
|
||||||
|
secondaryColor: '#8B1A12',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: 'Italy. Serie A',
|
||||||
|
icon: '🇮🇹',
|
||||||
|
color: '#2E468C',
|
||||||
|
secondaryColor: '#1A2B5C',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
name: 'ATP 5.\nDoha...',
|
||||||
|
icon: '🎾',
|
||||||
|
color: '#3A3A3A',
|
||||||
|
secondaryColor: '#1A1A1A',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CATEGORY_FILTERS = [
|
||||||
|
{ id: '1', label: 'Highlights', icon: '🔥', active: true },
|
||||||
|
{ id: '2', label: '', icon: '⚙️', active: false },
|
||||||
|
{ id: '3', label: '', icon: '🏀', active: false },
|
||||||
|
{ id: '4', label: '', icon: '⚽', active: false },
|
||||||
|
{ id: '5', label: '', icon: '🏇', active: false },
|
||||||
|
{ id: '6', label: '', icon: '🏐', active: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MATCHES: Match[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
league: 'Spain. LaLiga',
|
||||||
|
leagueIcon: '🇪🇸',
|
||||||
|
status: "1st half 31'",
|
||||||
|
homeTeam: { name: 'Levante', icon: '⚽', score: 0 },
|
||||||
|
awayTeam: { name: 'Villarreal', icon: '⚽', score: 0 },
|
||||||
|
markets: [
|
||||||
|
{ label: 'W1', value: 4.05 },
|
||||||
|
{ label: 'X', value: 3 },
|
||||||
|
{ label: 'W2', value: 2.14 },
|
||||||
|
],
|
||||||
|
marketCount: 5,
|
||||||
|
isFavorite: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
league: 'Germany. BBL',
|
||||||
|
leagueIcon: '🇩🇪',
|
||||||
|
status: "Overtime 1'",
|
||||||
|
homeTeam: { name: 'Rostock Seawolves', icon: '🏀', score: 81 },
|
||||||
|
awayTeam: { name: 'Bamberg', icon: '🏀', score: 85 },
|
||||||
|
markets: [
|
||||||
|
{ label: 'W1', value: 4.4 },
|
||||||
|
{ label: 'W2', value: 1.17 },
|
||||||
|
],
|
||||||
|
marketCount: 2,
|
||||||
|
isFavorite: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
league: 'England. Premier League',
|
||||||
|
leagueIcon: '🏴',
|
||||||
|
status: "2nd half 67'",
|
||||||
|
homeTeam: { name: 'Arsenal', icon: '⚽', score: 2 },
|
||||||
|
awayTeam: { name: 'Chelsea', icon: '⚽', score: 1 },
|
||||||
|
markets: [
|
||||||
|
{ label: 'W1', value: 1.35 },
|
||||||
|
{ label: 'X', value: 5.2 },
|
||||||
|
{ label: 'W2', value: 8.5 },
|
||||||
|
],
|
||||||
|
marketCount: 6,
|
||||||
|
isFavorite: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
league: 'Italy. Serie A',
|
||||||
|
leagueIcon: '🇮🇹',
|
||||||
|
status: "1st half 15'",
|
||||||
|
homeTeam: { name: 'AC Milan', icon: '⚽', score: 0 },
|
||||||
|
awayTeam: { name: 'Juventus', icon: '⚽', score: 1 },
|
||||||
|
markets: [
|
||||||
|
{ label: 'W1', value: 2.8 },
|
||||||
|
{ label: 'X', value: 3.1 },
|
||||||
|
{ label: 'W2', value: 2.65 },
|
||||||
|
],
|
||||||
|
marketCount: 5,
|
||||||
|
isFavorite: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
134
src/navigation/BottomTabNavigator.tsx
Normal file
134
src/navigation/BottomTabNavigator.tsx
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
|
||||||
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
|
import { COLORS } from '../theme/theme';
|
||||||
|
|
||||||
|
import HomeScreen from '../screens/HomeScreen';
|
||||||
|
import LiveScreen from '../screens/LiveScreen';
|
||||||
|
import PrematchScreen from '../screens/PrematchScreen';
|
||||||
|
import MyBetsScreen from '../screens/MyBetsScreen';
|
||||||
|
import ProfileScreen from '../screens/ProfileScreen';
|
||||||
|
|
||||||
|
const Tab = createBottomTabNavigator();
|
||||||
|
|
||||||
|
interface TabItemProps {
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
focused: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabItem: React.FC<TabItemProps> = ({ icon, label, focused }) => (
|
||||||
|
<View style={styles.tabItem}>
|
||||||
|
<Text style={[styles.tabIcon, focused && styles.tabIconActive]}>
|
||||||
|
{icon}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.tabLabel, focused && styles.tabLabelActive]}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const BottomTabNavigator: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Tab.Navigator
|
||||||
|
screenOptions={{
|
||||||
|
headerShown: false,
|
||||||
|
tabBarStyle: styles.tabBar,
|
||||||
|
tabBarShowLabel: false,
|
||||||
|
tabBarActiveTintColor: '#FFD700',
|
||||||
|
tabBarInactiveTintColor: COLORS.textSecondary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tab.Screen
|
||||||
|
name="Menu"
|
||||||
|
component={HomeScreen}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ focused }) => (
|
||||||
|
<TabItem icon="☰" label="Menu" focused={focused} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tab.Screen
|
||||||
|
name="Main"
|
||||||
|
component={HomeScreen}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ focused }) => (
|
||||||
|
<TabItem icon="⚡" label="Main" focused={focused} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tab.Screen
|
||||||
|
name="Live"
|
||||||
|
component={LiveScreen}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ focused }) => (
|
||||||
|
<TabItem icon="📺" label="Live" focused={focused} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tab.Screen
|
||||||
|
name="Prematch"
|
||||||
|
component={PrematchScreen}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ focused }) => (
|
||||||
|
<TabItem icon="⚽" label="Prematch" focused={focused} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tab.Screen
|
||||||
|
name="MyBets"
|
||||||
|
component={MyBetsScreen}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ focused }) => (
|
||||||
|
<TabItem icon="🎟️" label="My Bets" focused={focused} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tab.Screen
|
||||||
|
name="Profile"
|
||||||
|
component={ProfileScreen}
|
||||||
|
options={{
|
||||||
|
tabBarIcon: ({ focused }) => (
|
||||||
|
<TabItem icon="👤" label="Log in" focused={focused} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tab.Navigator>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
tabBar: {
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
borderTopWidth: 0.5,
|
||||||
|
borderTopColor: COLORS.border,
|
||||||
|
height: 70,
|
||||||
|
paddingTop: 8,
|
||||||
|
paddingBottom: 12,
|
||||||
|
elevation: 0,
|
||||||
|
shadowOpacity: 0,
|
||||||
|
},
|
||||||
|
tabItem: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 2,
|
||||||
|
},
|
||||||
|
tabIcon: {
|
||||||
|
fontSize: 20,
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
tabIconActive: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
tabLabel: {
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '500',
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
},
|
||||||
|
tabLabelActive: {
|
||||||
|
color: '#FFD700',
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default BottomTabNavigator;
|
||||||
76
src/screens/HomeScreen.tsx
Normal file
76
src/screens/HomeScreen.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
ScrollView,
|
||||||
|
StyleSheet,
|
||||||
|
StatusBar,
|
||||||
|
Text,
|
||||||
|
TouchableOpacity,
|
||||||
|
} from 'react-native';
|
||||||
|
import { COLORS, SPACING } from '../theme/theme';
|
||||||
|
import SportsHeader from '../components/SportsHeader';
|
||||||
|
import LeagueCarousel from '../components/LeagueCarousel';
|
||||||
|
import CategoryPills from '../components/CategoryPills';
|
||||||
|
import LiveMatchesSection from '../components/LiveMatchesSection';
|
||||||
|
import { MATCHES } from '../constants/data';
|
||||||
|
|
||||||
|
const HomeScreen: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.headerTitle}>Sportsbook</Text>
|
||||||
|
<TouchableOpacity activeOpacity={0.7}>
|
||||||
|
<Text style={styles.searchIcon}>🔍</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
>
|
||||||
|
{/* Sports Categories */}
|
||||||
|
<SportsHeader />
|
||||||
|
|
||||||
|
{/* League Carousel */}
|
||||||
|
<LeagueCarousel />
|
||||||
|
|
||||||
|
{/* Live Matches Header + Category Filters */}
|
||||||
|
<CategoryPills />
|
||||||
|
|
||||||
|
{/* Live Matches Section */}
|
||||||
|
<LiveMatchesSection matches={MATCHES} />
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.md,
|
||||||
|
paddingTop: SPACING.xl,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
scrollContent: {
|
||||||
|
paddingBottom: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default HomeScreen;
|
||||||
74
src/screens/LiveScreen.tsx
Normal file
74
src/screens/LiveScreen.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, ScrollView, StatusBar } from 'react-native';
|
||||||
|
import { COLORS, SPACING } from '../theme/theme';
|
||||||
|
|
||||||
|
const LiveScreen: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.headerTitle}>Live</Text>
|
||||||
|
</View>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.content}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Text style={styles.emptyIcon}>🔴</Text>
|
||||||
|
<Text style={styles.emptyTitle}>Live Events</Text>
|
||||||
|
<Text style={styles.emptySubtitle}>
|
||||||
|
All live matches and events will appear here in real-time.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.md,
|
||||||
|
paddingTop: SPACING.xl,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
borderBottomColor: COLORS.border,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingBottom: 100,
|
||||||
|
},
|
||||||
|
emptyState: {
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: SPACING.xl,
|
||||||
|
paddingTop: 80,
|
||||||
|
},
|
||||||
|
emptyIcon: {
|
||||||
|
fontSize: 48,
|
||||||
|
marginBottom: SPACING.md,
|
||||||
|
},
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
emptySubtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LiveScreen;
|
||||||
75
src/screens/MyBetsScreen.tsx
Normal file
75
src/screens/MyBetsScreen.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, ScrollView, StatusBar } from 'react-native';
|
||||||
|
import { COLORS, SPACING } from '../theme/theme';
|
||||||
|
|
||||||
|
const MyBetsScreen: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.headerTitle}>My Bets</Text>
|
||||||
|
</View>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.content}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Text style={styles.emptyIcon}>🎟️</Text>
|
||||||
|
<Text style={styles.emptyTitle}>No Active Bets</Text>
|
||||||
|
<Text style={styles.emptySubtitle}>
|
||||||
|
Your active and settled bets will appear here. Start betting on live
|
||||||
|
or upcoming matches!
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.md,
|
||||||
|
paddingTop: SPACING.xl,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
borderBottomColor: COLORS.border,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingBottom: 100,
|
||||||
|
},
|
||||||
|
emptyState: {
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: SPACING.xl,
|
||||||
|
paddingTop: 80,
|
||||||
|
},
|
||||||
|
emptyIcon: {
|
||||||
|
fontSize: 48,
|
||||||
|
marginBottom: SPACING.md,
|
||||||
|
},
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
emptySubtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default MyBetsScreen;
|
||||||
74
src/screens/PrematchScreen.tsx
Normal file
74
src/screens/PrematchScreen.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, ScrollView, StatusBar } from 'react-native';
|
||||||
|
import { COLORS, SPACING } from '../theme/theme';
|
||||||
|
|
||||||
|
const PrematchScreen: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.headerTitle}>Prematch</Text>
|
||||||
|
</View>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.content}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Text style={styles.emptyIcon}>📅</Text>
|
||||||
|
<Text style={styles.emptyTitle}>Upcoming Events</Text>
|
||||||
|
<Text style={styles.emptySubtitle}>
|
||||||
|
Browse upcoming matches and place your bets before they start.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.md,
|
||||||
|
paddingTop: SPACING.xl,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
borderBottomColor: COLORS.border,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingBottom: 100,
|
||||||
|
},
|
||||||
|
emptyState: {
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: SPACING.xl,
|
||||||
|
paddingTop: 80,
|
||||||
|
},
|
||||||
|
emptyIcon: {
|
||||||
|
fontSize: 48,
|
||||||
|
marginBottom: SPACING.md,
|
||||||
|
},
|
||||||
|
emptyTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
emptySubtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PrematchScreen;
|
||||||
114
src/screens/ProfileScreen.tsx
Normal file
114
src/screens/ProfileScreen.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
StatusBar,
|
||||||
|
TouchableOpacity,
|
||||||
|
} from 'react-native';
|
||||||
|
import { COLORS, SPACING, SIZES } from '../theme/theme';
|
||||||
|
|
||||||
|
const ProfileScreen: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<StatusBar barStyle="light-content" backgroundColor={COLORS.background} />
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.headerTitle}>Account</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<View style={styles.avatarContainer}>
|
||||||
|
<Text style={styles.avatarText}>👤</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.loginTitle}>Log in to your account</Text>
|
||||||
|
<Text style={styles.loginSubtitle}>
|
||||||
|
Access your bets, wallet, and personalized settings.
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity style={styles.loginButton} activeOpacity={0.8}>
|
||||||
|
<Text style={styles.loginButtonText}>Log In</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.registerButton} activeOpacity={0.8}>
|
||||||
|
<Text style={styles.registerButtonText}>Register</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: COLORS.background,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: SPACING.md,
|
||||||
|
paddingVertical: SPACING.md,
|
||||||
|
paddingTop: SPACING.xl,
|
||||||
|
borderBottomWidth: 0.5,
|
||||||
|
borderBottomColor: COLORS.border,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: SPACING.xl,
|
||||||
|
paddingBottom: 100,
|
||||||
|
},
|
||||||
|
avatarContainer: {
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 40,
|
||||||
|
backgroundColor: COLORS.surface,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: SPACING.lg,
|
||||||
|
},
|
||||||
|
avatarText: {
|
||||||
|
fontSize: 36,
|
||||||
|
},
|
||||||
|
loginTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: COLORS.text,
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
loginSubtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: COLORS.textSecondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: 20,
|
||||||
|
marginBottom: SPACING.lg,
|
||||||
|
},
|
||||||
|
loginButton: {
|
||||||
|
backgroundColor: '#FFD700',
|
||||||
|
borderRadius: SIZES.radius,
|
||||||
|
paddingVertical: 14,
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
marginBottom: SPACING.sm,
|
||||||
|
},
|
||||||
|
loginButtonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#000',
|
||||||
|
},
|
||||||
|
registerButton: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: COLORS.border,
|
||||||
|
borderRadius: SIZES.radius,
|
||||||
|
paddingVertical: 14,
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
registerButtonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: COLORS.text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ProfileScreen;
|
||||||
27
src/theme/theme.ts
Normal file
27
src/theme/theme.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
export const COLORS = {
|
||||||
|
background: '#0D0D0D',
|
||||||
|
surface: '#1A1A1A',
|
||||||
|
surfaceLight: '#262626',
|
||||||
|
primary: '#FFD700', // Gold/Yellow for odds/highlights
|
||||||
|
text: '#FFFFFF',
|
||||||
|
textSecondary: '#A0A0A0',
|
||||||
|
accentGreen: '#00D084', // Premier League
|
||||||
|
accentBlue: '#007AFF', // UEFA
|
||||||
|
accentRed: '#FF4D4D', // LaLiga
|
||||||
|
accentIndigo: '#4C6EF5', // Serie A
|
||||||
|
accentOrange: '#FF8C00', // Bundesliga
|
||||||
|
border: '#333333',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SPACING = {
|
||||||
|
xs: 4,
|
||||||
|
sm: 8,
|
||||||
|
md: 16,
|
||||||
|
lg: 24,
|
||||||
|
xl: 32,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SIZES = {
|
||||||
|
radius: 12,
|
||||||
|
pillRadius: 20,
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user