添加签到功能,修复客服链接
This commit is contained in:
parent
02ca6256ec
commit
2009578b80
|
|
@ -17,9 +17,18 @@ function findPressableByText(renderer: ReactTestRenderer, label: string) {
|
|||
);
|
||||
}
|
||||
|
||||
async function flushPromises(times = 4) {
|
||||
for (let i = 0; i < times; i += 1) {
|
||||
await Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
alertMock,
|
||||
appStateRef,
|
||||
dailyCheckInStatusMock,
|
||||
dailyCheckInSubmitMock,
|
||||
getAuthTokenMock,
|
||||
getChannelIdMock,
|
||||
getVersionCodeMock,
|
||||
getVersionNameMock,
|
||||
|
|
@ -27,6 +36,7 @@ const {
|
|||
openPrivacyPolicyMock,
|
||||
openTermsOfServiceMock,
|
||||
profileItemsRef,
|
||||
refreshUserInfoMock,
|
||||
setClipboardStringMock,
|
||||
toastErrorMock,
|
||||
toastSuccessMock,
|
||||
|
|
@ -43,11 +53,42 @@ const {
|
|||
},
|
||||
actions: {
|
||||
resetConnection: vi.fn(),
|
||||
syncSession: vi.fn(),
|
||||
toggleCheckIn: vi.fn(),
|
||||
toggleSourceName: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
dailyCheckInStatusMock: vi.fn(async () => ({
|
||||
canCheckIn: true,
|
||||
currentDate: '2026-07-16',
|
||||
eligible: true,
|
||||
eligibilityMessage: '可签到',
|
||||
eligibilityReason: null,
|
||||
lastSignTime: null,
|
||||
rewardMinutes: 30,
|
||||
signTime: null,
|
||||
timezone: 'Asia/Shanghai',
|
||||
todayCheckedIn: false,
|
||||
})),
|
||||
dailyCheckInSubmitMock: vi.fn(async () => ({
|
||||
__message: '签到成功',
|
||||
afterExpiredDate: '2026-07-20 12:30:00',
|
||||
alreadyCheckedIn: false,
|
||||
beforeExpiredDate: '2026-07-20 12:00:00',
|
||||
canCheckIn: false,
|
||||
checkedIn: true,
|
||||
currentDate: '2026-07-16',
|
||||
eligible: true,
|
||||
eligibilityMessage: '可签到',
|
||||
eligibilityReason: null,
|
||||
lastSignTime: '2026-07-16 08:01:02',
|
||||
rewardMinutes: 30,
|
||||
signTime: '2026-07-16 08:01:02',
|
||||
timezone: 'Asia/Shanghai',
|
||||
todayCheckedIn: true,
|
||||
})),
|
||||
getAuthTokenMock: vi.fn(async () => null as string | null),
|
||||
getChannelIdMock: vi.fn(async () => 'default'),
|
||||
getVersionCodeMock: vi.fn(async () => '100'),
|
||||
getVersionNameMock: vi.fn(async () => '1.0.0'),
|
||||
|
|
@ -72,11 +113,13 @@ const {
|
|||
{ key: 'probe', label: '线路探测', color: '#8E77FF' },
|
||||
],
|
||||
},
|
||||
refreshUserInfoMock: vi.fn(async () => null as any),
|
||||
setClipboardStringMock: vi.fn(async () => {}),
|
||||
toastErrorMock: vi.fn(),
|
||||
toastSuccessMock: vi.fn(),
|
||||
userInfoRef: {
|
||||
current: {
|
||||
refreshUserInfo: vi.fn(async () => null),
|
||||
userInfo: null as any,
|
||||
},
|
||||
},
|
||||
|
|
@ -129,6 +172,26 @@ vi.mock('@telescope/toast', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@telescope/api-client/react', () => ({
|
||||
useJsonRpcRequest: (method: string) => {
|
||||
if (method === 'GetDailyCheckInStatus') {
|
||||
return {
|
||||
loading: false,
|
||||
runAsync: dailyCheckInStatusMock,
|
||||
};
|
||||
}
|
||||
|
||||
if (method === 'V5DailyCheckIn') {
|
||||
return {
|
||||
loading: false,
|
||||
runAsync: dailyCheckInSubmitMock,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected JSON-RPC method: ${method}`);
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@telescope/user-info', () => ({
|
||||
useUserInfo: () => userInfoRef.current,
|
||||
resolveBoundState: (userInfo: unknown) => Boolean(userInfo),
|
||||
|
|
@ -138,6 +201,10 @@ vi.mock('@telescope/user-info', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('@telescope/user-info/auth', () => ({
|
||||
getAuthToken: getAuthTokenMock,
|
||||
}));
|
||||
|
||||
vi.mock('@telescope/version-info', () => ({
|
||||
getVersionName: getVersionNameMock,
|
||||
getVersionCode: getVersionCodeMock,
|
||||
|
|
@ -260,6 +327,7 @@ describe('screens/main/ProfileScreen', () => {
|
|||
},
|
||||
actions: {
|
||||
resetConnection: vi.fn(),
|
||||
syncSession: vi.fn(),
|
||||
toggleCheckIn: vi.fn(),
|
||||
toggleSourceName: vi.fn(),
|
||||
},
|
||||
|
|
@ -269,8 +337,40 @@ describe('screens/main/ProfileScreen', () => {
|
|||
navigate: vi.fn(),
|
||||
};
|
||||
userInfoRef.current = {
|
||||
refreshUserInfo: refreshUserInfoMock,
|
||||
userInfo: null,
|
||||
};
|
||||
getAuthTokenMock.mockResolvedValue(null);
|
||||
refreshUserInfoMock.mockResolvedValue(null);
|
||||
dailyCheckInStatusMock.mockResolvedValue({
|
||||
canCheckIn: true,
|
||||
currentDate: '2026-07-16',
|
||||
eligible: true,
|
||||
eligibilityMessage: '可签到',
|
||||
eligibilityReason: null,
|
||||
lastSignTime: null,
|
||||
rewardMinutes: 30,
|
||||
signTime: null,
|
||||
timezone: 'Asia/Shanghai',
|
||||
todayCheckedIn: false,
|
||||
});
|
||||
dailyCheckInSubmitMock.mockResolvedValue({
|
||||
__message: '签到成功',
|
||||
afterExpiredDate: '2026-07-20 12:30:00',
|
||||
alreadyCheckedIn: false,
|
||||
beforeExpiredDate: '2026-07-20 12:00:00',
|
||||
canCheckIn: false,
|
||||
checkedIn: true,
|
||||
currentDate: '2026-07-16',
|
||||
eligible: true,
|
||||
eligibilityMessage: '可签到',
|
||||
eligibilityReason: null,
|
||||
lastSignTime: '2026-07-16 08:01:02',
|
||||
rewardMinutes: 30,
|
||||
signTime: '2026-07-16 08:01:02',
|
||||
timezone: 'Asia/Shanghai',
|
||||
todayCheckedIn: true,
|
||||
});
|
||||
profileItemsRef.current = [
|
||||
{ key: 'settings', label: '设置', color: '#5AA6FF' },
|
||||
{ key: 'notice', label: '通知', color: '#FF6E57' },
|
||||
|
|
@ -402,6 +502,75 @@ describe('screens/main/ProfileScreen', () => {
|
|||
expect(openPrivacyPolicyMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('点击签到会调用每日签到接口并刷新用户信息', async () => {
|
||||
const refreshedUserInfo = {
|
||||
canDailyCheckIn: false,
|
||||
dailyCheckInDate: '2026-07-16',
|
||||
dailyCheckInRewardMinutes: 30,
|
||||
expiredDate: 1784531400,
|
||||
id: 1,
|
||||
isTrial: false,
|
||||
todayCheckedIn: true,
|
||||
username: 'paid-user',
|
||||
};
|
||||
getAuthTokenMock.mockResolvedValue('token-1');
|
||||
refreshUserInfoMock.mockResolvedValue(refreshedUserInfo);
|
||||
const { ProfileScreen } = await import('../../../src/screens/main/ProfileScreen');
|
||||
let renderer!: ReactTestRenderer;
|
||||
|
||||
await act(async () => {
|
||||
renderer = create(<ProfileScreen navigation={navigationRef.current} />);
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
const checkInButton = findPressableByText(renderer, '签到');
|
||||
|
||||
await act(async () => {
|
||||
checkInButton?.props.onPress();
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(dailyCheckInStatusMock).toHaveBeenCalledWith({ loginToken: 'token-1' });
|
||||
expect(dailyCheckInSubmitMock).toHaveBeenCalledWith({ loginToken: 'token-1' });
|
||||
expect(appStateRef.current.actions.toggleCheckIn).not.toHaveBeenCalled();
|
||||
expect(refreshUserInfoMock).toHaveBeenCalled();
|
||||
expect(appStateRef.current.actions.syncSession).toHaveBeenCalledWith(refreshedUserInfo);
|
||||
expect(alertMock).toHaveBeenCalledWith('签到', '签到成功');
|
||||
});
|
||||
|
||||
it('已签到状态会提示今日已签到且不重复提交', async () => {
|
||||
userInfoRef.current = {
|
||||
refreshUserInfo: refreshUserInfoMock,
|
||||
userInfo: {
|
||||
canDailyCheckIn: false,
|
||||
dailyCheckInDate: '2026-07-16',
|
||||
dailyCheckInRewardMinutes: 30,
|
||||
id: 1,
|
||||
isTrial: false,
|
||||
todayCheckedIn: true,
|
||||
username: 'paid-user',
|
||||
},
|
||||
};
|
||||
const { ProfileScreen } = await import('../../../src/screens/main/ProfileScreen');
|
||||
let renderer!: ReactTestRenderer;
|
||||
|
||||
await act(async () => {
|
||||
renderer = create(<ProfileScreen navigation={navigationRef.current} />);
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
const checkInButton = findPressableByText(renderer, '已签到');
|
||||
|
||||
await act(async () => {
|
||||
checkInButton?.props.onPress();
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(dailyCheckInSubmitMock).not.toHaveBeenCalled();
|
||||
expect(appStateRef.current.actions.toggleCheckIn).not.toHaveBeenCalled();
|
||||
expect(alertMock).toHaveBeenCalledWith('签到', '今日已签到');
|
||||
});
|
||||
|
||||
it('个人中心菜单会隐藏初始化修复、官网下载和线路探测入口', async () => {
|
||||
const { ProfileScreen } = await import('../../../src/screens/main/ProfileScreen');
|
||||
let renderer!: ReactTestRenderer;
|
||||
|
|
|
|||
|
|
@ -128,7 +128,35 @@ describe('screens/main/SharePosterScreen', () => {
|
|||
|
||||
expect(nativeShareMock).toHaveBeenCalledWith({
|
||||
message:
|
||||
'我正在使用Xingyun网络服务,邀请你一起体验稳定连接:https://example.com/invite/abc',
|
||||
'我正在使用行云网络服务,邀请你一起体验稳定连接:https://example.com/invite/abc',
|
||||
});
|
||||
});
|
||||
|
||||
it('没有邀请链接时会分享 App Store 地址', async () => {
|
||||
userInfoRef.current = {
|
||||
inviteUrl: '',
|
||||
};
|
||||
const { SharePosterScreen } = await import('../../../src/screens/main/SharePosterScreen');
|
||||
let renderer!: ReactTestRenderer;
|
||||
|
||||
await act(async () => {
|
||||
renderer = create(<SharePosterScreen navigation={navigation} />);
|
||||
});
|
||||
|
||||
const shareButton = renderer.root.findAllByType('Pressable' as React.ElementType).find(
|
||||
node => node
|
||||
.findAllByType('Text' as React.ElementType)
|
||||
.some(text => text.props.children === '通过链接分享'),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
shareButton?.props.onPress();
|
||||
await flushPromises(10);
|
||||
});
|
||||
|
||||
expect(nativeShareMock).toHaveBeenCalledWith({
|
||||
message:
|
||||
'我正在使用行云网络服务,邀请你一起体验稳定连接:https://apps.apple.com/cn/app/%E8%A1%8C%E4%BA%91%E5%8A%A0%E9%80%9F%E5%99%A8-%E5%85%A8%E7%90%83%E7%BD%91%E7%BB%9C%E5%8A%A0%E9%80%9F/id6751933882',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
"TAURI_PLUGIN_PAYMENT_REQUEST_NAME": "ivory-dock",
|
||||
"TAURI_PLUGIN_CUSTOMER_SERVICE_NAME": "ivory-lane",
|
||||
"CUSTOMER_SERVICE_URLS": "https://pubtofilegz.oss-rg-china-mainland.aliyuncs.com/kf/gz_kefu.txt,https://wfjsq.s3.ap-east-1.amazonaws.com/gz_kefu.txt,https://raw.githubusercontent.com/ym-source/ym/main/gz_kefu.txt",
|
||||
"CUSTOMER_SERVICE_ACCOUNT_NAME": "三键连"
|
||||
"CUSTOMER_SERVICE_ACCOUNT_NAME": "行云加速器"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -749,7 +749,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "三键连";
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "行云加速器";
|
||||
CUSTOMER_SERVICE_URLS = "https://pubtofilegz.oss-rg-china-mainland.aliyuncs.com/kf/gz_kefu.txt,https://wfjsq.s3.ap-east-1.amazonaws.com/gz_kefu.txt,https://raw.githubusercontent.com/ym-source/ym/main/gz_kefu.txt";
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 2Y94XJY574;
|
||||
|
|
@ -827,7 +827,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "三键连";
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "行云加速器";
|
||||
CUSTOMER_SERVICE_URLS = "https://pubtofilegz.oss-rg-china-mainland.aliyuncs.com/kf/gz_kefu.txt,https://wfjsq.s3.ap-east-1.amazonaws.com/gz_kefu.txt,https://raw.githubusercontent.com/ym-source/ym/main/gz_kefu.txt";
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 2Y94XJY574;
|
||||
|
|
@ -917,7 +917,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "三键连";
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "行云加速器";
|
||||
CUSTOMER_SERVICE_URLS = "https://pubtofilegz.oss-rg-china-mainland.aliyuncs.com/kf/gz_kefu.txt,https://wfjsq.s3.ap-east-1.amazonaws.com/gz_kefu.txt,https://raw.githubusercontent.com/ym-source/ym/main/gz_kefu.txt";
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 2Y94XJY574;
|
||||
|
|
@ -1008,7 +1008,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "三键连";
|
||||
CUSTOMER_SERVICE_ACCOUNT_NAME = "行云加速器";
|
||||
CUSTOMER_SERVICE_URLS = "https://pubtofilegz.oss-rg-china-mainland.aliyuncs.com/kf/gz_kefu.txt,https://wfjsq.s3.ap-east-1.amazonaws.com/gz_kefu.txt,https://raw.githubusercontent.com/ym-source/ym/main/gz_kefu.txt";
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 2Y94XJY574;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,19 @@ import { Alert, Image, Modal, Pressable, ScrollView, View } from 'react-native';
|
|||
import Text from '@telescope/shim-text';
|
||||
import { setString as setClipboardString } from '@telescope/clipboard';
|
||||
import { useToast } from '@telescope/toast';
|
||||
import { useJsonRpcRequest } from '@telescope/api-client/react';
|
||||
import useMemoizedFn from 'ahooks/es/useMemoizedFn';
|
||||
import useRequest from 'ahooks/es/useRequest';
|
||||
import useSafeState from 'ahooks/es/useSafeState';
|
||||
import type { UserInfo } from '@telescope/shared-types';
|
||||
import type {
|
||||
DailyCheckInResponse,
|
||||
DailyCheckInStatus,
|
||||
UserInfo,
|
||||
} from '@telescope/shared-types';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { ActionType, logger } from '@telescope/logger';
|
||||
import { useUserInfo } from '@telescope/user-info';
|
||||
import { getAuthToken } from '@telescope/user-info/auth';
|
||||
import { appAssets } from '../../assets';
|
||||
import type { ProfileItem } from '../../data';
|
||||
import { profileItems } from '../../data';
|
||||
|
|
@ -22,6 +28,7 @@ import {
|
|||
import { AppIcon } from '../../components/AppIcons';
|
||||
import { useAppState } from '../../context/AppState';
|
||||
import {
|
||||
getBrowserErrorMessage,
|
||||
getAppVersionLabel,
|
||||
getAppVersionLabelAsync,
|
||||
} from './helpers';
|
||||
|
|
@ -36,6 +43,18 @@ import LineProbeModal from '../../components/LineProbeModal';
|
|||
|
||||
const PROFILE_SHORTCUT_KEYS = new Set(['support', 'notice', 'checkIn']);
|
||||
const HIDDEN_PROFILE_MENU_KEYS = new Set(['repair', 'website', 'probe']);
|
||||
const DAILY_CHECK_IN_FALLBACK_REWARD_MINUTES = 30;
|
||||
|
||||
type DailyCheckInParams = {
|
||||
loginToken?: string;
|
||||
};
|
||||
|
||||
type DailyCheckInDisplayStatus = {
|
||||
rewardMinutes?: number;
|
||||
todayCheckedIn: boolean;
|
||||
canCheckIn: boolean;
|
||||
eligibilityMessage?: string | null;
|
||||
};
|
||||
|
||||
const profileShortcutItems: ProfileItem[] = [
|
||||
{
|
||||
|
|
@ -75,10 +94,81 @@ function getProfileRowLabel(item: ProfileItem) {
|
|||
return item.key === 'terms' ? '服务协议' : item.label;
|
||||
}
|
||||
|
||||
function resolveDailyCheckInStatusFromUserInfo(
|
||||
userInfo?: UserInfo | null,
|
||||
): DailyCheckInDisplayStatus | null {
|
||||
if (
|
||||
!userInfo ||
|
||||
(
|
||||
userInfo.todayCheckedIn === undefined &&
|
||||
userInfo.canDailyCheckIn === undefined &&
|
||||
userInfo.dailyCheckInRewardMinutes === undefined
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
rewardMinutes: userInfo.dailyCheckInRewardMinutes,
|
||||
todayCheckedIn: userInfo.todayCheckedIn === true,
|
||||
canCheckIn: userInfo.canDailyCheckIn === true,
|
||||
};
|
||||
}
|
||||
|
||||
function getDailyCheckInBlockedMessage(status: DailyCheckInDisplayStatus | null): string | null {
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status.todayCheckedIn) {
|
||||
return '今日已签到';
|
||||
}
|
||||
|
||||
if (!status.canCheckIn) {
|
||||
return status.eligibilityMessage || '当前暂不可签到';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatDailyCheckInReward(rewardMinutes?: number) {
|
||||
const minutes = typeof rewardMinutes === 'number' &&
|
||||
Number.isFinite(rewardMinutes) &&
|
||||
rewardMinutes > 0
|
||||
? rewardMinutes
|
||||
: DAILY_CHECK_IN_FALLBACK_REWARD_MINUTES;
|
||||
|
||||
if (minutes % (24 * 60) === 0) {
|
||||
return `${minutes / (24 * 60)}天`;
|
||||
}
|
||||
|
||||
if (minutes % 60 === 0) {
|
||||
return `${minutes / 60}小时`;
|
||||
}
|
||||
|
||||
return `${minutes}分钟`;
|
||||
}
|
||||
|
||||
function getProfileShortcutLabel(
|
||||
item: ProfileItem,
|
||||
dailyCheckInStatus: DailyCheckInDisplayStatus | null,
|
||||
dailyCheckInSubmitting: boolean,
|
||||
) {
|
||||
if (item.key !== 'checkIn') {
|
||||
return item.label;
|
||||
}
|
||||
|
||||
if (dailyCheckInSubmitting) {
|
||||
return '签到中';
|
||||
}
|
||||
|
||||
return dailyCheckInStatus?.todayCheckedIn ? '已签到' : item.label;
|
||||
}
|
||||
|
||||
export function ProfileScreen({ navigation }: any) {
|
||||
const { state, actions } = useAppState();
|
||||
const toast = useToast();
|
||||
const { userInfo } = useUserInfo<UserInfo>();
|
||||
const { userInfo, refreshUserInfo } = useUserInfo<UserInfo>();
|
||||
const rootNavigation = navigation.getParent?.();
|
||||
const bound = resolveBoundState(userInfo);
|
||||
const userLabel = resolveUserDisplayLabel({
|
||||
|
|
@ -87,6 +177,38 @@ export function ProfileScreen({ navigation }: any) {
|
|||
});
|
||||
const [bindModalVisible, setBindModalVisible] = useSafeState(false);
|
||||
const { data: versionLabel = getAppVersionLabel() } = useRequest(getAppVersionLabelAsync);
|
||||
const [dailyCheckInStatus, setDailyCheckInStatus] = useSafeState<DailyCheckInStatus | null>(null);
|
||||
const { runAsync: getDailyCheckInStatus } = useJsonRpcRequest<
|
||||
DailyCheckInParams,
|
||||
DailyCheckInStatus
|
||||
>('GetDailyCheckInStatus', undefined, { manual: true });
|
||||
const { loading: dailyCheckInSubmitting, runAsync: submitDailyCheckIn } = useJsonRpcRequest<
|
||||
DailyCheckInParams,
|
||||
DailyCheckInResponse
|
||||
>('V5DailyCheckIn', undefined, { manual: true });
|
||||
const userDailyCheckInStatus = useMemo(
|
||||
() => resolveDailyCheckInStatusFromUserInfo(userInfo),
|
||||
[userInfo],
|
||||
);
|
||||
const effectiveDailyCheckInStatus = dailyCheckInStatus ?? userDailyCheckInStatus;
|
||||
|
||||
useRequest(async () => {
|
||||
const token = await getAuthToken();
|
||||
if (!token) {
|
||||
setDailyCheckInStatus(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
const status = await getDailyCheckInStatus({ loginToken: token });
|
||||
setDailyCheckInStatus(status);
|
||||
return status;
|
||||
}, {
|
||||
onError: (error) => {
|
||||
logger.warn('每日签到状态加载失败', {
|
||||
error: getBrowserErrorMessage(error),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 判断是否在底部 tab 中访问,避免 tab 页也显示返回按钮。
|
||||
const parentState = navigation.getParent?.()?.getState?.();
|
||||
|
|
@ -137,7 +259,52 @@ export function ProfileScreen({ navigation }: any) {
|
|||
setBindModalVisible(true);
|
||||
};
|
||||
|
||||
const onProfileItemFunction = useMemo(() => new Map<string, (item: ProfileItem) => void>([
|
||||
const handleDailyCheckIn = useMemoizedFn(async () => {
|
||||
if (dailyCheckInSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blockedMessage = getDailyCheckInBlockedMessage(effectiveDailyCheckInStatus);
|
||||
if (blockedMessage) {
|
||||
Alert.alert('签到', blockedMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await getAuthToken();
|
||||
if (!token) {
|
||||
Alert.alert('签到', '请先登录后再签到');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await submitDailyCheckIn({ loginToken: token });
|
||||
setDailyCheckInStatus(result);
|
||||
Alert.alert(
|
||||
'签到',
|
||||
result.__message ||
|
||||
(result.alreadyCheckedIn
|
||||
? '今日已签到'
|
||||
: `签到成功,已赠送${formatDailyCheckInReward(result.rewardMinutes)}。`),
|
||||
);
|
||||
|
||||
try {
|
||||
const refreshedUserInfo = await refreshUserInfo();
|
||||
if (refreshedUserInfo) {
|
||||
actions.syncSession(refreshedUserInfo);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('每日签到后刷新用户信息失败', {
|
||||
error: getBrowserErrorMessage(error),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const message = getBrowserErrorMessage(error);
|
||||
logger.warn('每日签到失败', { error: message });
|
||||
Alert.alert('签到', message);
|
||||
}
|
||||
});
|
||||
|
||||
const onProfileItemFunction = useMemo(() => new Map<string, (item: ProfileItem) => void | Promise<void>>([
|
||||
// 导航类
|
||||
['settings', (item) => navigation.navigate('Settings')],
|
||||
['notice', (item) => navigation.navigate('Messages')],
|
||||
|
|
@ -148,13 +315,7 @@ export function ProfileScreen({ navigation }: any) {
|
|||
['logs', (item) => navigation.navigate('CustomerService')],
|
||||
|
||||
// 操作类
|
||||
['checkIn', (item) => {
|
||||
actions.toggleCheckIn();
|
||||
Alert.alert(
|
||||
'签到',
|
||||
state.checkedIn ? '今日已取消签到。' : '签到成功,额外赠送1天时长。',
|
||||
);
|
||||
}],
|
||||
['checkIn', () => handleDailyCheckIn()],
|
||||
['source', (item) => actions.toggleSourceName()],
|
||||
['repair', (item) => Alert.alert('初始化修复', '将清除所有本地缓存数据,确定继续吗?', [
|
||||
{
|
||||
|
|
@ -200,12 +361,17 @@ export function ProfileScreen({ navigation }: any) {
|
|||
(item) => openBrowserUrl({ url: XINGYUN_WEBSITE_CONFIG.url, source: 'linkable_text' }),
|
||||
],
|
||||
['probe', () => setProbeModalVisible(true)],
|
||||
]), [navigation, state.checkedIn, actions]);
|
||||
]), [navigation, actions, handleDailyCheckIn]);
|
||||
|
||||
// 统一的处理函数
|
||||
const handleProfileGridPress = useCallback((item: ProfileItem) => {
|
||||
const handler = onProfileItemFunction.get(item.key);
|
||||
handler ? handler(item) : Alert.alert('提示', `${item.label}功能暂未开放。`);
|
||||
if (handler) {
|
||||
void handler(item);
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert('提示', `${item.label}功能暂未开放。`);
|
||||
}, [onProfileItemFunction]);
|
||||
|
||||
return (
|
||||
|
|
@ -278,6 +444,7 @@ export function ProfileScreen({ navigation }: any) {
|
|||
<View style={styles.profileShortcutCard}>
|
||||
{profileShortcutItems.map(item => (
|
||||
<Pressable
|
||||
disabled={item.key === 'checkIn' && dailyCheckInSubmitting}
|
||||
key={item.key}
|
||||
onPress={() => handleProfileGridPress(item)}
|
||||
style={styles.profileShortcutItem}
|
||||
|
|
@ -287,7 +454,13 @@ export function ProfileScreen({ navigation }: any) {
|
|||
source={item.icon}
|
||||
style={styles.profileShortcutIcon}
|
||||
/>
|
||||
<Text style={styles.profileShortcutText}>{item.label}</Text>
|
||||
<Text style={styles.profileShortcutText}>
|
||||
{getProfileShortcutLabel(
|
||||
item,
|
||||
effectiveDailyCheckInStatus,
|
||||
dailyCheckInSubmitting,
|
||||
)}
|
||||
</Text>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import { AppPageHeader } from '../../navigation/AppScreenHeader';
|
|||
import { getBrowserErrorMessage } from './helpers';
|
||||
import { styles } from './SharePosterScreen.styles';
|
||||
|
||||
const XINGYUN_APP_STORE_URL =
|
||||
'https://apps.apple.com/cn/app/%E8%A1%8C%E4%BA%91%E5%8A%A0%E9%80%9F%E5%99%A8-%E5%85%A8%E7%90%83%E7%BD%91%E7%BB%9C%E5%8A%A0%E9%80%9F/id6751933882';
|
||||
|
||||
export function SharePosterScreen({
|
||||
navigation,
|
||||
showBackButton = true,
|
||||
|
|
@ -30,9 +33,8 @@ export function SharePosterScreen({
|
|||
|
||||
const handleShare = async () => {
|
||||
const inviteUrl = userInfo?.inviteUrl?.trim() || '';
|
||||
const message = inviteUrl
|
||||
? `我正在使用Xingyun网络服务,邀请你一起体验稳定连接:${inviteUrl}`
|
||||
: '我正在使用Xingyun网络服务,邀请你一起体验稳定连接。';
|
||||
const shareUrl = inviteUrl || XINGYUN_APP_STORE_URL;
|
||||
const message = `我正在使用行云网络服务,邀请你一起体验稳定连接:${shareUrl}`;
|
||||
|
||||
try {
|
||||
logger.action(ActionType.SHARE, { source: 'share_poster_invite', hasInviteUrl: !!inviteUrl });
|
||||
|
|
|
|||
Loading…
Reference in New Issue