diff --git a/app.json b/app.json
index 7ff7456..4558aef 100644
--- a/app.json
+++ b/app.json
@@ -2,7 +2,7 @@
"expo": {
"name": "Out Live",
"slug": "digital-pilates",
- "version": "1.1.3",
+ "version": "1.1.4",
"orientation": "portrait",
"scheme": "digitalpilates",
"userInterfaceStyle": "light",
diff --git a/app/settings/tab-bar-config.tsx b/app/settings/tab-bar-config.tsx
index 9e1fc5e..0d2fed6 100644
--- a/app/settings/tab-bar-config.tsx
+++ b/app/settings/tab-bar-config.tsx
@@ -1,5 +1,6 @@
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
+import { useVipService } from '@/hooks/useVipService';
import {
resetToDefault,
selectTabBarConfigs,
@@ -9,7 +10,7 @@ import {
import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { useRouter } from 'expo-router';
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import {
Alert,
ScrollView,
@@ -20,6 +21,7 @@ import {
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';
@@ -31,15 +33,38 @@ export default function TabBarConfigScreen() {
const dispatch = useAppDispatch();
const safeAreaTop = useSafeAreaTop(60);
const configs = useAppSelector(selectTabBarConfigs);
+ const { isVip } = useVipService();
+ const [showMembershipModal, setShowMembershipModal] = useState(false);
// 处理开关切换
const handleToggle = useCallback(
(tabId: string) => {
- dispatch(toggleTabEnabled(tabId));
+ // 直接检查用户是否是 VIP(底部栏配置不是权益类功能,而是基础功能)
+ if (isVip) {
+ // VIP 用户可以正常切换
+ dispatch(toggleTabEnabled(tabId));
+ } else {
+ // 非 VIP 用户显示购买弹窗
+ setShowMembershipModal(true);
+ }
},
- [dispatch]
+ [dispatch, isVip]
);
+ // 页面加载时检查 VIP 状态
+ useEffect(() => {
+ if (!isVip) {
+ // 非 VIP 用户进入页面时立即显示购买弹窗
+ setShowMembershipModal(true);
+ }
+ }, [isVip]);
+
+ // 购买成功回调
+ const handlePurchaseSuccess = useCallback(() => {
+ // 购买成功后可以执行一些操作,比如刷新用户信息
+ console.log('会员购买成功');
+ }, []);
+
// 恢复默认设置
const handleReset = useCallback(() => {
Alert.alert(
@@ -80,6 +105,11 @@ export default function TabBarConfigScreen() {
{t('personal.tabBarConfig.cannotDisable')}
)}
+ {item.canBeDisabled && !isVip && (
+
+ {t('personal.tabBarConfig.vipOnly')}
+
+ )}
@@ -87,7 +117,7 @@ export default function TabBarConfigScreen() {
handleToggle(item.id)}
- disabled={!item.canBeDisabled}
+ disabled={!item.canBeDisabled || !isVip}
trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
thumbColor="#FFFFFF"
style={styles.switch}
@@ -153,6 +183,13 @@ export default function TabBarConfigScreen() {
+
+ {/* 会员购买弹窗 */}
+ setShowMembershipModal(false)}
+ onPurchaseSuccess={handlePurchaseSuccess}
+ />
);
}
@@ -253,6 +290,10 @@ const styles = StyleSheet.create({
fontSize: 12,
color: '#9370DB',
},
+ vipSubtitle: {
+ fontSize: 12,
+ color: '#FF6B6B',
+ },
switch: {
transform: [{ scaleX: 0.9 }, { scaleY: 0.9 }],
},
diff --git a/components/model/MembershipModal.tsx b/components/model/MembershipModal.tsx
index 5cb002d..e315f1d 100644
--- a/components/model/MembershipModal.tsx
+++ b/components/model/MembershipModal.tsx
@@ -194,6 +194,20 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
vipText: t('membershipModal.benefits.permissions.notSupported')
}
},
+ {
+ title: t('membershipModal.benefits.items.tabBarCustomization.title'),
+ description: t('membershipModal.benefits.items.tabBarCustomization.description'),
+ vip: {
+ type: 'exclusive',
+ text: t('membershipModal.benefits.permissions.fullSupport'),
+ vipText: t('membershipModal.benefits.permissions.unlimited')
+ },
+ regular: {
+ type: 'exclusive',
+ text: t('membershipModal.benefits.permissions.notSupported'),
+ vipText: t('membershipModal.benefits.permissions.notSupported')
+ }
+ },
];
// 根据选中的产品生成tips内容
diff --git a/i18n/en/personal.ts b/i18n/en/personal.ts
index c3a1783..d21dd03 100644
--- a/i18n/en/personal.ts
+++ b/i18n/en/personal.ts
@@ -92,6 +92,7 @@ export const personal = {
description: 'Use toggles to show or hide tabs',
resetButton: 'Reset',
cannotDisable: 'Cannot be disabled',
+ vipOnly: 'VIP members only',
resetConfirm: {
title: 'Reset to Default?',
message: 'This will reset all tab bar settings and visibility',
diff --git a/i18n/zh/personal.ts b/i18n/zh/personal.ts
index 1312479..7777a4c 100644
--- a/i18n/zh/personal.ts
+++ b/i18n/zh/personal.ts
@@ -92,6 +92,7 @@ export const personal = {
description: '使用开关控制标签的显示和隐藏',
resetButton: '恢复默认',
cannotDisable: '此标签不可关闭',
+ vipOnly: '仅限VIP会员',
resetConfirm: {
title: '恢复默认设置?',
message: '将重置所有底部栏配置和显示状态',
@@ -299,6 +300,10 @@ export const membershipModal = {
title: '解锁无限自定义挑战',
description: '突破限制,邀请挚友同行,让坚持不再孤单,共同见证蜕变',
},
+ tabBarCustomization: {
+ title: '底部栏自定义',
+ description: '个性化底部导航栏,隐藏不需要的功能标签',
+ },
},
permissions: {
unlimited: '无限次使用',
diff --git a/ios/OutLive/Info.plist b/ios/OutLive/Info.plist
index bdb8f7e..e38d87e 100644
--- a/ios/OutLive/Info.plist
+++ b/ios/OutLive/Info.plist
@@ -27,7 +27,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.1.3
+ 1.1.4
CFBundleSignature
????
CFBundleURLTypes
diff --git a/store/tabBarConfigSlice.ts b/store/tabBarConfigSlice.ts
index 8ffa78f..b7eafb7 100644
--- a/store/tabBarConfigSlice.ts
+++ b/store/tabBarConfigSlice.ts
@@ -9,10 +9,17 @@ export interface TabConfig {
icon: string; // SF Symbol 图标名
titleKey: string; // i18n 翻译 key
enabled: boolean; // 是否启用
- canBeDisabled: boolean; // 是否可以被禁用
+ canBeDisabled: boolean; // 是否可以被禁用(系统级配置,不持久化)
order: number; // 显示顺序
}
+// 用户可持久化的配置(只包含用户可控制的属性)
+interface UserTabConfig {
+ id: string;
+ enabled: boolean;
+ order: number;
+}
+
// State 接口
interface TabBarConfigState {
configs: TabConfig[];
@@ -34,7 +41,7 @@ export const DEFAULT_TAB_CONFIGS: TabConfig[] = [
icon: 'pills.fill',
titleKey: 'statistics.tabs.medications',
enabled: true,
- canBeDisabled: false,
+ canBeDisabled: true, // 用药管理可以被关闭
order: 2,
},
{
@@ -42,7 +49,7 @@ export const DEFAULT_TAB_CONFIGS: TabConfig[] = [
icon: 'timer',
titleKey: 'statistics.tabs.fasting',
enabled: true,
- canBeDisabled: true, // 只有断食可以被关闭
+ canBeDisabled: true, // 断食可以被关闭
order: 3,
},
{
@@ -50,7 +57,7 @@ export const DEFAULT_TAB_CONFIGS: TabConfig[] = [
icon: 'trophy.fill',
titleKey: 'statistics.tabs.challenges',
enabled: true,
- canBeDisabled: false,
+ canBeDisabled: true, // 挑战可以被关闭
order: 4,
},
{
@@ -120,10 +127,16 @@ const tabBarConfigSlice = createSlice({
},
});
-// 持久化配置到 AsyncStorage
+// 持久化配置到 AsyncStorage(只保存用户可控制的属性)
const saveConfigsToStorage = async (configs: TabConfig[]) => {
try {
- await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(configs));
+ // 只保存用户可控制的属性:enabled 和 order
+ const userConfigs: UserTabConfig[] = configs.map(config => ({
+ id: config.id,
+ enabled: config.enabled,
+ order: config.order,
+ }));
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(userConfigs));
logger.info('底部栏配置已保存');
} catch (error) {
logger.error('保存底部栏配置失败:', error);
@@ -136,12 +149,12 @@ export const loadTabBarConfigs = () => async (dispatch: any) => {
const stored = await AsyncStorage.getItem(STORAGE_KEY);
if (stored) {
- const configs = JSON.parse(stored) as TabConfig[];
+ const userConfigs = JSON.parse(stored) as UserTabConfig[];
// 验证配置有效性
- if (Array.isArray(configs) && configs.length > 0) {
- // 合并默认配置,确保新增的 tab 也能显示
- const mergedConfigs = mergeWithDefaults(configs);
+ if (Array.isArray(userConfigs) && userConfigs.length > 0) {
+ // 合并用户配置和默认配置
+ const mergedConfigs = mergeWithDefaults(userConfigs);
dispatch(setConfigs(mergedConfigs));
logger.info('底部栏配置已加载');
return;
@@ -159,15 +172,24 @@ export const loadTabBarConfigs = () => async (dispatch: any) => {
}
};
-// 合并存储的配置和默认配置
-const mergeWithDefaults = (storedConfigs: TabConfig[]): TabConfig[] => {
- const merged = [...storedConfigs];
+// 合并用户配置和默认配置
+const mergeWithDefaults = (userConfigs: UserTabConfig[]): TabConfig[] => {
+ const merged: TabConfig[] = [];
- // 检查是否有新增的默认 tab
+ // 遍历默认配置,将用户的 enabled 和 order 合并进来
DEFAULT_TAB_CONFIGS.forEach(defaultConfig => {
- const exists = merged.find(c => c.id === defaultConfig.id);
- if (!exists) {
- // 新增的 tab,添加到末尾
+ const userConfig = userConfigs.find(c => c.id === defaultConfig.id);
+
+ if (userConfig) {
+ // 合并:系统配置(icon, titleKey, canBeDisabled)从默认配置读取
+ // 用户配置(enabled, order)从用户配置读取
+ merged.push({
+ ...defaultConfig,
+ enabled: userConfig.enabled,
+ order: userConfig.order,
+ });
+ } else {
+ // 新增的 tab,使用默认配置
merged.push({
...defaultConfig,
order: merged.length + 1,