305 lines
8.1 KiB
TypeScript
305 lines
8.1 KiB
TypeScript
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||
import { useVipService } from '@/hooks/useVipService';
|
||
import {
|
||
resetToDefault,
|
||
selectTabBarConfigs,
|
||
toggleTabEnabled,
|
||
type TabConfig,
|
||
} from '@/store/tabBarConfigSlice';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
import { LinearGradient } from 'expo-linear-gradient';
|
||
import { useRouter } from 'expo-router';
|
||
import React, { useCallback, useEffect, useState } from 'react';
|
||
import {
|
||
Alert,
|
||
ScrollView,
|
||
StyleSheet,
|
||
Switch,
|
||
Text,
|
||
TouchableOpacity,
|
||
View
|
||
} from 'react-native';
|
||
|
||
import { MembershipModal } from '@/components/model/MembershipModal';
|
||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||
import { palette } from '@/constants/Colors';
|
||
import { useI18n } from '@/hooks/useI18n';
|
||
|
||
export default function TabBarConfigScreen() {
|
||
const { t } = useI18n();
|
||
const router = useRouter();
|
||
const dispatch = useAppDispatch();
|
||
const safeAreaTop = useSafeAreaTop(60);
|
||
const configs = useAppSelector(selectTabBarConfigs);
|
||
const { isVip } = useVipService();
|
||
const [showMembershipModal, setShowMembershipModal] = useState(false);
|
||
|
||
// 处理开关切换
|
||
const handleToggle = useCallback(
|
||
(tabId: string) => {
|
||
// 直接检查用户是否是 VIP(底部栏配置不是权益类功能,而是基础功能)
|
||
if (isVip) {
|
||
// VIP 用户可以正常切换
|
||
dispatch(toggleTabEnabled(tabId));
|
||
} else {
|
||
// 非 VIP 用户显示购买弹窗
|
||
setShowMembershipModal(true);
|
||
}
|
||
},
|
||
[dispatch, isVip]
|
||
);
|
||
|
||
// 页面加载时检查 VIP 状态
|
||
useEffect(() => {
|
||
if (!isVip) {
|
||
// 非 VIP 用户进入页面时立即显示购买弹窗
|
||
setShowMembershipModal(true);
|
||
}
|
||
}, [isVip]);
|
||
|
||
// 购买成功回调
|
||
const handlePurchaseSuccess = useCallback(() => {
|
||
// 购买成功后可以执行一些操作,比如刷新用户信息
|
||
console.log('会员购买成功');
|
||
}, []);
|
||
|
||
// 恢复默认设置
|
||
const handleReset = useCallback(() => {
|
||
Alert.alert(
|
||
t('personal.tabBarConfig.resetConfirm.title'),
|
||
t('personal.tabBarConfig.resetConfirm.message'),
|
||
[
|
||
{
|
||
text: t('personal.tabBarConfig.resetConfirm.cancel'),
|
||
style: 'cancel',
|
||
},
|
||
{
|
||
text: t('personal.tabBarConfig.resetConfirm.confirm'),
|
||
style: 'destructive',
|
||
onPress: () => {
|
||
dispatch(resetToDefault());
|
||
Alert.alert('', t('personal.tabBarConfig.resetSuccess'));
|
||
},
|
||
},
|
||
]
|
||
);
|
||
}, [dispatch, t]);
|
||
|
||
// 渲染单个 Tab 行
|
||
const renderTabRow = useCallback(
|
||
(item: TabConfig, index: number, total: number) => {
|
||
return (
|
||
<View key={item.id}>
|
||
<View style={styles.tabItem}>
|
||
{/* Tab 图标和名称 */}
|
||
<View style={styles.tabInfo}>
|
||
<View style={styles.iconContainer}>
|
||
<IconSymbol name={item.icon as any} size={24} color="#9370DB" />
|
||
</View>
|
||
<View style={styles.tabTextContainer}>
|
||
<Text style={styles.tabTitle}>{t(item.titleKey)}</Text>
|
||
{!item.canBeDisabled && (
|
||
<Text style={styles.tabSubtitle}>
|
||
{t('personal.tabBarConfig.cannotDisable')}
|
||
</Text>
|
||
)}
|
||
{item.canBeDisabled && !isVip && (
|
||
<Text style={styles.vipSubtitle}>
|
||
{t('personal.tabBarConfig.vipOnly')}
|
||
</Text>
|
||
)}
|
||
</View>
|
||
</View>
|
||
|
||
{/* 开关 */}
|
||
<Switch
|
||
value={item.enabled}
|
||
onValueChange={() => handleToggle(item.id)}
|
||
disabled={!item.canBeDisabled || !isVip}
|
||
trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
|
||
thumbColor="#FFFFFF"
|
||
style={styles.switch}
|
||
/>
|
||
</View>
|
||
|
||
{/* 分割线 - 最后一项不显示 */}
|
||
{index < total - 1 && (
|
||
<View style={styles.separatorContainer}>
|
||
<View style={styles.separator} />
|
||
</View>
|
||
)}
|
||
</View>
|
||
);
|
||
},
|
||
[handleToggle, t]
|
||
);
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<LinearGradient
|
||
colors={[palette.purple[100], '#F5F5F5']}
|
||
start={{ x: 1, y: 0 }}
|
||
end={{ x: 0.3, y: 0.4 }}
|
||
style={styles.gradientBackground}
|
||
/>
|
||
|
||
{/* 顶部导航栏 */}
|
||
<HeaderBar
|
||
title={t('personal.tabBarConfig.title')}
|
||
onBack={() => router.back()}
|
||
right={
|
||
<TouchableOpacity onPress={handleReset} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
|
||
<Text style={styles.headerRightButton}>
|
||
{t('personal.tabBarConfig.resetButton')}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
}
|
||
/>
|
||
|
||
{/* 主内容区 */}
|
||
<ScrollView
|
||
style={styles.content}
|
||
contentContainerStyle={[styles.scrollContent, { paddingTop: safeAreaTop }]} // 增加顶部间距,因为 HeaderBar 现在是 absolute 的
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
{/* 说明区域 */}
|
||
<View style={styles.headerSection}>
|
||
<Text style={styles.subtitle}>{t('personal.tabBarConfig.subtitle')}</Text>
|
||
<View style={styles.descriptionCard}>
|
||
<View style={styles.hintRow}>
|
||
<Ionicons name="information-circle-outline" size={20} color="#9370DB" />
|
||
<Text style={styles.descriptionText}>
|
||
{t('personal.tabBarConfig.description')}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* Tab 列表 - 聚合在一个卡片中 */}
|
||
<View style={styles.sectionContainer}>
|
||
{configs.map((item, index) => renderTabRow(item, index, configs.length))}
|
||
</View>
|
||
|
||
</ScrollView>
|
||
|
||
{/* 会员购买弹窗 */}
|
||
<MembershipModal
|
||
visible={showMembershipModal}
|
||
onClose={() => setShowMembershipModal(false)}
|
||
onPurchaseSuccess={handlePurchaseSuccess}
|
||
/>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: '#F5F5F5',
|
||
},
|
||
gradientBackground: {
|
||
position: 'absolute',
|
||
left: 0,
|
||
right: 0,
|
||
top: 0,
|
||
height: '60%', // 渐变覆盖上半部分即可
|
||
},
|
||
content: {
|
||
flex: 1,
|
||
},
|
||
scrollContent: {
|
||
paddingHorizontal: 16,
|
||
paddingBottom: 40,
|
||
},
|
||
headerSection: {
|
||
marginBottom: 16,
|
||
},
|
||
subtitle: {
|
||
fontSize: 14,
|
||
color: '#6C757D',
|
||
marginBottom: 12,
|
||
},
|
||
descriptionCard: {
|
||
backgroundColor: 'rgba(255, 255, 255, 0.6)',
|
||
borderRadius: 12,
|
||
padding: 12,
|
||
gap: 8,
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(147, 112, 219, 0.1)',
|
||
},
|
||
hintRow: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 8,
|
||
},
|
||
descriptionText: {
|
||
flex: 1,
|
||
fontSize: 13,
|
||
color: '#2C3E50',
|
||
lineHeight: 18,
|
||
},
|
||
sectionContainer: {
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 20,
|
||
marginBottom: 20,
|
||
overflow: 'hidden',
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 2 },
|
||
shadowOpacity: 0.03,
|
||
shadowRadius: 8,
|
||
elevation: 2,
|
||
},
|
||
tabItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
padding: 16,
|
||
paddingVertical: 16,
|
||
},
|
||
separatorContainer: {
|
||
paddingLeft: 68, // 40(icon) + 12(gap) + 16(padding)
|
||
paddingRight: 16,
|
||
},
|
||
separator: {
|
||
height: 1,
|
||
backgroundColor: '#F0F0F0',
|
||
},
|
||
tabInfo: {
|
||
flex: 1,
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 12,
|
||
},
|
||
iconContainer: {
|
||
width: 40,
|
||
height: 40,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
tabTextContainer: {
|
||
flex: 1,
|
||
},
|
||
tabTitle: {
|
||
fontSize: 16,
|
||
fontWeight: '500',
|
||
color: '#2C3E50',
|
||
marginBottom: 2,
|
||
},
|
||
tabSubtitle: {
|
||
fontSize: 12,
|
||
color: '#9370DB',
|
||
},
|
||
vipSubtitle: {
|
||
fontSize: 12,
|
||
color: '#FF6B6B',
|
||
},
|
||
switch: {
|
||
transform: [{ scaleX: 0.9 }, { scaleY: 0.9 }],
|
||
},
|
||
headerRightButton: {
|
||
fontSize: 15,
|
||
fontWeight: '600',
|
||
color: '#9370DB', // 使用主色调
|
||
},
|
||
}); |