feat(toast): 实现原生Toast系统并优化会员购买错误处理

- 新增iOS原生Toast模块(NativeToastManager),提供毛玻璃风格的Toast展示
- 重构ToastContext为原生模块调用,添加错误边界和回退机制
- 优化会员购买流程的错误处理,使用RevenueCat标准错误码
- 调整购买按钮高度和恢复购买按钮字体大小,改善UI体验
- 移除不必要的延迟和注释代码,提升代码质量
This commit is contained in:
richarjiang
2025-10-28 11:04:34 +08:00
parent db8b50f6d7
commit 71a8bb9740
6 changed files with 707 additions and 140 deletions

View File

@@ -1,58 +1,122 @@
/**
* 全局Toast工具函数
*
* 使用方式:
* Global toast helper backed by native UI.
*
* Usage:
* import { Toast } from '@/utils/toast.utils';
*
*
* Toast.success('操作成功!');
* Toast.error('操作失败!');
* Toast.warning('注意!');
*/
import { ToastContextType } from '@/contexts/ToastContext';
import { NativeModules, Platform, ToastAndroid } from 'react-native';
let toastRef: ToastContextType | null = null;
export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info';
export const setToastRef = (ref: ToastContextType) => {
toastRef = ref;
};
export interface ToastConfig {
message?: string;
text1?: string;
duration?: number;
type?: ToastType;
backgroundColor?: string;
textColor?: string;
icon?: string;
}
export const Toast = {
success: (message: string, duration?: number) => {
if (toastRef) {
toastRef.showSuccess(message, duration);
} else {
console.warn('Toast not initialized. Please wrap your app with ToastProvider');
}
},
error: (message: string, duration?: number) => {
if (toastRef) {
toastRef.showError(message, duration);
} else {
console.warn('Toast not initialized. Please wrap your app with ToastProvider');
}
},
warning: (message: string, duration?: number) => {
if (toastRef) {
toastRef.showWarning(message, duration);
} else {
console.warn('Toast not initialized. Please wrap your app with ToastProvider');
}
},
show: (config: {
message: string;
type NativeToastModule = {
show: (options: {
message?: string;
text1?: string;
duration?: number;
type?: ToastType;
backgroundColor?: string;
textColor?: string;
icon?: string;
}) => {
if (toastRef) {
toastRef.showToast(config);
} else {
console.warn('Toast not initialized. Please wrap your app with ToastProvider');
}) => void;
};
const DEFAULT_DURATION = 2000;
const MIN_DURATION = 1000;
const MAX_DURATION = 10000;
// 增强类型安全性:添加运行时检查确保模块存在且具有预期的方法
const nativeToast = NativeModules.NativeToastManager &&
typeof NativeModules.NativeToastManager.show === 'function'
? NativeModules.NativeToastManager as NativeToastModule
: undefined;
const clampDuration = (duration?: number) => {
const value = typeof duration === 'number' ? duration : DEFAULT_DURATION;
return Math.min(Math.max(value, MIN_DURATION), MAX_DURATION);
};
const resolveMessage = (config: ToastConfig) => {
const raw = (config.message ?? config.text1 ?? '').toString();
return raw.trim();
};
const showOnAndroid = (message: string, duration: number) => {
const toastDuration = duration >= 3000 ? ToastAndroid.LONG : ToastAndroid.SHORT;
ToastAndroid.showWithGravity(message, toastDuration, ToastAndroid.CENTER);
};
const showNative = (config: ToastConfig) => {
const message = resolveMessage(config);
if (!message) {
console.warn('Toast invoked without message content');
return;
}
const duration = clampDuration(config.duration);
const type = config.type ?? 'default';
if (Platform.OS === 'android') {
showOnAndroid(message, duration);
return;
}
if (nativeToast?.show) {
nativeToast.show({
...config,
message,
duration,
type,
});
return;
}
// 为不支持的平台提供更明显的用户反馈
if (Platform.OS === 'web') {
// 在 Web 环境下使用 console.warn 并尝试使用浏览器原生 alert
console.warn(`Toast: ${message}`);
try {
// 仅在重要消息时使用 alert避免过度打扰用户
if (config.type === 'error' || config.type === 'warning') {
alert(message);
}
} catch (e) {
// 忽略 alert 错误
}
} else {
console.log(`Toast: ${message}`);
}
};
export const Toast = {
show: (config: ToastConfig) => {
showNative(config);
},
};
success: (message: string, duration?: number) => {
showNative({ message, duration, type: 'success' });
},
error: (message: string, duration?: number) => {
showNative({ message, duration, type: 'error' });
},
warning: (message: string, duration?: number) => {
showNative({ message, duration, type: 'warning' });
},
info: (message: string, duration?: number) => {
showNative({ message, duration, type: 'info' });
},
};