添加收藏

This commit is contained in:
yuchenglong
2026-01-16 14:41:15 +08:00
parent 4bccf39a8b
commit d20080eaf3
11 changed files with 656 additions and 75 deletions

View File

@@ -9,7 +9,12 @@ import { IconSymbol } from "@/components/ui/icon-symbol";
import { Colors } from "@/constants/theme";
import { useAppState } from "@/context/AppStateContext";
import { useTheme } from "@/context/ThemeContext";
import { fetchLeagues, fetchSports, fetchTodayMatches } from "@/lib/api";
import {
checkFavorite,
fetchLeagues,
fetchSports,
fetchTodayMatches,
} from "@/lib/api";
import { League, Match, Sport } from "@/types/api";
import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -310,7 +315,26 @@ export default function HomeScreen() {
selectedDate,
deviceTimeZone
);
setMatches(list);
// 直接传递 match.id 查询是否收藏,并更新列表状态
const listWithFavStatus = await Promise.all(
list.map(async (m) => {
try {
const favRes = await checkFavorite("match", m.id);
return { ...m, fav: favRes.isFavorite };
} catch (error) {
console.error(`Check favorite failed for match ${m.id}:`, error);
return m;
}
})
);
// 将收藏的比赛置顶
const sortedList = [...listWithFavStatus].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
setMatches(sortedList);
} catch (e) {
console.error(e);
} finally {
@@ -318,6 +342,18 @@ export default function HomeScreen() {
}
};
const handleFavoriteToggle = (matchId: string, isFav: boolean) => {
setMatches((prev) => {
const updated = prev.map((m) =>
m.id === matchId ? { ...m, fav: isFav } : m
);
return [...updated].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
});
};
const currentSport = sports.find((s) => s.id === selectedSportId);
const filteredMatches = useMemo(
@@ -457,7 +493,9 @@ export default function HomeScreen() {
<FlatList
data={filteredMatches}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <MatchCard match={item} />}
renderItem={({ item }) => (
<MatchCard match={item} onFavoriteToggle={handleFavoriteToggle} />
)}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<View style={styles.center}>

View File

@@ -9,7 +9,12 @@ import { IconSymbol } from "@/components/ui/icon-symbol";
import { Colors } from "@/constants/theme";
import { useAppState } from "@/context/AppStateContext";
import { useTheme } from "@/context/ThemeContext";
import { fetchLeagues, fetchSports, fetchTodayMatches } from "@/lib/api";
import {
checkFavorite,
fetchLeagues,
fetchSports,
fetchTodayMatches,
} from "@/lib/api";
import { League, Match, Sport } from "@/types/api";
import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -310,7 +315,27 @@ export default function HomeScreen() {
selectedDate,
deviceTimeZone
);
setMatches(list);
// 直接传递 match.id 查询是否收藏,并更新列表状态
const listWithFavStatus = await Promise.all(
list.map(async (m) => {
try {
// 查询比赛是否已被收藏
const favRes = await checkFavorite("match", m.id);
return { ...m, fav: favRes.isFavorite };
} catch (error) {
console.error(`Check favorite failed for match ${m.id}:`, error);
return m;
}
})
);
// 将收藏的比赛置顶
const sortedList = [...listWithFavStatus].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
setMatches(sortedList);
} catch (e) {
console.error(e);
} finally {
@@ -318,6 +343,18 @@ export default function HomeScreen() {
}
};
const handleFavoriteToggle = (matchId: string, isFav: boolean) => {
setMatches((prev) => {
const updated = prev.map((m) =>
m.id === matchId ? { ...m, fav: isFav } : m
);
return [...updated].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
});
};
const currentSport = sports.find((s) => s.id === selectedSportId);
// 获取当前运动的国际化名称
@@ -452,7 +489,9 @@ export default function HomeScreen() {
<FlatList
data={matches}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <MatchCard match={item} />}
renderItem={({ item }) => (
<MatchCard match={item} onFavoriteToggle={handleFavoriteToggle} />
)}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<View style={styles.center}>

View File

