fix rnpay
This commit is contained in:
842
App.tsx
842
App.tsx
@@ -1,795 +1,55 @@
|
|||||||
import React, { Component } from "react";
|
import React from 'react';
|
||||||
import { Alert, AppState, AppStateStatus, Modal, Text, TextInput, TouchableOpacity, View } from "react-native";
|
import { Text } from 'react-native';
|
||||||
import DeviceInfo from 'react-native-device-info';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import {
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
PhonePeBusinessBind,
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
GooglePayBusinessBind,
|
import HomeScreen from './screens/HomeScreen';
|
||||||
WalletType,
|
import MessageScreen from './screens/MessageScreen';
|
||||||
PaytmBusinessBindResult,
|
|
||||||
PhonePeBusinessBindResult,
|
|
||||||
PaytmPersonalBind,
|
|
||||||
PaytmPersonalBindResult,
|
|
||||||
MobikwikPersonalBindResult,
|
|
||||||
FreechargePersonalBindResult,
|
|
||||||
GooglePayBusinessBindResult,
|
|
||||||
BharatPeBusinessBindResult,
|
|
||||||
onSmsMessage,
|
|
||||||
startSmsListener,
|
|
||||||
stopSmsListener,
|
|
||||||
checkSmsPermission,
|
|
||||||
requestSmsPermission,
|
|
||||||
PhonePePersonalBindResult,
|
|
||||||
PhonePePersonalBind,
|
|
||||||
SmsMessage,
|
|
||||||
proxyBackgroundService,
|
|
||||||
proxySendMessage,
|
|
||||||
onProxyMessage,
|
|
||||||
} from "rnwalletman";
|
|
||||||
|
|
||||||
import {
|
const Tab = createBottomTabNavigator();
|
||||||
FreeChargeBind,
|
|
||||||
MobikwikOTPBind,
|
|
||||||
PayTmPersonalOTPBind,
|
|
||||||
PhonePePersonalOTPBind,
|
|
||||||
BharatPeBusinessOTPBind,
|
|
||||||
PaytmBusinessOTPBind,
|
|
||||||
} from './components/WalletBindComponents';
|
|
||||||
|
|
||||||
import Api, { loadServerDomain, saveServerDomain, getServerDomain, getUseHttps } from './services/api';
|
export default function App() {
|
||||||
import { AppProps, WalletmanAppState } from './types';
|
return (
|
||||||
import { styles } from './styles';
|
<SafeAreaProvider>
|
||||||
import WebView from "react-native-webview";
|
<NavigationContainer>
|
||||||
|
<Tab.Navigator
|
||||||
export default class App extends Component<AppProps, WalletmanAppState> {
|
screenOptions={{
|
||||||
private deviceId: string;
|
tabBarActiveTintColor: '#3498db',
|
||||||
private tuneUserId: string;
|
tabBarInactiveTintColor: '#999',
|
||||||
private clientId: string = '';
|
tabBarStyle: {
|
||||||
private appStateSubscription?: any;
|
backgroundColor: '#fff',
|
||||||
|
borderTopWidth: 1,
|
||||||
private webViewRef: WebView | null = null;
|
borderTopColor: '#e0e0e0',
|
||||||
|
},
|
||||||
constructor(props: AppProps) {
|
headerStyle: { backgroundColor: '#3498db' },
|
||||||
super(props);
|
headerTintColor: '#fff',
|
||||||
this.state = {
|
headerTitleStyle: { fontWeight: 'bold' },
|
||||||
paytmPersonalBindType: 'otpMode',
|
|
||||||
showPaytmPersonalBind: false,
|
|
||||||
showPaytmBusinessBind: false,
|
|
||||||
showPhonePePersonalBind: false,
|
|
||||||
phonePePersonalBindType: 'otpMode',
|
|
||||||
showPhonePeBusinessBind: false,
|
|
||||||
showGooglePayBusinessBind: false,
|
|
||||||
showBharatPeBusinessBind: false,
|
|
||||||
showMobikwikPersonalBind: false,
|
|
||||||
showFreechargePersonalBind: false,
|
|
||||||
proxyStatus: 'idle',
|
|
||||||
showServerSettings: false,
|
|
||||||
settingsHost: '',
|
|
||||||
settingsPort: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
this.deviceId = DeviceInfo.getUniqueIdSync();
|
|
||||||
this.tuneUserId = Math.random().toString(36).substring(2, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
await loadServerDomain();
|
|
||||||
await this.setupPermissions();
|
|
||||||
|
|
||||||
this.onProxyMessageSub = onProxyMessage((msg) => {
|
|
||||||
switch (msg.type) {
|
|
||||||
case 'echo':
|
|
||||||
Alert.alert('Echo 回来了', JSON.stringify(msg.data));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Login with auto-retry every 3s until success (setTimeout, non-blocking)
|
|
||||||
const doLogin = () => {
|
|
||||||
Api.instance.login('test123', '123456')
|
|
||||||
.then(async (userId) => {
|
|
||||||
console.log('[Login] userId:', userId);
|
|
||||||
await this.startProxyClient();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log('[Login] retry in 3s:', error);
|
|
||||||
setTimeout(doLogin, 3000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
doLogin();
|
|
||||||
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onProxyMessageSub?: ReturnType<typeof onProxyMessage>;
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.stopProxyClient();
|
|
||||||
stopSmsListener();
|
|
||||||
this.onProxyMessageSub?.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEcho = () => {
|
|
||||||
const text = `hello_${Date.now()}`;
|
|
||||||
proxySendMessage({ type: 'echo', messageId: `echo_${Date.now()}`, data: { text } });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAppStateChange = (nextAppState: AppStateStatus) => {
|
|
||||||
if (nextAppState === 'background' || nextAppState === 'inactive') {
|
|
||||||
console.log('[AppState] 应用进入后台,代理服务继续运行');
|
|
||||||
} else if (nextAppState === 'active') {
|
|
||||||
console.log('[AppState] 应用回到前台');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setupPermissions() {
|
|
||||||
const hasSms = await checkSmsPermission();
|
|
||||||
if (!hasSms) await requestSmsPermission();
|
|
||||||
|
|
||||||
startSmsListener();
|
|
||||||
|
|
||||||
onSmsMessage((msg: SmsMessage) => {
|
|
||||||
console.log('[SMS]', msg.address, msg.body);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async startProxyClient() {
|
|
||||||
try {
|
|
||||||
this.clientId = DeviceInfo.getUniqueIdSync();
|
|
||||||
const userId = Api.instance.getUserId();
|
|
||||||
const { clientId } = this;
|
|
||||||
console.log('[Proxy] 初始化后台服务:', clientId, 'userId:', userId);
|
|
||||||
|
|
||||||
this.setState({ proxyStatus: 'connecting' });
|
|
||||||
await proxyBackgroundService.start({
|
|
||||||
wsUrl: Api.WS_URL,
|
|
||||||
clientId: this.clientId || '',
|
|
||||||
userId: userId,
|
|
||||||
debug: true,
|
|
||||||
heartbeatInterval: 10000,
|
|
||||||
reconnectInterval: 5000,
|
|
||||||
reconnectMaxAttempts: Infinity,
|
|
||||||
onConnected: () => {
|
|
||||||
console.log('[Proxy] 后台服务已连接');
|
|
||||||
this.setState({ proxyStatus: 'connected' });
|
|
||||||
},
|
|
||||||
onDisconnected: () => {
|
|
||||||
console.log('[Proxy] 后台服务已断开');
|
|
||||||
this.setState({ proxyStatus: 'disconnected' });
|
|
||||||
},
|
|
||||||
onError: (error: string) => {
|
|
||||||
console.log('[Proxy] 错误:', error);
|
|
||||||
this.setState({ proxyStatus: 'error', proxyError: error });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Proxy] 初始化失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stopProxyClient() {
|
|
||||||
try {
|
|
||||||
proxyBackgroundService.stop();
|
|
||||||
console.log('[Proxy] 后台服务已停止');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Proxy] 停止失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paytm Personal
|
|
||||||
handleUploadPaytmPersonalToken = async (result: PaytmPersonalBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log(result);
|
|
||||||
await Api.instance.register(WalletType.PAYTM_PERSONAL, result);
|
|
||||||
this.setState({ showPaytmPersonalBind: false });
|
|
||||||
Alert.alert('绑定成功', 'Paytm Personal Token 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showPaytmPersonalBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PhonePe Personal
|
|
||||||
handleUploadPhonePePersonalToken = async (result: PhonePePersonalBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log('PhonePe Personal Token Data:', result);
|
|
||||||
await Api.instance.register(WalletType.PHONEPE_PERSONAL, result);
|
|
||||||
this.setState({ showPhonePePersonalBind: false });
|
|
||||||
Alert.alert('绑定成功', 'PhonePe Personal Token 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showPhonePePersonalBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paytm Business
|
|
||||||
handleUploadPaytmBusiness = async (result: PaytmBusinessBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log(result);
|
|
||||||
this.setState({ showPaytmBusinessBind: false });
|
|
||||||
Alert.alert('绑定成功', 'Paytm Business 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showPaytmBusinessBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PhonePe Business
|
|
||||||
handleUploadPhonePeBusiness = async (result: PhonePeBusinessBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log(result);
|
|
||||||
await Api.instance.register(WalletType.PHONEPE_BUSINESS, result);
|
|
||||||
this.setState({ showPhonePeBusinessBind: false });
|
|
||||||
Alert.alert('绑定成功', 'PhonePe Business 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showPhonePeBusinessBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GooglePay Business
|
|
||||||
handleUploadGooglePayBusiness = async (result: GooglePayBusinessBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log(result);
|
|
||||||
await Api.instance.register(WalletType.GOOGLEPAY_BUSINESS, result);
|
|
||||||
this.setState({ showGooglePayBusinessBind: false });
|
|
||||||
Alert.alert('绑定成功', 'Google Pay Business 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showGooglePayBusinessBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BharatPe Business
|
|
||||||
handleUploadBharatPeBusiness = async (result: BharatPeBusinessBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log(JSON.stringify(result));
|
|
||||||
this.setState({ showBharatPeBusinessBind: false });
|
|
||||||
Alert.alert('绑定成功', 'BharatPe Business 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showBharatPeBusinessBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobikwik Personal
|
|
||||||
handleUploadMobikwikPersonal = async (result: MobikwikPersonalBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log(JSON.stringify(result));
|
|
||||||
this.setState({ showMobikwikPersonalBind: false });
|
|
||||||
Alert.alert('绑定成功', 'Mobikwik Personal 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showMobikwikPersonalBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Freecharge Personal
|
|
||||||
handleUploadFreechargePersonal = async (result: FreechargePersonalBindResult) => {
|
|
||||||
try {
|
|
||||||
console.log(JSON.stringify(result));
|
|
||||||
this.setState({ showFreechargePersonalBind: false });
|
|
||||||
Alert.alert('绑定成功', 'Freecharge Personal 绑定成功');
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert('绑定失败', (error as Error).message);
|
|
||||||
this.setState({ showFreechargePersonalBind: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPaytmPersonalTokenBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmPersonalBind: false })}>
|
|
||||||
<PaytmPersonalBind
|
|
||||||
processString="Processing..."
|
|
||||||
isDebug={true}
|
|
||||||
onSuccess={this.handleUploadPaytmPersonalToken}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showPaytmPersonalBind: false });
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
</Modal>
|
<Tab.Screen
|
||||||
);
|
name="Home"
|
||||||
}
|
component={HomeScreen}
|
||||||
|
options={{
|
||||||
renderPaytmPersonalOTPBind = () => {
|
title: '首页',
|
||||||
return (
|
tabBarLabel: '首页',
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmPersonalBind: false })}>
|
tabBarIcon: ({ color, size }) => (
|
||||||
<PayTmPersonalOTPBind
|
<Text style={{ fontSize: size, color }}>🏠</Text>
|
||||||
isDebug={true}
|
),
|
||||||
onRequestOTP={async (walletType, params) => {
|
}}
|
||||||
try {
|
/>
|
||||||
return await Api.instance.requestOTP(walletType, params.mobile, {});
|
<Tab.Screen
|
||||||
} catch (error) {
|
name="Message"
|
||||||
return { success: false, message: (error as Error).message };
|
component={MessageScreen}
|
||||||
}
|
options={{
|
||||||
}}
|
title: '消息',
|
||||||
onVerifyOTP={async (walletType, params) => {
|
tabBarLabel: '消息',
|
||||||
try {
|
tabBarIcon: ({ color, size }) => (
|
||||||
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
<Text style={{ fontSize: size, color }}>💬</Text>
|
||||||
sessionId: params.sessionId,
|
),
|
||||||
});
|
}}
|
||||||
} catch (error) {
|
/>
|
||||||
return { success: false, message: (error as Error).message };
|
</Tab.Navigator>
|
||||||
}
|
</NavigationContainer>
|
||||||
}}
|
</SafeAreaProvider>
|
||||||
onSuccess={(result: PaytmPersonalBindResult) => {
|
);
|
||||||
console.log('[PaytmPersonal] OTP 绑定成功:', result);
|
|
||||||
Alert.alert('绑定成功', 'Paytm Personal OTP 绑定成功');
|
|
||||||
this.setState({ showPaytmPersonalBind: false });
|
|
||||||
}}
|
|
||||||
onError={(error: string) => {
|
|
||||||
console.log('[PaytmPersonal] OTP 绑定失败:', error);
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showPaytmPersonalBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPhonePePersonalTokenBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePePersonalBind: false })}>
|
|
||||||
<PhonePePersonalBind
|
|
||||||
processString="Processing..."
|
|
||||||
isDebug={true}
|
|
||||||
onSuccess={this.handleUploadPhonePePersonalToken}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showPhonePePersonalBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPhonePePersonalOTPBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePePersonalBind: false })}>
|
|
||||||
<PhonePePersonalOTPBind
|
|
||||||
isDebug={true}
|
|
||||||
onRequestOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.requestOTP(walletType, params.mobile, {});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onVerifyOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
|
||||||
sessionId: params.sessionId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSuccess={(result: PhonePePersonalBindResult) => {
|
|
||||||
console.log('[PhonePePersonal] OTP 绑定成功:', result);
|
|
||||||
Alert.alert('绑定成功', 'PhonePe Personal OTP 绑定成功');
|
|
||||||
this.setState({ showPhonePePersonalBind: false });
|
|
||||||
}}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showPhonePePersonalBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPaytmBusinessBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmBusinessBind: false })}>
|
|
||||||
<PaytmBusinessOTPBind
|
|
||||||
isDebug={true}
|
|
||||||
onRequestOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.requestOTP(walletType, params.mobile, { password: params.password });
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onVerifyOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
|
||||||
sessionId: params.sessionId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSuccess={this.handleUploadPaytmBusiness}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showPaytmBusinessBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPhonePeBusinessBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePeBusinessBind: false })}>
|
|
||||||
<PhonePeBusinessBind
|
|
||||||
processString="Processing..."
|
|
||||||
isDebug={true}
|
|
||||||
onSuccess={this.handleUploadPhonePeBusiness}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showPhonePeBusinessBind: false });
|
|
||||||
}}
|
|
||||||
onRenderBottomView={({ showOtpInput, loading, formError, phone, otp, onPhoneChange, onOtpChange, onGetOtp, onSubmitOtp }) => (
|
|
||||||
<View style={{ position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: '#fff', padding: 16, borderTopWidth: 1, borderTopColor: '#e0e0e0', gap: 10 }}>
|
|
||||||
{!showOtpInput ? (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#333' }}
|
|
||||||
placeholder="Mobile Number"
|
|
||||||
placeholderTextColor="#999"
|
|
||||||
keyboardType="phone-pad"
|
|
||||||
value={phone}
|
|
||||||
onChangeText={onPhoneChange}
|
|
||||||
editable={!loading}
|
|
||||||
/>
|
|
||||||
{!!formError && <Text style={{ color: '#e53935', fontSize: 13 }}>{formError}</Text>}
|
|
||||||
<TouchableOpacity style={{ backgroundColor: '#5a2d9c', borderRadius: 8, paddingVertical: 12, alignItems: 'center', opacity: loading ? 0.5 : 1 }} onPress={onGetOtp} disabled={loading}>
|
|
||||||
<Text style={{ color: '#fff', fontSize: 15, fontWeight: '600' }}>{loading ? 'Loading...' : 'GET OTP'}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<TextInput
|
|
||||||
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#333' }}
|
|
||||||
placeholder="OTP"
|
|
||||||
placeholderTextColor="#999"
|
|
||||||
keyboardType="number-pad"
|
|
||||||
value={otp}
|
|
||||||
onChangeText={onOtpChange}
|
|
||||||
editable={!loading}
|
|
||||||
/>
|
|
||||||
{!!formError && <Text style={{ color: '#e53935', fontSize: 13 }}>{formError}</Text>}
|
|
||||||
<TouchableOpacity style={{ backgroundColor: '#5a2d9c', borderRadius: 8, paddingVertical: 12, alignItems: 'center', opacity: loading ? 0.5 : 1 }} onPress={onSubmitOtp} disabled={loading}>
|
|
||||||
<Text style={{ color: '#fff', fontSize: 15, fontWeight: '600' }}>{loading ? 'Loading...' : 'Verify OTP'}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderGooglePayBusinessBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showGooglePayBusinessBind: false })}>
|
|
||||||
<GooglePayBusinessBind
|
|
||||||
processString="Processing..."
|
|
||||||
isDebug={true}
|
|
||||||
onSuccess={this.handleUploadGooglePayBusiness}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showGooglePayBusinessBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderBharatPeBusinessBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showBharatPeBusinessBind: false })}>
|
|
||||||
<BharatPeBusinessOTPBind
|
|
||||||
isDebug={true}
|
|
||||||
onRequestOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.requestOTP(walletType, params.mobile);
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onVerifyOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
|
||||||
sessionId: params.sessionId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSuccess={this.handleUploadBharatPeBusiness}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showBharatPeBusinessBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMobikwikPersonalOTPBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showMobikwikPersonalBind: false })}>
|
|
||||||
<MobikwikOTPBind
|
|
||||||
isDebug={true}
|
|
||||||
deviceId={this.deviceId}
|
|
||||||
tuneUserId={this.tuneUserId}
|
|
||||||
onRequestOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.requestOTP(walletType, params.mobile, {
|
|
||||||
deviceId: params.deviceId,
|
|
||||||
tuneUserId: params.tuneUserId
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onVerifyOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
|
||||||
deviceId: params.deviceId,
|
|
||||||
tuneUserId: params.tuneUserId,
|
|
||||||
nid: params.nid
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSuccess={this.handleUploadMobikwikPersonal}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showMobikwikPersonalBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFreechargePersonalOTPBind = () => {
|
|
||||||
return (
|
|
||||||
<Modal visible transparent onRequestClose={() => this.setState({ showFreechargePersonalBind: false })}>
|
|
||||||
<FreeChargeBind
|
|
||||||
isDebug={true}
|
|
||||||
onRequestOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.requestOTP(walletType, params.mobile);
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onVerifyOTP={async (walletType, params) => {
|
|
||||||
try {
|
|
||||||
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
|
||||||
otpId: params.otpId,
|
|
||||||
deviceId: params.deviceId,
|
|
||||||
csrfId: params.csrfId,
|
|
||||||
appFc: params.appFc
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, message: (error as Error).message };
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSuccess={this.handleUploadFreechargePersonal}
|
|
||||||
onError={(error: string) => {
|
|
||||||
Alert.alert('绑定失败', error);
|
|
||||||
this.setState({ showFreechargePersonalBind: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderBindModal = () => {
|
|
||||||
// Paytm Personal
|
|
||||||
if (this.state.showPaytmPersonalBind) {
|
|
||||||
if (this.state.paytmPersonalBindType === 'tokenMode') {
|
|
||||||
return this.renderPaytmPersonalTokenBind();
|
|
||||||
} else {
|
|
||||||
return this.renderPaytmPersonalOTPBind ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PhonePe Personal
|
|
||||||
if (this.state.showPhonePePersonalBind) {
|
|
||||||
if (this.state.phonePePersonalBindType === 'tokenMode') {
|
|
||||||
return this.renderPhonePePersonalTokenBind();
|
|
||||||
} else {
|
|
||||||
return this.renderPhonePePersonalOTPBind ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paytm Business
|
|
||||||
if (this.state.showPaytmBusinessBind) {
|
|
||||||
return this.renderPaytmBusinessBind ();
|
|
||||||
}
|
|
||||||
|
|
||||||
// PhonePe Business
|
|
||||||
if (this.state.showPhonePeBusinessBind) {
|
|
||||||
return this.renderPhonePeBusinessBind ();
|
|
||||||
}
|
|
||||||
|
|
||||||
// GooglePay Business
|
|
||||||
if (this.state.showGooglePayBusinessBind) {
|
|
||||||
return this.renderGooglePayBusinessBind ();
|
|
||||||
}
|
|
||||||
|
|
||||||
// BharatPe Business
|
|
||||||
if (this.state.showBharatPeBusinessBind) {
|
|
||||||
return this.renderBharatPeBusinessBind ();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobikwik Personal
|
|
||||||
if (this.state.showMobikwikPersonalBind) {
|
|
||||||
return this.renderMobikwikPersonalOTPBind ();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Freecharge Personal
|
|
||||||
if (this.state.showFreechargePersonalBind) {
|
|
||||||
return this.renderFreechargePersonalOTPBind ();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
openServerSettings = () => {
|
|
||||||
const domain = getServerDomain();
|
|
||||||
const colonIdx = domain.lastIndexOf(':');
|
|
||||||
const host = colonIdx > 0 ? domain.substring(0, colonIdx) : domain;
|
|
||||||
const port = colonIdx > 0 ? domain.substring(colonIdx + 1) : '';
|
|
||||||
this.setState({ showServerSettings: true, settingsHost: host, settingsPort: port });
|
|
||||||
};
|
|
||||||
|
|
||||||
saveDomain = async () => {
|
|
||||||
const { settingsHost, settingsPort } = this.state;
|
|
||||||
const domain = settingsPort ? `${settingsHost}:${settingsPort}` : settingsHost;
|
|
||||||
const useHttps = settingsPort === '443';
|
|
||||||
await saveServerDomain(domain, useHttps);
|
|
||||||
this.setState({ showServerSettings: false });
|
|
||||||
Alert.alert('已保存', '重启 App 后生效');
|
|
||||||
};
|
|
||||||
|
|
||||||
renderServerSettingsModal() {
|
|
||||||
const { showServerSettings, settingsHost, settingsPort } = this.state;
|
|
||||||
const presets = [
|
|
||||||
{ label: 'aa.pfgame.org', host: 'aa.pfgame.org', port: '443' },
|
|
||||||
{ label: 'game.ainavx.com:16000', host: 'game.ainavx.com', port: '16000' },
|
|
||||||
{ label: '192.168.1.117:16000', host: '192.168.1.117', port: '16000' },
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<Modal visible={showServerSettings} transparent animationType="fade">
|
|
||||||
<View style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center' }}>
|
|
||||||
<View style={{ backgroundColor: '#fff', borderRadius: 10, padding: 20, width: '85%' }}>
|
|
||||||
<Text style={{ fontSize: 16, fontWeight: 'bold', marginBottom: 12 }}>服务器设置</Text>
|
|
||||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginBottom: 14 }}>
|
|
||||||
{presets.map(p => (
|
|
||||||
<TouchableOpacity
|
|
||||||
key={p.label}
|
|
||||||
onPress={() => this.setState({ settingsHost: p.host, settingsPort: p.port })}
|
|
||||||
style={{ paddingHorizontal: 10, paddingVertical: 5, borderRadius: 6, backgroundColor: settingsHost === p.host && settingsPort === p.port ? '#3498db' : '#f0f0f0' }}
|
|
||||||
>
|
|
||||||
<Text style={{ fontSize: 12, color: settingsHost === p.host && settingsPort === p.port ? '#fff' : '#333' }}>{p.label}</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
<Text style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>Host</Text>
|
|
||||||
<TextInput
|
|
||||||
style={{ borderWidth: 1, borderColor: '#ddd', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 8, marginBottom: 12, fontSize: 14 }}
|
|
||||||
value={settingsHost}
|
|
||||||
onChangeText={t => this.setState({ settingsHost: t })}
|
|
||||||
placeholder="192.168.1.198"
|
|
||||||
autoCapitalize="none"
|
|
||||||
keyboardType="default"
|
|
||||||
/>
|
|
||||||
<Text style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>Port</Text>
|
|
||||||
<TextInput
|
|
||||||
style={{ borderWidth: 1, borderColor: '#ddd', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 8, marginBottom: 20, fontSize: 14 }}
|
|
||||||
value={settingsPort}
|
|
||||||
onChangeText={t => this.setState({ settingsPort: t })}
|
|
||||||
placeholder="16000"
|
|
||||||
keyboardType="number-pad"
|
|
||||||
/>
|
|
||||||
<View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
|
|
||||||
<TouchableOpacity onPress={() => this.setState({ showServerSettings: false })} style={{ paddingHorizontal: 16, paddingVertical: 8, marginRight: 10 }}>
|
|
||||||
<Text style={{ color: '#666' }}>取消</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity onPress={this.saveDomain} style={{ backgroundColor: '#3498db', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 6 }}>
|
|
||||||
<Text style={{ color: '#fff', fontWeight: 'bold' }}>保存</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
{(() => {
|
|
||||||
const { proxyStatus, proxyError } = this.state;
|
|
||||||
const cfg: Record<string, { label: string; color: string }> = {
|
|
||||||
idle: { label: '未连接', color: '#95a5a6' },
|
|
||||||
connecting: { label: '连接中…', color: '#f39c12' },
|
|
||||||
connected: { label: '已连接', color: '#2ecc71' },
|
|
||||||
disconnected: { label: '已断开', color: '#e74c3c' },
|
|
||||||
error: { label: '连接失败', color: '#e74c3c' },
|
|
||||||
};
|
|
||||||
const { label, color } = cfg[proxyStatus];
|
|
||||||
return (
|
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 6, paddingHorizontal: 12 }}>
|
|
||||||
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: color, marginRight: 6 }} />
|
|
||||||
<Text style={{ color, fontSize: 13 }}>
|
|
||||||
Proxy {label}{proxyStatus === 'error' && proxyError ? `:${proxyError}` : ''}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
{this.renderBindModal()}
|
|
||||||
{this.renderServerSettingsModal()}
|
|
||||||
<TouchableOpacity onPress={this.openServerSettings} style={{ alignSelf: 'flex-end', paddingHorizontal: 12, paddingVertical: 4 }}>
|
|
||||||
<Text style={{ fontSize: 12, color: '#3498db' }}>⚙ 服务器设置</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={[styles.bindButton, { backgroundColor: "transparent", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }]}>
|
|
||||||
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
|
||||||
this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'otpMode' });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 Paytm Personal(OTP)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
|
||||||
this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'tokenMode' });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 Paytm Personal(Token)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
<TouchableOpacity style={styles.bindButton} onPress={() => {
|
|
||||||
this.setState({ showPaytmBusinessBind: true });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 Paytm Business (OTP)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={[styles.bindButton, { backgroundColor: "transparent", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }]}>
|
|
||||||
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
|
||||||
this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'otpMode' });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 PhonePe Personal(OTP)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
|
||||||
this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 PhonePe Personal(Token)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
<TouchableOpacity style={styles.bindButton} onPress={() => {
|
|
||||||
this.setState({ showPhonePeBusinessBind: true });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 PhonePe Business (OTP-WEB)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={styles.bindButton} onPress={() => {
|
|
||||||
this.setState({ showGooglePayBusinessBind: true });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 GooglePay Business</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={styles.bindButton} onPress={() => {
|
|
||||||
this.setState({ showBharatPeBusinessBind: true });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 BharatPe Business (OTP)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={styles.bindButton} onPress={() => {
|
|
||||||
this.setState({ showMobikwikPersonalBind: true });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 Mobikwik Personal (OTP)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={styles.bindButton} onPress={() => {
|
|
||||||
this.setState({ showFreechargePersonalBind: true });
|
|
||||||
}}>
|
|
||||||
<Text style={styles.bindButtonText}>绑定 Freecharge Personal (OTP)</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity style={[styles.bindButton, { backgroundColor: '#2ecc71' }]} onPress={this.sendEcho}>
|
|
||||||
<Text style={styles.bindButtonText}>Echo 测试</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
3
android/.idea/gradle.xml
generated
3
android/.idea/gradle.xml
generated
@@ -29,6 +29,9 @@
|
|||||||
<option value="$PROJECT_DIR$/../node_modules/react-native-background-actions/android" />
|
<option value="$PROJECT_DIR$/../node_modules/react-native-background-actions/android" />
|
||||||
<option value="$PROJECT_DIR$/../node_modules/react-native-device-info/android" />
|
<option value="$PROJECT_DIR$/../node_modules/react-native-device-info/android" />
|
||||||
<option value="$PROJECT_DIR$/../node_modules/react-native-fs/android" />
|
<option value="$PROJECT_DIR$/../node_modules/react-native-fs/android" />
|
||||||
|
<option value="$PROJECT_DIR$/../node_modules/react-native-gesture-handler/android" />
|
||||||
|
<option value="$PROJECT_DIR$/../node_modules/react-native-safe-area-context/android" />
|
||||||
|
<option value="$PROJECT_DIR$/../node_modules/react-native-screens/android" />
|
||||||
<option value="$PROJECT_DIR$/../node_modules/react-native-tcp-socket/android" />
|
<option value="$PROJECT_DIR$/../node_modules/react-native-tcp-socket/android" />
|
||||||
<option value="$PROJECT_DIR$/../node_modules/react-native-webview/android" />
|
<option value="$PROJECT_DIR$/../node_modules/react-native-webview/android" />
|
||||||
<option value="$PROJECT_DIR$/../node_modules/rnauto/android" />
|
<option value="$PROJECT_DIR$/../node_modules/rnauto/android" />
|
||||||
|
|||||||
@@ -14,12 +14,17 @@
|
|||||||
"@react-native-async-storage/async-storage": "^1.23.1",
|
"@react-native-async-storage/async-storage": "^1.23.1",
|
||||||
"@react-native-cookies/cookies": "^6.2.1",
|
"@react-native-cookies/cookies": "^6.2.1",
|
||||||
"@react-native-ml-kit/barcode-scanning": "^2.0.0",
|
"@react-native-ml-kit/barcode-scanning": "^2.0.0",
|
||||||
|
"@react-navigation/bottom-tabs": "^7.2.1",
|
||||||
|
"@react-navigation/native": "^7.0.14",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-native": "0.72.10",
|
"react-native": "0.72.10",
|
||||||
"react-native-background-actions": "^4.0.1",
|
"react-native-background-actions": "^4.0.1",
|
||||||
"react-native-device-info": "14.0.4",
|
"react-native-device-info": "14.0.4",
|
||||||
"react-native-fs": "^2.20.0",
|
"react-native-fs": "^2.20.0",
|
||||||
|
"react-native-gesture-handler": "~2.9.0",
|
||||||
|
"react-native-safe-area-context": "^4.4.1",
|
||||||
|
"react-native-screens": "~3.36.0",
|
||||||
"react-native-tcp-socket": "^6.4.1",
|
"react-native-tcp-socket": "^6.4.1",
|
||||||
"react-native-webview": "13.6.2",
|
"react-native-webview": "13.6.2",
|
||||||
"rnauto": "./libs/rnauto",
|
"rnauto": "./libs/rnauto",
|
||||||
|
|||||||
24
patches/react-native-gesture-handler+2.9.0.patch
Normal file
24
patches/react-native-gesture-handler+2.9.0.patch
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
diff --git a/node_modules/react-native-gesture-handler/android/build.gradle b/node_modules/react-native-gesture-handler/android/build.gradle
|
||||||
|
index 20f41ea..2081d2f 100644
|
||||||
|
--- a/node_modules/react-native-gesture-handler/android/build.gradle
|
||||||
|
+++ b/node_modules/react-native-gesture-handler/android/build.gradle
|
||||||
|
@@ -5,6 +5,9 @@ buildscript {
|
||||||
|
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['RNGH_kotlinVersion']
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
|
||||||
|
+ maven { url 'https://repo.huaweicloud.com/repository/maven/' }
|
||||||
|
maven {
|
||||||
|
url "https://plugins.gradle.org/m2/"
|
||||||
|
}
|
||||||
|
@@ -127,6 +130,9 @@ tasks.preBuild {
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
|
||||||
|
+ maven { url 'https://repo.huaweicloud.com/repository/maven/' }
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
24
patches/react-native-safe-area-context+4.14.1.patch
Normal file
24
patches/react-native-safe-area-context+4.14.1.patch
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
diff --git a/node_modules/react-native-safe-area-context/android/build.gradle b/node_modules/react-native-safe-area-context/android/build.gradle
|
||||||
|
index f324b9a..55bcb4d 100644
|
||||||
|
--- a/node_modules/react-native-safe-area-context/android/build.gradle
|
||||||
|
+++ b/node_modules/react-native-safe-area-context/android/build.gradle
|
||||||
|
@@ -2,6 +2,9 @@ buildscript {
|
||||||
|
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['RNSAC_kotlinVersion']
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
|
||||||
|
+ maven { url 'https://repo.huaweicloud.com/repository/maven/' }
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
@@ -108,6 +111,9 @@ def reactNativeArchitectures() {
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
|
+ maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
|
||||||
|
+ maven { url 'https://repo.huaweicloud.com/repository/maven/' }
|
||||||
|
google()
|
||||||
|
maven {
|
||||||
|
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||||
21
patches/react-native-screens+3.36.0.patch
Normal file
21
patches/react-native-screens+3.36.0.patch
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt
|
||||||
|
index 644f017..96e00c8 100644
|
||||||
|
--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt
|
||||||
|
+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
package com.swmansion.rnscreens
|
||||||
|
|
||||||
|
-import com.facebook.react.BaseReactPackage
|
||||||
|
+import com.facebook.react.TurboReactPackage
|
||||||
|
import com.facebook.react.bridge.NativeModule
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
|
import com.facebook.react.module.annotations.ReactModuleList
|
||||||
|
@@ -16,7 +16,7 @@ import com.swmansion.rnscreens.utils.ScreenDummyLayoutHelper
|
||||||
|
ScreensModule::class,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
-class RNScreensPackage : BaseReactPackage() {
|
||||||
|
+class RNScreensPackage : TurboReactPackage() {
|
||||||
|
// We just retain it here. This object helps us tackle jumping content when using native header.
|
||||||
|
// See: https://github.com/software-mansion/react-native-screens/pull/2169
|
||||||
|
private var screenDummyLayoutHelper: ScreenDummyLayoutHelper? = null
|
||||||
756
screens/HomeScreen.tsx
Normal file
756
screens/HomeScreen.tsx
Normal file
@@ -0,0 +1,756 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import { Alert, AppState, AppStateStatus, Modal, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
|
||||||
|
import DeviceInfo from 'react-native-device-info';
|
||||||
|
import {
|
||||||
|
PhonePeBusinessBind,
|
||||||
|
GooglePayBusinessBind,
|
||||||
|
WalletType,
|
||||||
|
PaytmBusinessBindResult,
|
||||||
|
PhonePeBusinessBindResult,
|
||||||
|
PaytmPersonalBind,
|
||||||
|
PaytmPersonalBindResult,
|
||||||
|
MobikwikPersonalBindResult,
|
||||||
|
FreechargePersonalBindResult,
|
||||||
|
GooglePayBusinessBindResult,
|
||||||
|
BharatPeBusinessBindResult,
|
||||||
|
onSmsMessage,
|
||||||
|
startSmsListener,
|
||||||
|
stopSmsListener,
|
||||||
|
checkSmsPermission,
|
||||||
|
requestSmsPermission,
|
||||||
|
PhonePePersonalBindResult,
|
||||||
|
PhonePePersonalBind,
|
||||||
|
SmsMessage,
|
||||||
|
proxyBackgroundService,
|
||||||
|
proxySendMessage,
|
||||||
|
onProxyMessage,
|
||||||
|
} from "rnwalletman";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FreeChargeBind,
|
||||||
|
MobikwikOTPBind,
|
||||||
|
PayTmPersonalOTPBind,
|
||||||
|
PhonePePersonalOTPBind,
|
||||||
|
BharatPeBusinessOTPBind,
|
||||||
|
PaytmBusinessOTPBind,
|
||||||
|
} from '../components/WalletBindComponents';
|
||||||
|
|
||||||
|
import Api, { loadServerDomain, saveServerDomain, getServerDomain } from '../services/api';
|
||||||
|
import { WalletmanAppState } from '../types';
|
||||||
|
|
||||||
|
interface HomeScreenState extends WalletmanAppState {}
|
||||||
|
|
||||||
|
export default class HomeScreen extends Component<any, HomeScreenState> {
|
||||||
|
private deviceId: string;
|
||||||
|
private tuneUserId: string;
|
||||||
|
private clientId: string = '';
|
||||||
|
private appStateSubscription?: any;
|
||||||
|
private onProxyMessageSub?: ReturnType<typeof onProxyMessage>;
|
||||||
|
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
paytmPersonalBindType: 'otpMode',
|
||||||
|
showPaytmPersonalBind: false,
|
||||||
|
showPaytmBusinessBind: false,
|
||||||
|
showPhonePePersonalBind: false,
|
||||||
|
phonePePersonalBindType: 'otpMode',
|
||||||
|
showPhonePeBusinessBind: false,
|
||||||
|
showGooglePayBusinessBind: false,
|
||||||
|
showBharatPeBusinessBind: false,
|
||||||
|
showMobikwikPersonalBind: false,
|
||||||
|
showFreechargePersonalBind: false,
|
||||||
|
proxyStatus: 'idle',
|
||||||
|
showServerSettings: false,
|
||||||
|
settingsHost: '',
|
||||||
|
settingsPort: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.deviceId = DeviceInfo.getUniqueIdSync();
|
||||||
|
this.tuneUserId = Math.random().toString(36).substring(2, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
await loadServerDomain();
|
||||||
|
await this.setupPermissions();
|
||||||
|
|
||||||
|
this.onProxyMessageSub = onProxyMessage((msg) => {
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'echo':
|
||||||
|
Alert.alert('Echo 回来了', JSON.stringify(msg.data));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const doLogin = () => {
|
||||||
|
Api.instance.login('test123', '123456')
|
||||||
|
.then(async (userId) => {
|
||||||
|
console.log('[Login] userId:', userId);
|
||||||
|
await this.startProxyClient();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('[Login] retry in 3s:', error);
|
||||||
|
setTimeout(doLogin, 3000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
doLogin();
|
||||||
|
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.stopProxyClient();
|
||||||
|
stopSmsListener();
|
||||||
|
this.onProxyMessageSub?.remove();
|
||||||
|
this.appStateSubscription?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEcho = () => {
|
||||||
|
const text = `hello_${Date.now()}`;
|
||||||
|
proxySendMessage({ type: 'echo', messageId: `echo_${Date.now()}`, data: { text } });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleAppStateChange = (nextAppState: AppStateStatus) => {
|
||||||
|
if (nextAppState === 'background' || nextAppState === 'inactive') {
|
||||||
|
console.log('[AppState] 应用进入后台,代理服务继续运行');
|
||||||
|
} else if (nextAppState === 'active') {
|
||||||
|
console.log('[AppState] 应用回到前台');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setupPermissions() {
|
||||||
|
const hasSms = await checkSmsPermission();
|
||||||
|
if (!hasSms) await requestSmsPermission();
|
||||||
|
startSmsListener();
|
||||||
|
onSmsMessage((msg: SmsMessage) => {
|
||||||
|
console.log('[SMS]', msg.address, msg.body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async startProxyClient() {
|
||||||
|
try {
|
||||||
|
this.clientId = DeviceInfo.getUniqueIdSync();
|
||||||
|
const userId = Api.instance.getUserId();
|
||||||
|
console.log('[Proxy] 初始化后台服务:', this.clientId, 'userId:', userId);
|
||||||
|
this.setState({ proxyStatus: 'connecting' });
|
||||||
|
await proxyBackgroundService.start({
|
||||||
|
wsUrl: Api.WS_URL,
|
||||||
|
clientId: this.clientId || '',
|
||||||
|
userId: userId,
|
||||||
|
debug: true,
|
||||||
|
heartbeatInterval: 10000,
|
||||||
|
reconnectInterval: 5000,
|
||||||
|
reconnectMaxAttempts: Infinity,
|
||||||
|
onConnected: () => {
|
||||||
|
console.log('[Proxy] 后台服务已连接');
|
||||||
|
this.setState({ proxyStatus: 'connected' });
|
||||||
|
},
|
||||||
|
onDisconnected: () => {
|
||||||
|
console.log('[Proxy] 后台服务已断开');
|
||||||
|
this.setState({ proxyStatus: 'disconnected' });
|
||||||
|
},
|
||||||
|
onError: (error: string) => {
|
||||||
|
console.log('[Proxy] 错误:', error);
|
||||||
|
this.setState({ proxyStatus: 'error', proxyError: error });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Proxy] 初始化失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopProxyClient() {
|
||||||
|
try {
|
||||||
|
proxyBackgroundService.stop();
|
||||||
|
console.log('[Proxy] 后台服务已停止');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Proxy] 停止失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadPaytmPersonalToken = async (result: PaytmPersonalBindResult) => {
|
||||||
|
try {
|
||||||
|
await Api.instance.register(WalletType.PAYTM_PERSONAL, result);
|
||||||
|
this.setState({ showPaytmPersonalBind: false });
|
||||||
|
Alert.alert('绑定成功', 'Paytm Personal Token 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showPaytmPersonalBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadPhonePePersonalToken = async (result: PhonePePersonalBindResult) => {
|
||||||
|
try {
|
||||||
|
await Api.instance.register(WalletType.PHONEPE_PERSONAL, result);
|
||||||
|
this.setState({ showPhonePePersonalBind: false });
|
||||||
|
Alert.alert('绑定成功', 'PhonePe Personal Token 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showPhonePePersonalBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadPaytmBusiness = async (result: PaytmBusinessBindResult) => {
|
||||||
|
try {
|
||||||
|
console.log(result);
|
||||||
|
this.setState({ showPaytmBusinessBind: false });
|
||||||
|
Alert.alert('绑定成功', 'Paytm Business 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showPaytmBusinessBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadPhonePeBusiness = async (result: PhonePeBusinessBindResult) => {
|
||||||
|
try {
|
||||||
|
await Api.instance.register(WalletType.PHONEPE_BUSINESS, result);
|
||||||
|
this.setState({ showPhonePeBusinessBind: false });
|
||||||
|
Alert.alert('绑定成功', 'PhonePe Business 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showPhonePeBusinessBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadGooglePayBusiness = async (result: GooglePayBusinessBindResult) => {
|
||||||
|
try {
|
||||||
|
await Api.instance.register(WalletType.GOOGLEPAY_BUSINESS, result);
|
||||||
|
this.setState({ showGooglePayBusinessBind: false });
|
||||||
|
Alert.alert('绑定成功', 'Google Pay Business 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showGooglePayBusinessBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadBharatPeBusiness = async (result: BharatPeBusinessBindResult) => {
|
||||||
|
try {
|
||||||
|
console.log(JSON.stringify(result));
|
||||||
|
this.setState({ showBharatPeBusinessBind: false });
|
||||||
|
Alert.alert('绑定成功', 'BharatPe Business 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showBharatPeBusinessBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadMobikwikPersonal = async (result: MobikwikPersonalBindResult) => {
|
||||||
|
try {
|
||||||
|
console.log(JSON.stringify(result));
|
||||||
|
this.setState({ showMobikwikPersonalBind: false });
|
||||||
|
Alert.alert('绑定成功', 'Mobikwik Personal 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showMobikwikPersonalBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUploadFreechargePersonal = async (result: FreechargePersonalBindResult) => {
|
||||||
|
try {
|
||||||
|
console.log(JSON.stringify(result));
|
||||||
|
this.setState({ showFreechargePersonalBind: false });
|
||||||
|
Alert.alert('绑定成功', 'Freecharge Personal 绑定成功');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('绑定失败', (error as Error).message);
|
||||||
|
this.setState({ showFreechargePersonalBind: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPaytmPersonalTokenBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmPersonalBind: false })}>
|
||||||
|
<PaytmPersonalBind
|
||||||
|
processString="Processing..."
|
||||||
|
isDebug={true}
|
||||||
|
onSuccess={this.handleUploadPaytmPersonalToken}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showPaytmPersonalBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderPaytmPersonalOTPBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmPersonalBind: false })}>
|
||||||
|
<PayTmPersonalOTPBind
|
||||||
|
isDebug={true}
|
||||||
|
onRequestOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.requestOTP(walletType, params.mobile, {});
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onVerifyOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { sessionId: params.sessionId });
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSuccess={(result: PaytmPersonalBindResult) => {
|
||||||
|
Alert.alert('绑定成功', 'Paytm Personal OTP 绑定成功');
|
||||||
|
this.setState({ showPaytmPersonalBind: false });
|
||||||
|
}}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showPaytmPersonalBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderPhonePePersonalTokenBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePePersonalBind: false })}>
|
||||||
|
<PhonePePersonalBind
|
||||||
|
processString="Processing..."
|
||||||
|
isDebug={true}
|
||||||
|
onSuccess={this.handleUploadPhonePePersonalToken}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showPhonePePersonalBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderPhonePePersonalOTPBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePePersonalBind: false })}>
|
||||||
|
<PhonePePersonalOTPBind
|
||||||
|
isDebug={true}
|
||||||
|
onRequestOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.requestOTP(walletType, params.mobile, {});
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onVerifyOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { sessionId: params.sessionId });
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSuccess={(result: PhonePePersonalBindResult) => {
|
||||||
|
Alert.alert('绑定成功', 'PhonePe Personal OTP 绑定成功');
|
||||||
|
this.setState({ showPhonePePersonalBind: false });
|
||||||
|
}}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showPhonePePersonalBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderPaytmBusinessBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmBusinessBind: false })}>
|
||||||
|
<PaytmBusinessOTPBind
|
||||||
|
isDebug={true}
|
||||||
|
onRequestOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.requestOTP(walletType, params.mobile, { password: params.password });
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onVerifyOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { sessionId: params.sessionId });
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSuccess={this.handleUploadPaytmBusiness}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showPaytmBusinessBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderPhonePeBusinessBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePeBusinessBind: false })}>
|
||||||
|
<PhonePeBusinessBind
|
||||||
|
processString="Processing..."
|
||||||
|
isDebug={true}
|
||||||
|
onSuccess={this.handleUploadPhonePeBusiness}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showPhonePeBusinessBind: false });
|
||||||
|
}}
|
||||||
|
onRenderBottomView={({ showOtpInput, loading, formError, phone, otp, onPhoneChange, onOtpChange, onGetOtp, onSubmitOtp }) => (
|
||||||
|
<View style={{ position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: '#fff', padding: 16, borderTopWidth: 1, borderTopColor: '#e0e0e0', gap: 10 }}>
|
||||||
|
{!showOtpInput ? (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#333' }}
|
||||||
|
placeholder="Mobile Number"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
keyboardType="phone-pad"
|
||||||
|
value={phone}
|
||||||
|
onChangeText={onPhoneChange}
|
||||||
|
editable={!loading}
|
||||||
|
/>
|
||||||
|
{!!formError && <Text style={{ color: '#e53935', fontSize: 13 }}>{formError}</Text>}
|
||||||
|
<TouchableOpacity style={{ backgroundColor: '#5a2d9c', borderRadius: 8, paddingVertical: 12, alignItems: 'center', opacity: loading ? 0.5 : 1 }} onPress={onGetOtp} disabled={loading}>
|
||||||
|
<Text style={{ color: '#fff', fontSize: 15, fontWeight: '600' }}>{loading ? 'Loading...' : 'GET OTP'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
style={{ borderWidth: 1, borderColor: '#ccc', borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#333' }}
|
||||||
|
placeholder="OTP"
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
keyboardType="number-pad"
|
||||||
|
value={otp}
|
||||||
|
onChangeText={onOtpChange}
|
||||||
|
editable={!loading}
|
||||||
|
/>
|
||||||
|
{!!formError && <Text style={{ color: '#e53935', fontSize: 13 }}>{formError}</Text>}
|
||||||
|
<TouchableOpacity style={{ backgroundColor: '#5a2d9c', borderRadius: 8, paddingVertical: 12, alignItems: 'center', opacity: loading ? 0.5 : 1 }} onPress={onSubmitOtp} disabled={loading}>
|
||||||
|
<Text style={{ color: '#fff', fontSize: 15, fontWeight: '600' }}>{loading ? 'Loading...' : 'Verify OTP'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderGooglePayBusinessBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showGooglePayBusinessBind: false })}>
|
||||||
|
<GooglePayBusinessBind
|
||||||
|
processString="Processing..."
|
||||||
|
isDebug={true}
|
||||||
|
onSuccess={this.handleUploadGooglePayBusiness}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showGooglePayBusinessBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderBharatPeBusinessBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showBharatPeBusinessBind: false })}>
|
||||||
|
<BharatPeBusinessOTPBind
|
||||||
|
isDebug={true}
|
||||||
|
onRequestOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.requestOTP(walletType, params.mobile);
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onVerifyOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { sessionId: params.sessionId });
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSuccess={this.handleUploadBharatPeBusiness}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showBharatPeBusinessBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderMobikwikPersonalOTPBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showMobikwikPersonalBind: false })}>
|
||||||
|
<MobikwikOTPBind
|
||||||
|
isDebug={true}
|
||||||
|
deviceId={this.deviceId}
|
||||||
|
tuneUserId={this.tuneUserId}
|
||||||
|
onRequestOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.requestOTP(walletType, params.mobile, {
|
||||||
|
deviceId: params.deviceId,
|
||||||
|
tuneUserId: params.tuneUserId
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onVerifyOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
||||||
|
deviceId: params.deviceId,
|
||||||
|
tuneUserId: params.tuneUserId,
|
||||||
|
nid: params.nid
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSuccess={this.handleUploadMobikwikPersonal}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showMobikwikPersonalBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderFreechargePersonalOTPBind = () => (
|
||||||
|
<Modal visible transparent onRequestClose={() => this.setState({ showFreechargePersonalBind: false })}>
|
||||||
|
<FreeChargeBind
|
||||||
|
isDebug={true}
|
||||||
|
onRequestOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.requestOTP(walletType, params.mobile);
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onVerifyOTP={async (walletType, params) => {
|
||||||
|
try {
|
||||||
|
return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
|
||||||
|
otpId: params.otpId,
|
||||||
|
deviceId: params.deviceId,
|
||||||
|
csrfId: params.csrfId,
|
||||||
|
appFc: params.appFc
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: (error as Error).message };
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSuccess={this.handleUploadFreechargePersonal}
|
||||||
|
onError={(error: string) => {
|
||||||
|
Alert.alert('绑定失败', error);
|
||||||
|
this.setState({ showFreechargePersonalBind: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
renderBindModal = () => {
|
||||||
|
if (this.state.showPaytmPersonalBind) {
|
||||||
|
return this.state.paytmPersonalBindType === 'tokenMode'
|
||||||
|
? this.renderPaytmPersonalTokenBind()
|
||||||
|
: this.renderPaytmPersonalOTPBind();
|
||||||
|
}
|
||||||
|
if (this.state.showPhonePePersonalBind) {
|
||||||
|
return this.state.phonePePersonalBindType === 'tokenMode'
|
||||||
|
? this.renderPhonePePersonalTokenBind()
|
||||||
|
: this.renderPhonePePersonalOTPBind();
|
||||||
|
}
|
||||||
|
if (this.state.showPaytmBusinessBind) return this.renderPaytmBusinessBind();
|
||||||
|
if (this.state.showPhonePeBusinessBind) return this.renderPhonePeBusinessBind();
|
||||||
|
if (this.state.showGooglePayBusinessBind) return this.renderGooglePayBusinessBind();
|
||||||
|
if (this.state.showBharatPeBusinessBind) return this.renderBharatPeBusinessBind();
|
||||||
|
if (this.state.showMobikwikPersonalBind) return this.renderMobikwikPersonalOTPBind();
|
||||||
|
if (this.state.showFreechargePersonalBind) return this.renderFreechargePersonalOTPBind();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
openServerSettings = () => {
|
||||||
|
const domain = getServerDomain();
|
||||||
|
const colonIdx = domain.lastIndexOf(':');
|
||||||
|
const host = colonIdx > 0 ? domain.substring(0, colonIdx) : domain;
|
||||||
|
const port = colonIdx > 0 ? domain.substring(colonIdx + 1) : '';
|
||||||
|
this.setState({ showServerSettings: true, settingsHost: host, settingsPort: port });
|
||||||
|
};
|
||||||
|
|
||||||
|
saveDomain = async () => {
|
||||||
|
const { settingsHost, settingsPort } = this.state;
|
||||||
|
const domain = settingsPort ? `${settingsHost}:${settingsPort}` : settingsHost;
|
||||||
|
const useHttps = settingsPort === '443';
|
||||||
|
await saveServerDomain(domain, useHttps);
|
||||||
|
this.setState({ showServerSettings: false });
|
||||||
|
Alert.alert('已保存', '重启 App 后生效');
|
||||||
|
};
|
||||||
|
|
||||||
|
renderServerSettingsModal() {
|
||||||
|
const { showServerSettings, settingsHost, settingsPort } = this.state;
|
||||||
|
const presets = [
|
||||||
|
{ label: 'aa.pfgame.org', host: 'aa.pfgame.org', port: '443' },
|
||||||
|
{ label: 'game.ainavx.com:16000', host: 'game.ainavx.com', port: '16000' },
|
||||||
|
{ label: '192.168.1.117:16000', host: '192.168.1.117', port: '16000' },
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Modal visible={showServerSettings} transparent animationType="fade">
|
||||||
|
<View style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<View style={{ backgroundColor: '#fff', borderRadius: 10, padding: 20, width: '85%' }}>
|
||||||
|
<Text style={{ fontSize: 16, fontWeight: 'bold', marginBottom: 12 }}>服务器设置</Text>
|
||||||
|
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginBottom: 14 }}>
|
||||||
|
{presets.map(p => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={p.label}
|
||||||
|
onPress={() => this.setState({ settingsHost: p.host, settingsPort: p.port })}
|
||||||
|
style={{ paddingHorizontal: 10, paddingVertical: 5, borderRadius: 6, backgroundColor: settingsHost === p.host && settingsPort === p.port ? '#3498db' : '#f0f0f0' }}
|
||||||
|
>
|
||||||
|
<Text style={{ fontSize: 12, color: settingsHost === p.host && settingsPort === p.port ? '#fff' : '#333' }}>{p.label}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<Text style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>Host</Text>
|
||||||
|
<TextInput
|
||||||
|
style={{ borderWidth: 1, borderColor: '#ddd', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 8, marginBottom: 12, fontSize: 14 }}
|
||||||
|
value={settingsHost}
|
||||||
|
onChangeText={t => this.setState({ settingsHost: t })}
|
||||||
|
placeholder="192.168.1.198"
|
||||||
|
autoCapitalize="none"
|
||||||
|
keyboardType="default"
|
||||||
|
/>
|
||||||
|
<Text style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>Port</Text>
|
||||||
|
<TextInput
|
||||||
|
style={{ borderWidth: 1, borderColor: '#ddd', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 8, marginBottom: 20, fontSize: 14 }}
|
||||||
|
value={settingsPort}
|
||||||
|
onChangeText={t => this.setState({ settingsPort: t })}
|
||||||
|
placeholder="16000"
|
||||||
|
keyboardType="number-pad"
|
||||||
|
/>
|
||||||
|
<View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
|
||||||
|
<TouchableOpacity onPress={() => this.setState({ showServerSettings: false })} style={{ paddingHorizontal: 16, paddingVertical: 8, marginRight: 10 }}>
|
||||||
|
<Text style={{ color: '#666' }}>取消</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={this.saveDomain} style={{ backgroundColor: '#3498db', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 6 }}>
|
||||||
|
<Text style={{ color: '#fff', fontWeight: 'bold' }}>保存</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { proxyStatus, proxyError } = this.state;
|
||||||
|
const cfg: Record<string, { label: string; color: string }> = {
|
||||||
|
idle: { label: '未连接', color: '#95a5a6' },
|
||||||
|
connecting: { label: '连接中…', color: '#f39c12' },
|
||||||
|
connected: { label: '已连接', color: '#2ecc71' },
|
||||||
|
disconnected: { label: '已断开', color: '#e74c3c' },
|
||||||
|
error: { label: '连接失败', color: '#e74c3c' },
|
||||||
|
};
|
||||||
|
const { label, color } = cfg[proxyStatus];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 6, paddingHorizontal: 12 }}>
|
||||||
|
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: color, marginRight: 6 }} />
|
||||||
|
<Text style={{ color, fontSize: 13 }}>
|
||||||
|
Proxy {label}{proxyStatus === 'error' && proxyError ? `:${proxyError}` : ''}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{this.renderBindModal()}
|
||||||
|
{this.renderServerSettingsModal()}
|
||||||
|
<TouchableOpacity onPress={this.openServerSettings} style={{ alignSelf: 'flex-end', paddingHorizontal: 12, paddingVertical: 4 }}>
|
||||||
|
<Text style={{ fontSize: 12, color: '#3498db' }}>⚙ 服务器设置</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollViewContent}>
|
||||||
|
<View style={[styles.bindButton, { backgroundColor: "transparent", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }]}>
|
||||||
|
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
||||||
|
this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'otpMode' });
|
||||||
|
}}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 Paytm Personal(OTP)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
||||||
|
this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'tokenMode' });
|
||||||
|
}}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 Paytm Personal(Token)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity style={styles.bindButton} onPress={() => this.setState({ showPaytmBusinessBind: true })}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 Paytm Business (OTP)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={[styles.bindButton, { backgroundColor: "transparent", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }]}>
|
||||||
|
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
||||||
|
this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'otpMode' });
|
||||||
|
}}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 PhonePe Personal(OTP)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
|
||||||
|
this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' });
|
||||||
|
}}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 PhonePe Personal(Token)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity style={styles.bindButton} onPress={() => this.setState({ showPhonePeBusinessBind: true })}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 PhonePe Business (OTP-WEB)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.bindButton} onPress={() => this.setState({ showGooglePayBusinessBind: true })}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 GooglePay Business</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.bindButton} onPress={() => this.setState({ showBharatPeBusinessBind: true })}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 BharatPe Business (OTP)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.bindButton} onPress={() => this.setState({ showMobikwikPersonalBind: true })}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 Mobikwik Personal (OTP)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.bindButton} onPress={() => this.setState({ showFreechargePersonalBind: true })}>
|
||||||
|
<Text style={styles.bindButtonText}>绑定 Freecharge Personal (OTP)</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={[styles.bindButton, { backgroundColor: '#2ecc71' }]} onPress={this.sendEcho}>
|
||||||
|
<Text style={styles.bindButtonText}>Echo 测试</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "#f0f0f0",
|
||||||
|
},
|
||||||
|
scrollView: {
|
||||||
|
flex: 1,
|
||||||
|
// backgroundColor: "lightblue",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
scrollViewContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
padding: 10,
|
||||||
|
backgroundColor: "lightblue",
|
||||||
|
borderRadius: 5,
|
||||||
|
width: 200,
|
||||||
|
height: 55,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
modalContent: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
bindButton: {
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
backgroundColor: "#007AFF",
|
||||||
|
borderRadius: 5,
|
||||||
|
width: "90%",
|
||||||
|
height: 45,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
bindButtonText: {
|
||||||
|
fontSize: 14,
|
||||||
|
// fontWeight: "bold",
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
});
|
||||||
78
screens/MessageScreen.tsx
Normal file
78
screens/MessageScreen.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { View, Text, StyleSheet, FlatList } from 'react-native';
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOCK_MESSAGES: Message[] = [
|
||||||
|
{ id: '1', title: '系统通知', content: '欢迎使用 RNPay,绑定钱包开始使用。', time: '10:00' },
|
||||||
|
{ id: '2', title: '代理连接', content: '代理服务已成功连接到服务器。', time: '10:05' },
|
||||||
|
{ id: '3', title: '绑定提醒', content: '您有钱包待绑定,请前往首页操作。', time: '10:10' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class MessageScreen extends Component {
|
||||||
|
renderItem = ({ item }: { item: Message }) => (
|
||||||
|
<View style={styles.item}>
|
||||||
|
<View style={styles.itemHeader}>
|
||||||
|
<Text style={styles.itemTitle}>{item.title}</Text>
|
||||||
|
<Text style={styles.itemTime}>{item.time}</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.itemContent}>{item.content}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<FlatList
|
||||||
|
data={MOCK_MESSAGES}
|
||||||
|
keyExtractor={item => item.id}
|
||||||
|
renderItem={this.renderItem}
|
||||||
|
contentContainerStyle={styles.list}
|
||||||
|
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: 14,
|
||||||
|
},
|
||||||
|
itemHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
itemTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
itemTime: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#999',
|
||||||
|
},
|
||||||
|
itemContent: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#666',
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
Submodule servers/walletman updated: 8b3e2b2785...f688b4df5e
45
styles.ts
45
styles.ts
@@ -1,45 +0,0 @@
|
|||||||
import { StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
export const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
padding: 10,
|
|
||||||
backgroundColor: "lightblue",
|
|
||||||
borderRadius: 5,
|
|
||||||
width: 200,
|
|
||||||
height: 55,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
modalContent: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
bindButton: {
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 10,
|
|
||||||
backgroundColor: "#007AFF",
|
|
||||||
borderRadius: 5,
|
|
||||||
width: "90%",
|
|
||||||
height: 45,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
bindButtonText: {
|
|
||||||
fontSize: 14,
|
|
||||||
// fontWeight: "bold",
|
|
||||||
color: "#fff",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user