添加赔率设置功能,支持选择博彩公司并展示赔率信息;优化状态管理和国际化文本

This commit is contained in:
yuchenglong
2026-01-19 17:24:09 +08:00
parent e1320a67e4
commit 7024b03c30
7 changed files with 439 additions and 51 deletions

View File

@@ -15,24 +15,59 @@ import {
import { ThemedText } from "@/components/themed-text";
import { IconSymbol } from "@/components/ui/icon-symbol";
import { useAppState } from "@/context/AppStateContext";
import { useTheme } from "@/context/ThemeContext";
import { changeLanguage } from "@/i18n";
import { appleSignIn, fetchUserProfile, logout } from "@/lib/api";
import { storage } from "@/lib/storage";
import type { UserProfile } from "@/types/api";
const BOOKMAKERS = [
"10Bet",
"WilliamHill",
"bet365",
"Marathon",
"Unibet",
"Betfair",
"188bet",
"Pncl",
"Sbo",
];
export default function ProfileScreen() {
const { theme, toggleTheme, setTheme, isSystemTheme, useSystemTheme } =
useTheme();
const { state, updateOddsSettings } = useAppState();
const { t, i18n } = useTranslation();
const router = useRouter();
const isDark = theme === "dark";
const [appleAvailable, setAppleAvailable] = React.useState(false);
const [user, setUser] = React.useState<UserProfile | null>(null);
const [loginModalVisible, setLoginModalVisible] = React.useState(false);
const [oddsModalVisible, setOddsModalVisible] = React.useState(false);
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 nextLang = currentLanguage.startsWith("en") ? "zh" : "en";
changeLanguage(nextLang);
@@ -65,7 +100,8 @@ export default function ProfileScreen() {
platformVersion,
},
user:
credential.fullName && (credential.fullName.givenName || credential.fullName.familyName)
credential.fullName &&
(credential.fullName.givenName || credential.fullName.familyName)
? {
name: {
firstName: credential.fullName.givenName || undefined,
@@ -178,7 +214,9 @@ export default function ProfileScreen() {
<>
<View style={styles.profileHeader}>
<Image
source={{ uri: user?.avatar || "https://via.placeholder.com/100" }}
source={{
uri: user?.avatar || "https://via.placeholder.com/100",
}}
style={styles.avatar}
contentFit="cover"
/>
@@ -201,7 +239,9 @@ export default function ProfileScreen() {
]}
onPress={handleLogout}
>
<ThemedText style={{ color: "#FF3B30" }}></ThemedText>
<ThemedText style={{ color: "#FF3B30" }}>
{t("settings.logout")}
</ThemedText>
</TouchableOpacity>
</>
) : (
@@ -212,9 +252,11 @@ export default function ProfileScreen() {
>
<View style={styles.loginIcon} />
<View style={styles.loginInfo}>
<ThemedText style={styles.loginTitle}></ThemedText>
<ThemedText style={styles.loginTitle}>
{t("settings.login")}
</ThemedText>
<ThemedText style={{ color: subTextColor }}>
{t("settings.click_to_login")}
</ThemedText>
</View>
</TouchableOpacity>
@@ -310,6 +352,83 @@ export default function ProfileScreen() {
</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>
<View
@@ -367,7 +486,9 @@ export default function ProfileScreen() {
{ backgroundColor: isDark ? "#1c1c1e" : "#fff" },
]}
>
<ThemedText style={styles.modalTitle}></ThemedText>
<ThemedText style={styles.modalTitle}>
{t("settings.select_login_method")}
</ThemedText>
{appleAvailable && (
<AppleAuthentication.AppleAuthenticationButton
buttonType={
@@ -384,13 +505,76 @@ export default function ProfileScreen() {
/>
)}
<TouchableOpacity style={styles.googleButton} onPress={() => {}}>
<ThemedText>Google </ThemedText>
<ThemedText>{t("settings.google_login")}</ThemedText>
</TouchableOpacity>
<TouchableOpacity
style={styles.modalCancel}
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>
</View>
</TouchableOpacity>
@@ -502,6 +686,13 @@ const styles = StyleSheet.create({
alignItems: "center",
justifyContent: "center",
},
bookmakerItem: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 14,
borderBottomWidth: StyleSheet.hairlineWidth,
},
logoutButton: {
paddingVertical: 12,
alignItems: "center",