diff --git a/app/profile.tsx b/app/profile.tsx
index b0b5a28..8b31215 100644
--- a/app/profile.tsx
+++ b/app/profile.tsx
@@ -37,7 +37,7 @@ const BOOKMAKERS = [
export default function ProfileScreen() {
const { theme, toggleTheme, setTheme, isSystemTheme, useSystemTheme } =
useTheme();
- const { state, updateOddsSettings } = useAppState();
+ const { state, updateOddsSettings, updateCardsSettings } = useAppState();
const { t, i18n } = useTranslation();
const router = useRouter();
const isDark = theme === "dark";
@@ -55,6 +55,13 @@ export default function ProfileScreen() {
});
};
+ const toggleCards = () => {
+ updateCardsSettings({
+ ...state.cardsSettings,
+ enabled: !state.cardsSettings.enabled,
+ });
+ };
+
const selectBookmaker = (name: string) => {
const current = state.oddsSettings.selectedBookmakers;
let next: string[];
@@ -429,6 +436,38 @@ export default function ProfileScreen() {
+
+ {t("settings.cards_title")}
+
+
+
+
+
+
+ {t("settings.cards_show")}
+
+
+
+
+ {state.cardsSettings.enabled
+ ? t("settings.cards_enabled")
+ : t("settings.cards_disabled")}
+
+
+
+
+
+
{/* 登录
(match.odds || []);
+ const [liveDetail, setLiveDetail] = useState(null);
// console.log("MatchCard render:", JSON.stringify(match));
const oddsSettings = state.oddsSettings;
+ const cardsSettings = state.cardsSettings;
useEffect(() => {
if (
@@ -52,6 +59,20 @@ export function MatchCard({
match.odds,
]);
+ // Fetch live score detail for cards info
+ useEffect(() => {
+ if (cardsSettings.enabled && isLive && match.leagueKey) {
+ fetchLiveScore(match.sportId || 1, Number(match.leagueKey))
+ .then((matches) => {
+ const detail = matches.find((m) => String(m.event_key) === match.id);
+ if (detail) {
+ setLiveDetail(detail);
+ }
+ })
+ .catch((err) => console.log("Fetch live detail for cards error:", err));
+ }
+ }, [cardsSettings.enabled, match.id, match.leagueKey, match.sportId]);
+
// 当外部传入的 match.fav 改变时,更新内部状态
useEffect(() => {
setIsFav(match.fav);
@@ -90,10 +111,53 @@ export function MatchCard({
if (m) return { home: m[1], away: m[2], hasScore: true };
if (s && s !== "-" && s !== "0 - 0")
return { home: s, away: "", hasScore: true };
- if (s === "0 - 0") return { home: "0", away: "0", hasScore: true };
+ if (s === "0 - 0" || s === "0-0")
+ return { home: "0", away: "0", hasScore: true };
return { home: "", away: "", hasScore: false };
}, [match.scoreText]);
+ const cardsCount = React.useMemo(() => {
+ if (!liveDetail?.cards || !cardsSettings.enabled) {
+ return { homeYellow: 0, homeRed: 0, awayYellow: 0, awayRed: 0 };
+ }
+
+ let homeYellow = 0,
+ homeRed = 0,
+ awayYellow = 0,
+ awayRed = 0;
+
+ liveDetail.cards.forEach((card) => {
+ const cardType = (card.card || "").toLowerCase();
+ const isYellow = cardType.includes("yellow");
+ const isRed = cardType.includes("red");
+ if (!isYellow && !isRed) return;
+
+ const info = (card.info || "").toLowerCase();
+ const sideFromInfo = info.includes("home")
+ ? "home"
+ : info.includes("away")
+ ? "away"
+ : null;
+ const sideFromFault = card.home_fault
+ ? "home"
+ : card.away_fault
+ ? "away"
+ : null;
+ const side = sideFromInfo || sideFromFault;
+ if (!side) return;
+
+ if (side === "home") {
+ if (isYellow) homeYellow++;
+ if (isRed) homeRed++;
+ } else {
+ if (isYellow) awayYellow++;
+ if (isRed) awayRed++;
+ }
+ });
+
+ return { homeYellow, homeRed, awayYellow, awayRed };
+ }, [liveDetail, cardsSettings.enabled]);
+
const handlePress = () => {
if (onPress) {
onPress(match);
@@ -191,6 +255,30 @@ export function MatchCard({
);
};
+ const renderCardsInline = (yellow: number, red: number) => {
+ if (!cardsSettings.enabled || !liveDetail) return null;
+ if (yellow <= 0 && red <= 0) return null;
+
+ return (
+
+ {yellow > 0 && (
+
+
+ {yellow}
+
+
+ )}
+ {red > 0 && (
+
+ {red}
+
+ )}
+
+ );
+ };
+
return (
{match.homeTeamName || match.home}
+ {renderCardsInline(cardsCount.homeYellow, cardsCount.homeRed)}
{renderOddsRow(oddsSettings.selectedBookmakers[0], true)}
@@ -265,6 +354,7 @@ export function MatchCard({
>
{match.awayTeamName || match.away}
+ {renderCardsInline(cardsCount.awayYellow, cardsCount.awayRed)}
{renderOddsRow(oddsSettings.selectedBookmakers[1], false)}
@@ -368,7 +458,7 @@ const styles = StyleSheet.create({
flexDirection: "row",
alignItems: "center",
flex: 1,
- marginRight: 6,
+ minWidth: 0,
},
teamLogo: {
width: 22,
@@ -379,6 +469,7 @@ const styles = StyleSheet.create({
fontWeight: "600",
marginLeft: 6,
flex: 1,
+ minWidth: 0,
},
bookmakerOddsRow: {
flexDirection: "row",
@@ -427,4 +518,34 @@ const styles = StyleSheet.create({
width: 36,
height: 54,
},
+ cardsInline: {
+ flexDirection: "row",
+ alignItems: "center",
+ gap: 4,
+ marginLeft: 6,
+ flexShrink: 0,
+ },
+ cardBadge: {
+ minWidth: 16,
+ height: 16,
+ borderRadius: 3,
+ alignItems: "center",
+ justifyContent: "center",
+ paddingHorizontal: 3,
+ },
+ cardBadgeYellow: {
+ backgroundColor: "#FFC400",
+ },
+ cardBadgeRed: {
+ backgroundColor: "#FF3B30",
+ },
+ cardBadgeText: {
+ fontSize: 10,
+ fontWeight: "900",
+ lineHeight: 12,
+ color: "#fff",
+ },
+ cardBadgeTextDark: {
+ color: "#000",
+ },
});
diff --git a/context/AppStateContext.tsx b/context/AppStateContext.tsx
index 097b06f..08d30e4 100644
--- a/context/AppStateContext.tsx
+++ b/context/AppStateContext.tsx
@@ -1,4 +1,4 @@
-import { OddsSettings, storage } from "@/lib/storage";
+import { CardsSettings, OddsSettings, storage } from "@/lib/storage";
import React, {
createContext,
ReactNode,
@@ -13,6 +13,7 @@ interface AppState {
selectedLeagueKey: string | null;
timezone: string;
oddsSettings: OddsSettings;
+ cardsSettings: CardsSettings;
}
interface AppStateContextType {
@@ -22,6 +23,7 @@ interface AppStateContextType {
updateLeagueKey: (leagueKey: string | null) => void;
updateTimezone: (timezone: string) => void;
updateOddsSettings: (settings: OddsSettings) => void;
+ updateCardsSettings: (settings: CardsSettings) => void;
}
const AppStateContext = createContext(
@@ -35,13 +37,17 @@ export function AppStateProvider({ children }: { children: ReactNode }) {
selectedLeagueKey: null,
timezone: "UTC",
oddsSettings: { enabled: false, selectedBookmakers: [] },
+ cardsSettings: { enabled: false },
});
useEffect(() => {
- // Initial load of odds settings
+ // Initial load of odds settings and cards settings
storage.getOddsSettings().then((settings) => {
setState((prev) => ({ ...prev, oddsSettings: settings }));
});
+ storage.getCardsSettings().then((settings) => {
+ setState((prev) => ({ ...prev, cardsSettings: settings }));
+ });
}, []);
const updateSportId = (sportId: number | null) => {
@@ -65,6 +71,11 @@ export function AppStateProvider({ children }: { children: ReactNode }) {
storage.setOddsSettings(settings);
};
+ const updateCardsSettings = (settings: CardsSettings) => {
+ setState((prev) => ({ ...prev, cardsSettings: settings }));
+ storage.setCardsSettings(settings);
+ };
+
return (
{children}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 486c7b1..8f6d80e 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -32,6 +32,10 @@
"odds_unselected": "Unselected",
"odds_modal_title": "Select Bookmakers (Max 2)",
"odds_confirm": "Confirm",
+ "cards_title": "Cards Settings",
+ "cards_show": "Show Cards",
+ "cards_enabled": "On",
+ "cards_disabled": "Off",
"login": "Login",
"click_to_login": "Click to login",
"logout": "Logout",
diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json
index d55c902..87b72ff 100644
--- a/i18n/locales/zh.json
+++ b/i18n/locales/zh.json
@@ -32,6 +32,10 @@
"odds_unselected": "未选择",
"odds_modal_title": "选择赔率公司 (最多2项)",
"odds_confirm": "确定",
+ "cards_title": "卡牌设置",
+ "cards_show": "显示红黄牌",
+ "cards_enabled": "开启",
+ "cards_disabled": "关闭",
"login": "登录",
"click_to_login": "点击登录",
"logout": "登出",
diff --git a/lib/api.ts b/lib/api.ts
index 0f630b4..e3e47d2 100644
--- a/lib/api.ts
+++ b/lib/api.ts
@@ -41,12 +41,12 @@ apiClient.interceptors.request.use(async (config) => {
});
const refreshTokenApi = async (
- request: RefreshTokenRequest
+ request: RefreshTokenRequest,
): Promise => {
try {
const response = await apiClient.post>(
API_ENDPOINTS.REFRESH_TOKEN,
- request
+ request,
);
if (response.data.code === 0) {
@@ -81,13 +81,13 @@ apiClient.interceptors.response.use(
}
}
return Promise.reject(error);
- }
+ },
);
export const fetchSports = async (): Promise => {
try {
const response = await apiClient.get>>(
- API_ENDPOINTS.SPORTS
+ API_ENDPOINTS.SPORTS,
);
if (response.data.code === 0) {
return response.data.data.list;
@@ -103,7 +103,7 @@ export const fetchSports = async (): Promise => {
export const fetchCountries = async (): Promise => {
try {
const response = await apiClient.get>>(
- API_ENDPOINTS.COUNTRIES
+ API_ENDPOINTS.COUNTRIES,
);
if (response.data.code === 0) {
return response.data.data.list;
@@ -127,7 +127,7 @@ export const fetchLeagues = async (params: {
try {
const response = await apiClient.get>>(
API_ENDPOINTS.LEAGUES,
- { params }
+ { params },
);
if (response.data.code === 0) {
return response.data.data;
@@ -179,7 +179,7 @@ export const fetchTodayMatches = async (options: {
const response = await apiClient.get>>(
API_ENDPOINTS.MATCHES_TODAY,
- { params }
+ { params },
);
if (response.data.code === 0) {
return response.data.data;
@@ -192,11 +192,11 @@ export const fetchTodayMatches = async (options: {
};
export const fetchMatchDetail = async (
- id: string
+ id: string,
): Promise => {
try {
const response = await apiClient.get>(
- API_ENDPOINTS.MATCH_DETAIL(id)
+ API_ENDPOINTS.MATCH_DETAIL(id),
);
if (response.data.code === 0) {
return response.data.data;
@@ -211,7 +211,7 @@ export const fetchMatchDetail = async (
export const fetchLiveScore = async (
sportId: number,
leagueId?: number,
- timezone?: string
+ timezone?: string,
): Promise => {
// console.log("Fetching live scores with params:", {
// sportId,
@@ -220,9 +220,9 @@ export const fetchLiveScore = async (
// });
try {
const params: { sport_id: number; league_id?: number; timezone?: string } =
- {
- sport_id: sportId,
- };
+ {
+ sport_id: sportId,
+ };
if (leagueId) {
params.league_id = leagueId;
@@ -234,10 +234,11 @@ export const fetchLiveScore = async (
const response = await apiClient.get>(
API_ENDPOINTS.LIVESCORE,
- { params }
+ { params },
);
if (response.data.code === 0) {
+ // console.log("Live score data:", JSON.stringify(response.data.data));
return response.data.data;
}
@@ -251,7 +252,7 @@ export const fetchLiveScore = async (
export const fetchUpcomingMatches = async (
sportId: number,
leagueKey: string,
- limit: number = 50
+ limit: number = 50,
): Promise => {
try {
const response = await apiClient.get<
@@ -278,7 +279,7 @@ export const fetchUpcomingMatches = async (
// 获取实时赔率(足球/网球使用 LiveOdds,篮球/板球使用 Odds)
export const fetchOdds = async (
sportId: number,
- matchId: number
+ matchId: number,
): Promise => {
try {
const response = await apiClient.get>(
@@ -288,7 +289,7 @@ export const fetchOdds = async (
sport_id: sportId,
match_id: matchId,
},
- }
+ },
);
if (response.data.code === 0) {
@@ -305,7 +306,7 @@ export const fetchOdds = async (
// 搜索联赛、球队或球员
export const fetchSearch = async (
query: string,
- sportId?: number
+ sportId?: number,
): Promise => {
try {
const params: { q: string; sportId?: number } = { q: query };
@@ -315,7 +316,7 @@ export const fetchSearch = async (
const response = await apiClient.get>(
API_ENDPOINTS.SEARCH,
- { params }
+ { params },
);
if (response.data.code === 0) {
@@ -338,7 +339,7 @@ export const fetchH2H = async (
firstPlayerId?: number;
secondPlayerId?: number;
timezone?: string;
- }
+ },
): Promise => {
try {
const params: {
@@ -370,7 +371,7 @@ export const fetchH2H = async (
const response = await apiClient.get>(
API_ENDPOINTS.H2H,
- { params }
+ { params },
);
if (response.data.code === 0) {
@@ -385,12 +386,12 @@ export const fetchH2H = async (
};
export const appleSignIn = async (
- request: AppleSignInRequest
+ request: AppleSignInRequest,
): Promise => {
try {
const response = await apiClient.post>(
API_ENDPOINTS.APPLE_SIGNIN,
- request
+ request,
);
if (response.data.code === 0) {
@@ -407,7 +408,7 @@ export const appleSignIn = async (
export const logout = async (): Promise => {
try {
const response = await apiClient.post>(
- API_ENDPOINTS.LOGOUT
+ API_ENDPOINTS.LOGOUT,
);
if (response.data.code === 0) {
@@ -426,7 +427,7 @@ export const refreshToken = refreshTokenApi;
export const fetchUserProfile = async (): Promise => {
try {
const response = await apiClient.get>(
- API_ENDPOINTS.USER_PROFILE
+ API_ENDPOINTS.USER_PROFILE,
);
if (response.data.code === 0) {
@@ -450,7 +451,7 @@ export const addFavorite = async (request: FavoriteRequest): Promise => {
// console.log("Adding favorite with request:", request);
const response = await apiClient.post>(
API_ENDPOINTS.FAVORITES,
- request
+ request,
);
if (response.data.code === 0) {
return response.data.data;
@@ -475,7 +476,7 @@ export const removeFavorite = async (request: {
console.log("Removing favorite with request:", request);
const response = await apiClient.delete>(
API_ENDPOINTS.FAVORITES,
- { data: request }
+ { data: request },
);
if (response.data.code === 0) {
return response.data.data;
@@ -489,14 +490,14 @@ export const removeFavorite = async (request: {
export const checkFavorite = async (
type: string,
- typeId: string
+ typeId: string,
): Promise => {
try {
const response = await apiClient.get>(
API_ENDPOINTS.CHECK_FAVORITE,
{
params: { type, typeId },
- }
+ },
);
if (response.data.code === 0) {
return response.data.data;
@@ -511,14 +512,14 @@ export const checkFavorite = async (
export const fetchFavorites = async (
type: string,
page: number = 1,
- pageSize: number = 20
+ pageSize: number = 20,
): Promise => {
try {
const response = await apiClient.get>(
API_ENDPOINTS.FAVORITES,
{
params: { type, page, pageSize },
- }
+ },
);
if (response.data.code === 0) {
console.log("Fetched favorites:", JSON.stringify(response.data.data));
diff --git a/lib/storage.ts b/lib/storage.ts
index ae6ce72..5dda9f2 100644
--- a/lib/storage.ts
+++ b/lib/storage.ts
@@ -6,6 +6,7 @@ const STORAGE_KEYS = {
REFRESH_TOKEN: "refresh_token",
USER: "user",
ODDS_SETTINGS: "odds_settings",
+ CARDS_SETTINGS: "cards_settings",
};
export interface OddsSettings {
@@ -13,6 +14,10 @@ export interface OddsSettings {
selectedBookmakers: string[];
}
+export interface CardsSettings {
+ enabled: boolean;
+}
+
export const storage = {
async setAccessToken(token: string): Promise {
await AsyncStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token);
@@ -61,12 +66,30 @@ export const storage = {
}
},
+ async setCardsSettings(settings: CardsSettings): Promise {
+ await AsyncStorage.setItem(
+ STORAGE_KEYS.CARDS_SETTINGS,
+ JSON.stringify(settings),
+ );
+ },
+
+ async getCardsSettings(): Promise {
+ const settingsStr = await AsyncStorage.getItem(STORAGE_KEYS.CARDS_SETTINGS);
+ if (!settingsStr) return { enabled: false };
+ try {
+ return JSON.parse(settingsStr) as CardsSettings;
+ } catch {
+ return { enabled: false };
+ }
+ },
+
async clear(): Promise {
await AsyncStorage.multiRemove([
STORAGE_KEYS.ACCESS_TOKEN,
STORAGE_KEYS.REFRESH_TOKEN,
STORAGE_KEYS.USER,
STORAGE_KEYS.ODDS_SETTINGS,
+ STORAGE_KEYS.CARDS_SETTINGS,
]);
},
};