This commit is contained in:
xianyi
2026-01-19 17:42:55 +08:00
7 changed files with 439 additions and 51 deletions

View File

@@ -15,24 +15,59 @@ import {
import { ThemedText } from "@/components/themed-text"; import { ThemedText } from "@/components/themed-text";
import { IconSymbol } from "@/components/ui/icon-symbol"; import { IconSymbol } from "@/components/ui/icon-symbol";
import { useAppState } from "@/context/AppStateContext";
import { useTheme } from "@/context/ThemeContext"; import { useTheme } from "@/context/ThemeContext";
import { changeLanguage } from "@/i18n"; import { changeLanguage } from "@/i18n";
import { appleSignIn, fetchUserProfile, logout } from "@/lib/api"; import { appleSignIn, fetchUserProfile, logout } from "@/lib/api";
import { storage } from "@/lib/storage"; import { storage } from "@/lib/storage";
import type { UserProfile } from "@/types/api"; import type { UserProfile } from "@/types/api";
const BOOKMAKERS = [
"10Bet",
"WilliamHill",
"bet365",
"Marathon",
"Unibet",
"Betfair",
"188bet",
"Pncl",
"Sbo",
];
export default function ProfileScreen() { export default function ProfileScreen() {
const { theme, toggleTheme, setTheme, isSystemTheme, useSystemTheme } = const { theme, toggleTheme, setTheme, isSystemTheme, useSystemTheme } =
useTheme(); useTheme();
const { state, updateOddsSettings } = useAppState();
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const router = useRouter(); const router = useRouter();
const isDark = theme === "dark"; const isDark = theme === "dark";
const [appleAvailable, setAppleAvailable] = React.useState(false); const [appleAvailable, setAppleAvailable] = React.useState(false);
const [user, setUser] = React.useState<UserProfile | null>(null); const [user, setUser] = React.useState<UserProfile | null>(null);
const [loginModalVisible, setLoginModalVisible] = React.useState(false); const [loginModalVisible, setLoginModalVisible] = React.useState(false);
const [oddsModalVisible, setOddsModalVisible] = React.useState(false);
const currentLanguage = i18n.language; const currentLanguage = i18n.language;
const toggleOdds = () => {
updateOddsSettings({
...state.oddsSettings,
enabled: !state.oddsSettings.enabled,
});
};
const selectBookmaker = (name: string) => {
const current = state.oddsSettings.selectedBookmakers;
let next: string[];
if (current.includes(name)) {
next = current.filter((b) => b !== name);
} else {
next = [...current, name].slice(-2); // Keep last 2
}
updateOddsSettings({
...state.oddsSettings,
selectedBookmakers: next,
});
};
const toggleLanguage = () => { const toggleLanguage = () => {
const nextLang = currentLanguage.startsWith("en") ? "zh" : "en"; const nextLang = currentLanguage.startsWith("en") ? "zh" : "en";
changeLanguage(nextLang); changeLanguage(nextLang);
@@ -65,7 +100,8 @@ export default function ProfileScreen() {
platformVersion, platformVersion,
}, },
user: user:
credential.fullName && (credential.fullName.givenName || credential.fullName.familyName) credential.fullName &&
(credential.fullName.givenName || credential.fullName.familyName)
? { ? {
name: { name: {
firstName: credential.fullName.givenName || undefined, firstName: credential.fullName.givenName || undefined,
@@ -178,7 +214,9 @@ export default function ProfileScreen() {
<> <>
<View style={styles.profileHeader}> <View style={styles.profileHeader}>
<Image <Image
source={{ uri: user?.avatar || "https://via.placeholder.com/100" }} source={{
uri: user?.avatar || "https://via.placeholder.com/100",
}}
style={styles.avatar} style={styles.avatar}
contentFit="cover" contentFit="cover"
/> />
@@ -201,7 +239,9 @@ export default function ProfileScreen() {
]} ]}
onPress={handleLogout} onPress={handleLogout}
> >
<ThemedText style={{ color: "#FF3B30" }}></ThemedText> <ThemedText style={{ color: "#FF3B30" }}>
{t("settings.logout")}
</ThemedText>
</TouchableOpacity> </TouchableOpacity>
</> </>
) : ( ) : (
@@ -212,9 +252,11 @@ export default function ProfileScreen() {
> >
<View style={styles.loginIcon} /> <View style={styles.loginIcon} />
<View style={styles.loginInfo}> <View style={styles.loginInfo}>
<ThemedText style={styles.loginTitle}></ThemedText> <ThemedText style={styles.loginTitle}>
{t("settings.login")}
</ThemedText>
<ThemedText style={{ color: subTextColor }}> <ThemedText style={{ color: subTextColor }}>
{t("settings.click_to_login")}
</ThemedText> </ThemedText>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
@@ -310,6 +352,83 @@ export default function ProfileScreen() {
</View> </View>
</View> </View>
<ThemedText style={styles.sectionTitle}>
{t("settings.odds_title")}
</ThemedText>
<View
style={[
styles.section,
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
]}
>
<View style={styles.settingItem}>
<View style={styles.settingLabel}>
<IconSymbol
name="stats-chart"
size={20}
color={iconColor}
style={{ marginRight: 10 }}
/>
<ThemedText>{t("settings.odds_show")}</ThemedText>
</View>
<View style={styles.settingControl}>
<TouchableOpacity onPress={toggleOdds} style={styles.button}>
<ThemedText>
{state.oddsSettings.enabled
? t("settings.odds_enabled")
: t("settings.odds_disabled")}
</ThemedText>
</TouchableOpacity>
</View>
</View>
<View
style={[
styles.settingItem,
{
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: isDark ? "#38383a" : "#c6c6c8",
},
]}
>
<TouchableOpacity
style={styles.settingItemContent}
onPress={() => setOddsModalVisible(true)}
disabled={!state.oddsSettings.enabled}
>
<View style={styles.settingLabel}>
<IconSymbol
name="list"
size={20}
color={state.oddsSettings.enabled ? iconColor : subTextColor}
style={{ marginRight: 10 }}
/>
<ThemedText
style={{
color: state.oddsSettings.enabled
? textColor
: subTextColor,
}}
>
{t("settings.odds_select_company")}
</ThemedText>
</View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<ThemedText style={{ color: subTextColor, marginRight: 4 }}>
{state.oddsSettings.selectedBookmakers.join(", ") ||
t("settings.odds_unselected")}
</ThemedText>
<IconSymbol
name="chevron-forward"
size={16}
color={subTextColor}
/>
</View>
</TouchableOpacity>
</View>
</View>
{/* <ThemedText style={styles.sectionTitle}>登录</ThemedText> {/* <ThemedText style={styles.sectionTitle}>登录</ThemedText>
<View <View
@@ -367,7 +486,9 @@ export default function ProfileScreen() {
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" }, { backgroundColor: isDark ? "#1c1c1e" : "#fff" },
]} ]}
> >
<ThemedText style={styles.modalTitle}></ThemedText> <ThemedText style={styles.modalTitle}>
{t("settings.select_login_method")}
</ThemedText>
{appleAvailable && ( {appleAvailable && (
<AppleAuthentication.AppleAuthenticationButton <AppleAuthentication.AppleAuthenticationButton
buttonType={ buttonType={
@@ -384,13 +505,76 @@ export default function ProfileScreen() {
/> />
)} )}
<TouchableOpacity style={styles.googleButton} onPress={() => {}}> <TouchableOpacity style={styles.googleButton} onPress={() => {}}>
<ThemedText>Google </ThemedText> <ThemedText>{t("settings.google_login")}</ThemedText>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={styles.modalCancel} style={styles.modalCancel}
onPress={() => setLoginModalVisible(false)} onPress={() => setLoginModalVisible(false)}
> >
<ThemedText></ThemedText> <ThemedText>{t("settings.cancel")}</ThemedText>
</TouchableOpacity>
</View>
</TouchableOpacity>
</Modal>
<Modal
visible={oddsModalVisible}
transparent
animationType="slide"
onRequestClose={() => setOddsModalVisible(false)}
>
<TouchableOpacity
style={styles.modalMask}
activeOpacity={1}
onPress={() => setOddsModalVisible(false)}
>
<View
style={[
styles.modalCard,
{
backgroundColor: isDark ? "#1c1c1e" : "#fff",
maxHeight: "70%",
},
]}
>
<ThemedText style={styles.modalTitle}>
{t("settings.odds_modal_title")}
</ThemedText>
<ScrollView style={{ marginVertical: 10 }}>
{BOOKMAKERS.map((name) => {
const isSelected =
state.oddsSettings.selectedBookmakers.includes(name);
return (
<TouchableOpacity
key={name}
style={[
styles.bookmakerItem,
{ borderColor: isDark ? "#38383a" : "#eee" },
]}
onPress={() => selectBookmaker(name)}
>
<ThemedText
style={{
color: isSelected ? "#FF9500" : textColor,
fontWeight: isSelected ? "bold" : "normal",
}}
>
{name}
</ThemedText>
{isSelected && (
<IconSymbol name="checkmark" size={18} color="#FF9500" />
)}
</TouchableOpacity>
);
})}
</ScrollView>
<TouchableOpacity
style={styles.modalCancel}
onPress={() => setOddsModalVisible(false)}
>
<ThemedText type="defaultSemiBold">
{t("settings.odds_confirm")}
</ThemedText>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
@@ -502,6 +686,13 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
bookmakerItem: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 14,
borderBottomWidth: StyleSheet.hairlineWidth,
},
logoutButton: { logoutButton: {
paddingVertical: 12, paddingVertical: 12,
alignItems: "center", alignItems: "center",

View File

@@ -1,13 +1,14 @@
import { ThemedText } from "@/components/themed-text"; import { ThemedText } from "@/components/themed-text";
import { IconSymbol } from "@/components/ui/icon-symbol"; import { IconSymbol } from "@/components/ui/icon-symbol";
import { Colors } from "@/constants/theme"; import { Colors } from "@/constants/theme";
import { useAppState } from "@/context/AppStateContext";
import { useTheme } from "@/context/ThemeContext"; import { useTheme } from "@/context/ThemeContext";
import { addFavorite, removeFavorite } from "@/lib/api"; import { addFavorite, fetchOdds, removeFavorite } from "@/lib/api";
import { Match } from "@/types/api"; import { Match, OddsItem } from "@/types/api";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { LinearGradient } from "expo-linear-gradient"; import { LinearGradient } from "expo-linear-gradient";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { Pressable, StyleSheet, TouchableOpacity, View } from "react-native"; import { Pressable, StyleSheet, TouchableOpacity, View } from "react-native";
interface MatchCardProps { interface MatchCardProps {
@@ -23,12 +24,36 @@ export function MatchCard({
}: MatchCardProps) { }: MatchCardProps) {
const router = useRouter(); const router = useRouter();
const { theme } = useTheme(); const { theme } = useTheme();
const { state } = useAppState();
const [isFav, setIsFav] = useState(match.fav); const [isFav, setIsFav] = useState(match.fav);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [odds, setOdds] = useState<OddsItem[]>(match.odds || []);
// console.log("MatchCard render:", JSON.stringify(match)); // console.log("MatchCard render:", JSON.stringify(match));
const oddsSettings = state.oddsSettings;
useEffect(() => {
if (
oddsSettings.enabled &&
oddsSettings.selectedBookmakers.length > 0 &&
!match.odds
) {
fetchOdds(match.sportId || 1, parseInt(match.id))
.then((res) => {
const matchOdds = res[match.id]?.data || [];
setOdds(matchOdds);
})
.catch((err) => console.log("Fetch match card odds error:", err));
}
}, [
oddsSettings.enabled,
oddsSettings.selectedBookmakers,
match.id,
match.odds,
]);
// 当外部传入的 match.fav 改变时,更新内部状态 // 当外部传入的 match.fav 改变时,更新内部状态
React.useEffect(() => { useEffect(() => {
setIsFav(match.fav); setIsFav(match.fav);
}, [match.fav]); }, [match.fav]);
@@ -39,6 +64,8 @@ export function MatchCard({
const scoreBorder = isDark ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.12)"; const scoreBorder = isDark ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.12)";
const scoreBg = isDark ? "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.6)"; const scoreBg = isDark ? "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.6)";
const oddBadgeBg = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.03)";
const isLive = React.useMemo(() => { const isLive = React.useMemo(() => {
return !!match.isLive; return !!match.isLive;
}, [match.isLive]); }, [match.isLive]);
@@ -149,42 +176,103 @@ export function MatchCard({
</ThemedText> </ThemedText>
</View> </View>
{/* Middle: Teams */} {/* Middle: Teams & Odds */}
<View style={styles.middle}> <View style={styles.middle}>
<View style={styles.teamRow}> <View style={styles.teamContainer}>
{match.homeTeamLogo ? ( <View style={styles.teamRow}>
<Image {match.homeTeamLogo ? (
source={{ uri: match.homeTeamLogo }} <Image
style={styles.teamLogo} source={{ uri: match.homeTeamLogo }}
contentFit="contain" style={styles.teamLogo}
/> contentFit="contain"
) : null} />
<ThemedText ) : null}
type="defaultSemiBold" <ThemedText
style={styles.teamLine} type="defaultSemiBold"
numberOfLines={1} style={styles.teamLine}
ellipsizeMode="tail" numberOfLines={1}
> ellipsizeMode="tail"
{match.homeTeamName || match.home} >
</ThemedText> {match.homeTeamName || match.home}
</View> </ThemedText>
<View style={styles.teamRow}> </View>
{match.awayTeamLogo ? ( <View style={styles.teamRow}>
<Image {match.awayTeamLogo ? (
source={{ uri: match.awayTeamLogo }} <Image
style={styles.teamLogo} source={{ uri: match.awayTeamLogo }}
contentFit="contain" style={styles.teamLogo}
/> contentFit="contain"
) : null} />
<ThemedText ) : null}
type="defaultSemiBold" <ThemedText
style={styles.teamLine} type="defaultSemiBold"
numberOfLines={1} style={styles.teamLine}
ellipsizeMode="tail" numberOfLines={1}
> ellipsizeMode="tail"
{match.awayTeamName || match.away} >
</ThemedText> {match.awayTeamName || match.away}
</ThemedText>
</View>
</View> </View>
{/* Odds Section */}
{oddsSettings.enabled && odds.length > 0 && (
<View style={styles.oddsContainer}>
{oddsSettings.selectedBookmakers.map((bookmaker, idx) => {
const item = odds.find((o) => o.odd_bookmakers === bookmaker);
if (!item) return null;
// Pick 3 values to display. Using odd_1, odd_x, odd_2 for example.
// Or try to match the screenshot's 3-column style.
const val1 = item.odd_1 || item.ah0_1 || "-";
const val2 = item.odd_x || "0" || "-";
const val3 = item.odd_2 || item.ah0_2 || "-";
return (
<View key={bookmaker} style={styles.bookmakerOddsRow}>
<View
style={[styles.oddBadge, { backgroundColor: oddBadgeBg }]}
>
<ThemedText
style={[
styles.oddText,
{ color: isDark ? "#fff" : "#000" },
idx === 0 && styles.oddTextHighlight,
]}
>
{val1}
</ThemedText>
</View>
<View
style={[styles.oddBadge, { backgroundColor: oddBadgeBg }]}
>
<ThemedText
style={[
styles.oddText,
{ color: isDark ? "#fff" : "#000" },
idx === 0 && styles.oddTextHighlight,
]}
>
{val2}
</ThemedText>
</View>
<View
style={[styles.oddBadge, { backgroundColor: oddBadgeBg }]}
>
<ThemedText
style={[
styles.oddText,
{ color: isDark ? "#fff" : "#000" },
idx === 0 && styles.oddTextHighlight,
]}
>
{val3}
</ThemedText>
</View>
</View>
);
})}
</View>
)}
</View> </View>
{/* Right: Score box + favorite */} {/* Right: Score box + favorite */}
@@ -270,6 +358,14 @@ const styles = StyleSheet.create({
fontWeight: "bold", fontWeight: "bold",
}, },
middle: { middle: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: 8,
minWidth: 0,
},
teamContainer: {
flex: 1, flex: 1,
justifyContent: "center", justifyContent: "center",
gap: 8, gap: 8,
@@ -289,6 +385,31 @@ const styles = StyleSheet.create({
lineHeight: 18, lineHeight: 18,
flex: 1, flex: 1,
}, },
oddsContainer: {
gap: 8,
alignItems: "flex-end",
},
bookmakerOddsRow: {
flexDirection: "row",
gap: 4,
},
oddBadge: {
backgroundColor: "rgba(0,0,0,0.03)",
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 6,
minWidth: 40,
alignItems: "center",
},
oddText: {
fontSize: 11,
fontWeight: "600",
opacity: 0.8,
},
oddTextHighlight: {
color: "#FF9500",
opacity: 1,
},
right: { right: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",

View File

@@ -1,10 +1,18 @@
import React, { createContext, ReactNode, useContext, useState } from "react"; import { OddsSettings, storage } from "@/lib/storage";
import React, {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
interface AppState { interface AppState {
selectedSportId: number | null; selectedSportId: number | null;
selectedDate: Date; selectedDate: Date;
selectedLeagueKey: string | null; selectedLeagueKey: string | null;
timezone: string; timezone: string;
oddsSettings: OddsSettings;
} }
interface AppStateContextType { interface AppStateContextType {
@@ -13,10 +21,11 @@ interface AppStateContextType {
updateDate: (date: Date) => void; updateDate: (date: Date) => void;
updateLeagueKey: (leagueKey: string | null) => void; updateLeagueKey: (leagueKey: string | null) => void;
updateTimezone: (timezone: string) => void; updateTimezone: (timezone: string) => void;
updateOddsSettings: (settings: OddsSettings) => void;
} }
const AppStateContext = createContext<AppStateContextType | undefined>( const AppStateContext = createContext<AppStateContextType | undefined>(
undefined undefined,
); );
export function AppStateProvider({ children }: { children: ReactNode }) { export function AppStateProvider({ children }: { children: ReactNode }) {
@@ -25,8 +34,16 @@ export function AppStateProvider({ children }: { children: ReactNode }) {
selectedDate: new Date(), selectedDate: new Date(),
selectedLeagueKey: null, selectedLeagueKey: null,
timezone: "UTC", timezone: "UTC",
oddsSettings: { enabled: false, selectedBookmakers: [] },
}); });
useEffect(() => {
// Initial load of odds settings
storage.getOddsSettings().then((settings) => {
setState((prev) => ({ ...prev, oddsSettings: settings }));
});
}, []);
const updateSportId = (sportId: number | null) => { const updateSportId = (sportId: number | null) => {
setState((prev) => ({ ...prev, selectedSportId: sportId })); setState((prev) => ({ ...prev, selectedSportId: sportId }));
}; };
@@ -43,6 +60,11 @@ export function AppStateProvider({ children }: { children: ReactNode }) {
setState((prev) => ({ ...prev, timezone })); setState((prev) => ({ ...prev, timezone }));
}; };
const updateOddsSettings = (settings: OddsSettings) => {
setState((prev) => ({ ...prev, oddsSettings: settings }));
storage.setOddsSettings(settings);
};
return ( return (
<AppStateContext.Provider <AppStateContext.Provider
value={{ value={{
@@ -51,6 +73,7 @@ export function AppStateProvider({ children }: { children: ReactNode }) {
updateDate, updateDate,
updateLeagueKey, updateLeagueKey,
updateTimezone, updateTimezone,
updateOddsSettings,
}} }}
> >
{children} {children}

View File

@@ -23,7 +23,21 @@
"dark": "Dark", "dark": "Dark",
"system": "System", "system": "System",
"english": "English", "english": "English",
"chinese": "Chinese" "chinese": "Chinese",
"odds_title": "Odds Settings",
"odds_show": "Show Odds",
"odds_select_company": "Select Bookmakers (Max 2)",
"odds_enabled": "On",
"odds_disabled": "Off",
"odds_unselected": "Unselected",
"odds_modal_title": "Select Bookmakers (Max 2)",
"odds_confirm": "Confirm",
"login": "Login",
"click_to_login": "Click to login",
"logout": "Logout",
"select_login_method": "Select login method",
"cancel": "Cancel",
"google_login": "Google Login"
}, },
"home": { "home": {
"title": "ScoreNow", "title": "ScoreNow",

View File

@@ -23,7 +23,21 @@
"dark": "深色", "dark": "深色",
"system": "跟随系统", "system": "跟随系统",
"english": "英文", "english": "英文",
"chinese": "中文" "chinese": "中文",
"odds_title": "赔率设置",
"odds_show": "展示赔率",
"odds_select_company": "选择公司 (最多2个)",
"odds_enabled": "开启",
"odds_disabled": "关闭",
"odds_unselected": "未选择",
"odds_modal_title": "选择赔率公司 (最多2项)",
"odds_confirm": "确定",
"login": "登录",
"click_to_login": "点击登录",
"logout": "登出",
"select_login_method": "选择登录方式",
"cancel": "取消",
"google_login": "Google 登录"
}, },
"home": { "home": {
"title": "ScoreNow", "title": "ScoreNow",

View File

@@ -1,12 +1,18 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import type { UserProfile } from "@/types/api"; import type { UserProfile } from "@/types/api";
import AsyncStorage from "@react-native-async-storage/async-storage";
const STORAGE_KEYS = { const STORAGE_KEYS = {
ACCESS_TOKEN: "access_token", ACCESS_TOKEN: "access_token",
REFRESH_TOKEN: "refresh_token", REFRESH_TOKEN: "refresh_token",
USER: "user", USER: "user",
ODDS_SETTINGS: "odds_settings",
}; };
export interface OddsSettings {
enabled: boolean;
selectedBookmakers: string[];
}
export const storage = { export const storage = {
async setAccessToken(token: string): Promise<void> { async setAccessToken(token: string): Promise<void> {
await AsyncStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token); await AsyncStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token);
@@ -38,11 +44,29 @@ export const storage = {
} }
}, },
async setOddsSettings(settings: OddsSettings): Promise<void> {
await AsyncStorage.setItem(
STORAGE_KEYS.ODDS_SETTINGS,
JSON.stringify(settings),
);
},
async getOddsSettings(): Promise<OddsSettings> {
const settingsStr = await AsyncStorage.getItem(STORAGE_KEYS.ODDS_SETTINGS);
if (!settingsStr) return { enabled: false, selectedBookmakers: [] };
try {
return JSON.parse(settingsStr) as OddsSettings;
} catch {
return { enabled: false, selectedBookmakers: [] };
}
},
async clear(): Promise<void> { async clear(): Promise<void> {
await AsyncStorage.multiRemove([ await AsyncStorage.multiRemove([
STORAGE_KEYS.ACCESS_TOKEN, STORAGE_KEYS.ACCESS_TOKEN,
STORAGE_KEYS.REFRESH_TOKEN, STORAGE_KEYS.REFRESH_TOKEN,
STORAGE_KEYS.USER, STORAGE_KEYS.USER,
STORAGE_KEYS.ODDS_SETTINGS,
]); ]);
}, },
}; };

View File

@@ -36,6 +36,7 @@ export interface Match {
leagueId?: number; leagueId?: number;
isLive?: boolean; isLive?: boolean;
meta?: string; meta?: string;
odds?: OddsItem[];
} }
export interface LiveScoreMatch { export interface LiveScoreMatch {