@@ -5,7 +5,7 @@ import { ThemedView } from "@/components/themed-view";
import { Colors } from "@/constants/theme";
import { useAppState } from "@/context/AppStateContext";
import { useTheme } from "@/context/ThemeContext";
import { fetchLiveScore } from "@/lib/api";
import { checkFavorite, fetchLiveScore } from "@/lib/api";
import { LiveScoreMatch, Match } from "@/types/api";
import { useRouter } from "expo-router";
import React, { useEffect, useState } from "react";
@@ -66,7 +66,25 @@ export default function LiveScreen() {
isLive: true,
}));
setMatches(converted);
// 直接传递 match.id 查询是否收藏,并更新列表状态
const listWithFavStatus = await Promise.all(
converted.map(async (m) => {
try {
const favRes = await checkFavorite("match", m.id);
return { ...m, fav: favRes.isFavorite };
} catch (error) {
console.error(`Check favorite failed for match ${m.id}:`, error);
return m;
}
})
);
// 将收藏的比赛置顶
const sortedList = [...listWithFavStatus].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
setMatches(sortedList);
} catch (error) {
console.error("Load live matches error:", error);
setMatches([]);
@@ -75,6 +93,18 @@ export default function LiveScreen() {
}
};
const handleFavoriteToggle = (matchId: string, isFav: boolean) => {
setMatches((prev) => {
const updated = prev.map((m) =>
m.id === matchId ? { ...m, fav: isFav } : m
);
return [...updated].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
});
};
return (
<ThemedView style={styles.container}>
<HomeHeader />
@@ -91,6 +121,7 @@ export default function LiveScreen() {
renderItem={({ item }) => (
<MatchCard
match={item}
onFavoriteToggle={handleFavoriteToggle}
onPress={(m) => {
router.push({
pathname: "/live-detail/[id]",

View File

@@ -7,7 +7,12 @@ import { IconSymbol } from "@/components/ui/icon-symbol";
import { UpcomingMatchCard } from "@/components/upcoming-match-card";
import { Colors } from "@/constants/theme";
import { useTheme } from "@/context/ThemeContext";
import { fetchLeagues, fetchSports, fetchUpcomingMatches } from "@/lib/api";
import {
checkFavorite,
fetchLeagues,
fetchSports,
fetchUpcomingMatches,
} from "@/lib/api";
import { League, Sport, UpcomingMatch } from "@/types/api";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -66,29 +71,93 @@ export default function HomeScreen() {
const apiList = await fetchSports();
// 创建8个运动的完整列表
const defaultSports: Sport[] = [
{ id: 1, name: "football", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 2, name: "basketball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 3, name: "tennis", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 4, name: "cricket", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 5, name: "baseball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 6, name: "badminton", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 7, name: "snooker", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 8, name: "volleyball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{
id: 1,
name: "football",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 2,
name: "basketball",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 3,
name: "tennis",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 4,
name: "cricket",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 5,
name: "baseball",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 6,
name: "badminton",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 7,
name: "snooker",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 8,
name: "volleyball",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
];
// 合并API返回的运动和默认列表
const sportsMap = new Map<number, Sport>();
apiList.forEach((sport) => {
sportsMap.set(sport.id, sport);
});
// 补充默认运动到8个
defaultSports.forEach((sport) => {
if (!sportsMap.has(sport.id)) {
sportsMap.set(sport.id, sport);
}
});
const allSports = Array.from(sportsMap.values())
.sort((a, b) => a.id - b.id)
.slice(0, 8);
@@ -98,14 +167,78 @@ export default function HomeScreen() {
console.error(e);
// API失败时使用默认8个运动
const defaultSports: Sport[] = [
{ id: 1, name: "football", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 2, name: "basketball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 3, name: "tennis", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 4, name: "cricket", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 5, name: "baseball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 6, name: "badminton", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 7, name: "snooker", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{ id: 8, name: "volleyball", description: "", icon: "", isActive: true, updatedAt: "", createdAt: "" },
{
id: 1,
name: "football",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 2,
name: "basketball",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 3,
name: "tennis",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 4,
name: "cricket",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 5,
name: "baseball",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 6,
name: "badminton",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 7,
name: "snooker",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
{
id: 8,
name: "volleyball",
description: "",
icon: "",
isActive: true,
updatedAt: "",
createdAt: "",
},
];
setSports(defaultSports);
setSelectedSportId(1);
@@ -129,12 +262,34 @@ export default function HomeScreen() {
const loadMatches = async () => {
if (selectedSportId === null) return;
setLoading(true);
try {
// 使用 fetchUpcomingMatches默认 leagueKey 为空字符串
const list = await fetchUpcomingMatches(selectedSportId, selectedLeagueKey || "");
setMatches(list);
const list = await fetchUpcomingMatches(
selectedSportId,
selectedLeagueKey || ""
);
// 直接传递 match.id 查询是否收藏,并更新列表状态
const listWithFavStatus = await Promise.all(
list.map(async (m) => {
try {
const favRes = await checkFavorite("match", m.id.toString());
return { ...m, fav: favRes.isFavorite };
} catch (error) {
console.error(`Check favorite failed for match ${m.id}:`, error);
return m;
}
})
);
// 将收藏的比赛置顶
const sortedList = [...listWithFavStatus].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
setMatches(sortedList);
} catch (e) {
console.error(e);
} finally {
@@ -142,8 +297,20 @@ export default function HomeScreen() {
}
};
const handleFavoriteToggle = (matchId: string, isFav: boolean) => {
setMatches((prev) => {
const updated = prev.map((m) =>
m.id.toString() === matchId ? { ...m, fav: isFav } : m
);
return [...updated].sort((a, b) => {
if (a.fav === b.fav) return 0;
return a.fav ? -1 : 1;
});
});
};
const currentSport = sports.find((s) => s.id === selectedSportId);
// 获取当前运动的国际化名称
const getSportName = (sport: Sport | undefined): string => {
if (!sport) return t("home.select_sport");
@@ -174,9 +341,7 @@ export default function HomeScreen() {
disabled
>
<IconSymbol name="trophy-outline" size={18} color={iconColor} />
<ThemedText style={styles.filterText}>
{t("home.league")}
</ThemedText>
<ThemedText style={styles.filterText}>{t("home.league")}</ThemedText>
</TouchableOpacity>
{/* Sport Selector */}
@@ -211,8 +376,9 @@ export default function HomeScreen() {
>
<IconSymbol name="trophy-outline" size={18} color={iconColor} />
<ThemedText style={styles.filterText} numberOfLines={1}>
{selectedLeagueKey
? leagues.find(l => l.key === selectedLeagueKey)?.name || t("home.select_league")
{selectedLeagueKey
? leagues.find((l) => l.key === selectedLeagueKey)?.name ||
t("home.select_league")
: t("home.all_leagues")}
</ThemedText>
</TouchableOpacity>
@@ -234,7 +400,12 @@ export default function HomeScreen() {
<FlatList
data={matches}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => <UpcomingMatchCard match={item} />}
renderItem={({ item }) => (
<UpcomingMatchCard
match={item}
onFavoriteToggle={handleFavoriteToggle}
/>
)}
contentContainerStyle={styles.listContent}
ListEmptyComponent={
<View style={styles.center}>