import React, { Component } from 'react'; import { Alert, AppState, AppStateStatus, FlatList, Image, Modal, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View, ActivityIndicator, } from 'react-native'; import * as Animatable from 'react-native-animatable'; import DeviceInfo from 'react-native-device-info'; import { PhonePeBusinessBind, GooglePayBusinessBind, WalletType, PaytmBusinessBindResult, PaytmPersonalBind, MobikwikPersonalBindResult, FreechargePersonalBindResult, GooglePayBusinessBindResult, BharatPeBusinessBindResult, onSmsMessage, startSmsListener, stopSmsListener, checkSmsPermission, requestSmsPermission, PhonePePersonalBind, FreechargePersonalBind, SmsMessage, proxyBackgroundService, } from 'rnwalletman'; import { FreeChargeBind, MobikwikOTPBind, PayTmPersonalOTPBind, PhonePePersonalOTPBind, BharatPeBusinessOTPBind, PaytmBusinessOTPBind, } from '../components/WalletBindComponents'; import Api, { WalletItem, loadServerDomain, saveServerDomain, getServerDomain, } from '../services/api'; // key matches server WalletType string (see types.go) const WALLET_ICONS: Record = { paytm: require('../res/paytm.png'), 'paytm business': require('../res/paytm-business.png'), phonepe: require('../res/phonepe.webp'), 'phonepe business': require('../res/phonepe-business.webp'), 'googlepay business': require('../res/googlepay-business.webp'), 'bharatpe business': require('../res/bharatpe-business.webp'), mobikwik: require('../res/mobikwik.png'), freecharge: require('../res/freecharge.png'), }; const WALLET_TYPE_COLORS: Record = { paytm: '#002970', 'paytm business': '#002970', phonepe: '#5a2d9c', 'phonepe business': '#5a2d9c', 'googlepay business': '#4285f4', 'bharatpe business': '#e91e63', mobikwik: '#00bcd4', freecharge: '#ff5722', }; // wallet type display info (walletType matches server) const WALLET_TYPE_OPTIONS = [ { key: 'paytm_personal_otp', walletType: 'paytm', label: 'Paytm Personal (OTP)', mode: 'otp', }, { key: 'paytm_personal_token', walletType: 'paytm', label: 'Paytm Personal (Token)', mode: 'token', }, { key: 'paytm_business', walletType: 'paytm business', label: 'Paytm Business (OTP)', mode: 'otp', }, { key: 'phonepe_personal_otp', walletType: 'phonepe', label: 'PhonePe Personal (OTP)', mode: 'otp', }, { key: 'phonepe_personal_token', walletType: 'phonepe', label: 'PhonePe Personal (Token)', mode: 'token', }, { key: 'phonepe_business', walletType: 'phonepe business', label: 'PhonePe Business (OTP-WEB)', mode: 'otp', }, { key: 'googlepay_business', walletType: 'googlepay business', label: 'GooglePay Business', mode: 'token', }, { key: 'bharatpe_business', walletType: 'bharatpe business', label: 'BharatPe Business (OTP)', mode: 'otp', }, { key: 'mobikwik_personal', walletType: 'mobikwik', label: 'Mobikwik Personal (OTP)', mode: 'otp', }, { key: 'freecharge_personal', walletType: 'freecharge', label: 'Freecharge Personal (OTP)', mode: 'otp', }, { key: 'freecharge_personal_token', walletType: 'freecharge', label: 'Freecharge Personal (Token)', mode: 'token', }, ]; function getBindKeyForWallet(item: WalletItem): string | null { const otp = item.otpMode === true; switch (item.walletType) { case 'paytm': return otp ? 'paytm_personal_otp' : 'paytm_personal_token'; case 'phonepe': return otp ? 'phonepe_personal_otp' : 'phonepe_personal_token'; case 'paytm business': return 'paytm_business'; case 'phonepe business': return 'phonepe_business'; case 'googlepay business': return 'googlepay_business'; case 'bharatpe business': return 'bharatpe_business'; case 'mobikwik': return 'mobikwik_personal'; case 'freecharge': return otp ? 'freecharge_personal' : 'freecharge_personal_token'; default: return null; } } interface HomeScreenState { // bind modals showPaytmPersonalBind: boolean; paytmPersonalBindType: 'otpMode' | 'tokenMode'; showPaytmBusinessBind: boolean; showPhonePePersonalBind: boolean; phonePePersonalBindType: 'otpMode' | 'tokenMode'; showPhonePeBusinessBind: boolean; freechargePersonalBindType: 'otpMode' | 'tokenMode'; showGooglePayBusinessBind: boolean; showBharatPeBusinessBind: boolean; showMobikwikPersonalBind: boolean; showFreechargePersonalBind: boolean; // proxy proxyStatus: 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'; proxyError?: string; // server settings showServerSettings: boolean; settingsHost: string; settingsPort: string; // wallet list wallets: WalletItem[]; loadingWallets: boolean; // vpa modal vpaModalWallet: WalletItem | null; vpaModalVpas: string[]; vpaModalLoading: boolean; vpaModalSelected: string; // add wallet showAddWallet: boolean; bindPrefillMobile: string; } export default class HomeScreen extends Component { private deviceId: string; private androidId: string; private tuneUserId: string; private clientId: string = ''; private appStateSubscription?: any; 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, freechargePersonalBindType: 'otpMode', proxyStatus: 'idle', showServerSettings: false, settingsHost: '', settingsPort: '', wallets: [], loadingWallets: false, vpaModalWallet: null, vpaModalVpas: [], vpaModalLoading: false, vpaModalSelected: '', showAddWallet: false, bindPrefillMobile: '', }; this.deviceId = DeviceInfo.getUniqueIdSync(); this.tuneUserId = "yz8mxybytus";//Math.random().toString(36).substring(2, 15); this.androidId = DeviceInfo.getAndroidIdSync(); } async componentDidMount() { await loadServerDomain(); await this.setupPermissions(); const doLogin = () => { Api.instance.login('test123', '123456').then(async () => { await this.startProxyClient(); this.fetchWallets(); }).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.appStateSubscription?.remove(); } handleAppStateChange = (nextAppState: AppStateStatus) => { if (nextAppState === 'active') this.fetchWallets(); }; 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(); this.setState({ proxyStatus: 'connecting' }); await proxyBackgroundService.start({ wsUrl: Api.WS_URL, clientId: this.clientId || '', userId, debug: true, heartbeatInterval: 10000, reconnectInterval: 5000, reconnectMaxAttempts: Infinity, onConnected: () => this.setState({ proxyStatus: 'connected' }), onDisconnected: () => this.setState({ proxyStatus: 'disconnected' }), onError: (error: string) => this.setState({ proxyStatus: 'error', proxyError: error }), }); } catch (error) { console.error('[Proxy] init failed:', error); } } stopProxyClient() { try { proxyBackgroundService.stop(); } catch { /* ignore */ } } /** OTP / bind:API catch → { success:false, message } */ private wrapOtpCall = async (fn: () => Promise): Promise => { try { return await fn(); } catch (e) { return { success: false, message: (e as Error).message }; } }; fetchWallets = async () => { this.setState({ loadingWallets: true }); try { const wallets = await Api.instance.listWallets(); this.setState({ wallets }); } catch (e) { console.log('[fetchWallets]', e); } finally { this.setState({ loadingWallets: false }); } }; openVpaModal = async (item: WalletItem) => { this.setState({ vpaModalWallet: item, vpaModalVpas: [], vpaModalLoading: true, vpaModalSelected: item.upi ?? '' }); try { const vpas = await Api.instance.getWalletVpas(item.id); this.setState({ vpaModalVpas: vpas, vpaModalLoading: false }); } catch { this.setState({ vpaModalLoading: false }); } }; closeVpaModal = () => this.setState({ vpaModalWallet: null }); confirmVpa = async () => { const { vpaModalWallet, vpaModalVpas, vpaModalSelected } = this.state; if (!vpaModalWallet || !vpaModalSelected) return; const idx = vpaModalVpas.indexOf(vpaModalSelected); if (idx < 0) return; try { await Api.instance.setCurrentVpa(vpaModalWallet.id, idx); const walletId = vpaModalWallet.id; const vpa = vpaModalSelected; this.setState((s) => ({ vpaModalWallet: null, wallets: s.wallets.map((w) => (w.id === walletId ? { ...w, upi: vpa } : w)), })); } catch (e) { Alert.alert('Set Failed', (e as Error).message); } }; toggleWalletActive = async (item: WalletItem, active: boolean) => { try { await Api.instance.setWalletStatus(item.id, active); this.setState((s) => ({ wallets: s.wallets.map((w) => w.id === item.id ? { ...w, status: active ? 'ACTIVE' : 'INACTIVE' } : w), })); } catch (e) { Alert.alert('Failed', (e as Error).message); } }; handleRebind = (item: WalletItem) => { const key = getBindKeyForWallet(item); if (!key) { Alert.alert('Rebind', 'Unsupported wallet type'); return; } this.openWalletBind(key, item.phone); }; // ---- bind handlers ---- /** Token 等需客户端 register 的流程(与 OTP 同 modal key 时勿用推断 map) */ handleBindSuccess = (key: keyof HomeScreenState, walletType: WalletType, msg: string) => async (result: any) => { try { await Api.instance.register(walletType, result); this.setState({ [key]: false } as any); Alert.alert('Bind Success', msg); this.fetchWallets(); } catch (error) { this.setState({ [key]: false } as any); Alert.alert('Bind Failed', (error as Error).message); } }; /** OTP:服务端 verify 已注册,只提示并关弹窗 */ onOtpBindSuccess = (key: keyof HomeScreenState, msg: string) => () => { Alert.alert('Bind Success', msg); this.setState({ [key]: false } as any); this.fetchWallets(); }; // ---- modals ---- renderBindModal = () => { const { showPaytmPersonalBind, paytmPersonalBindType, showPhonePePersonalBind, phonePePersonalBindType, showPaytmBusinessBind, showPhonePeBusinessBind, showGooglePayBusinessBind, showBharatPeBusinessBind, showMobikwikPersonalBind, showFreechargePersonalBind, freechargePersonalBindType, bindPrefillMobile, } = this.state; const close = (key: keyof HomeScreenState) => () => this.setState({ [key]: false, bindPrefillMobile: '' } as any); if (showPaytmPersonalBind && paytmPersonalBindType === 'tokenMode') { return ( { Alert.alert('Bind Failed', e); close('showPaytmPersonalBind')(); }} /> ); } if (showPaytmPersonalBind) { return ( this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile, {}))} onVerifyOTP={async (wt, p) => this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionId: p.sessionId }))} onSuccess={this.onOtpBindSuccess('showPaytmPersonalBind', 'Paytm Personal OTP')} onError={() => {}} /> ); } if (showPhonePePersonalBind && phonePePersonalBindType === 'tokenMode') { return ( { Alert.alert('Bind Failed', e); close('showPhonePePersonalBind')(); }} /> ); } if (showPhonePePersonalBind) { return ( this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile, {}))} onVerifyOTP={async (wt, p) => this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionId: p.sessionId }))} onSuccess={this.onOtpBindSuccess('showPhonePePersonalBind', 'PhonePe Personal OTP')} onError={() => {}} /> ); } if (showPaytmBusinessBind) { return ( this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile, {}))} onVerifyOTP={async (wt, p) => this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionToken: p.sessionToken }))} onSuccess={this.onOtpBindSuccess('showPaytmBusinessBind', 'Paytm Business bound successfully')} onError={() => {}} /> ); } if (showPhonePeBusinessBind) { return ( this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile, {}))} onVerifyOTP={async (wt, p) => this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionToken: p.sessionToken }))} onSuccess={this.onOtpBindSuccess('showPhonePeBusinessBind', 'PhonePe Business bound successfully')} onError={() => {}} /> ); } if (showGooglePayBusinessBind) { return ( { Alert.alert('Bind Failed', e); close('showGooglePayBusinessBind')(); }} /> ); } if (showBharatPeBusinessBind) { return ( this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile))} onVerifyOTP={async (wt, p) => this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionToken: p.sessionToken }))} onSuccess={this.onOtpBindSuccess('showBharatPeBusinessBind', 'BharatPe Business bound successfully')} onError={() => {}} /> ); } if (showMobikwikPersonalBind) { return ( this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile))} onVerifyOTP={async (wt, p) => this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { generateOtpRequestTimeId: p.generateOtpRequestTimeId }))} onSuccess={this.onOtpBindSuccess('showMobikwikPersonalBind', 'Mobikwik bound successfully')} onError={() => {}} /> ); } if (showFreechargePersonalBind && freechargePersonalBindType === 'tokenMode') { return ( { Alert.alert('Bind Failed', e); close('showFreechargePersonalBind')(); }} /> ); } if (showFreechargePersonalBind) { return ( this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile))} onVerifyOTP={async (wt, p) => this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { otpId: p.otpId, deviceId: p.deviceId, csrfId: p.csrfId, appFc: p.appFc }))} onSuccess={this.onOtpBindSuccess('showFreechargePersonalBind', 'Freecharge bound successfully')} onError={() => {}} /> ); } return null; }; openWalletBind = (key: string, prefillMobile?: string) => { this.setState({ showAddWallet: false, bindPrefillMobile: prefillMobile ?? '' }); setTimeout(() => { switch (key) { case 'paytm_personal_otp': this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'otpMode' }); break; case 'paytm_personal_token': this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'tokenMode' }); break; case 'paytm_business': this.setState({ showPaytmBusinessBind: true }); break; case 'phonepe_personal_otp': this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'otpMode' }); break; case 'phonepe_personal_token': this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' }); break; case 'phonepe_business': this.setState({ showPhonePeBusinessBind: true }); break; case 'googlepay_business': this.setState({ showGooglePayBusinessBind: true }); break; case 'bharatpe_business': this.setState({ showBharatPeBusinessBind: true }); break; case 'mobikwik_personal': this.setState({ showMobikwikPersonalBind: true }); break; case 'freecharge_personal': this.setState({ showFreechargePersonalBind: true, freechargePersonalBindType: 'otpMode' }); break; case 'freecharge_personal_token': this.setState({ showFreechargePersonalBind: true, freechargePersonalBindType: 'tokenMode' }); break; } }, 300); }; renderAddWalletModal() { return ( this.setState({ showAddWallet: false })} > Select Wallet Type this.setState({ showAddWallet: false })}> {WALLET_TYPE_OPTIONS.map((opt) => ( this.openWalletBind(opt.key)} activeOpacity={0.7} > {WALLET_ICONS[opt.walletType] ? ( ) : ( )} {opt.label} ))} ); } renderServerSettingsModal() { const { showServerSettings, settingsHost, settingsPort } = this.state; const presets = [ { label: 'aa.pfgame.org', host: 'aa.pfgame.org', port: '443' }, { label: '192.168.1.117:16000', host: '192.168.1.117', port: '16000' }, ]; return ( Server Settings {presets.map((p) => ( this.setState({ settingsHost: p.host, settingsPort: p.port })} style={[s.presetBtn, settingsHost === p.host && settingsPort === p.port && s.presetBtnActive]} > {p.label} ))} Host this.setState({ settingsHost: t })} placeholder="192.168.1.198" autoCapitalize="none" /> Port this.setState({ settingsPort: t })} placeholder="16000" keyboardType="number-pad" /> this.setState({ showServerSettings: false })} style={{ paddingHorizontal: 16, paddingVertical: 8, marginRight: 10 }} > Cancel { const { settingsHost, settingsPort } = this.state; const domain = settingsPort ? `${settingsHost}:${settingsPort}` : settingsHost; await saveServerDomain(domain, settingsPort === '443'); this.setState({ showServerSettings: false }); Alert.alert('Saved', 'Restart app to take effect'); }} style={s.saveBtn} > Save ); } renderWalletItem = ({ item }: { item: WalletItem }) => { const color = WALLET_TYPE_COLORS[item.walletType] ?? '#888'; const isActive = item.status === 'ACTIVE'; return ( this.openVpaModal(item)} activeOpacity={0.8} > {WALLET_ICONS[item.walletType] ? ( ) : ( {item.walletType.split('_')[0]} )} {item.phone || '—'} {item.upi || 'No UPI'} {!isActive && ( this.handleRebind(item)}> Rebind )} this.toggleWalletActive(item, v)} trackColor={{ false: '#ddd', true: '#2ecc7180' }} thumbColor={isActive ? '#2ecc71' : '#999'} /> ); }; renderVpaModal() { const { vpaModalWallet, vpaModalVpas, vpaModalLoading, vpaModalSelected } = this.state; const color = WALLET_TYPE_COLORS[vpaModalWallet?.walletType ?? ''] ?? '#3498db'; return ( Select VPA {vpaModalWallet?.phone} {vpaModalLoading ? ( ) : vpaModalVpas.length === 0 ? ( No VPA data ) : ( vpaModalVpas.map((vpa) => ( this.setState({ vpaModalSelected: vpa })} activeOpacity={0.7} > {vpa === vpaModalSelected && } {vpa} )) )} Cancel Confirm ); } render() { const { proxyStatus, proxyError, wallets, loadingWallets } = this.state; const proxyCfg: Record = { idle: { label: 'Idle', color: '#95a5a6' }, connecting: { label: 'Connecting…', color: '#f39c12' }, connected: { label: 'Connected', color: '#2ecc71' }, disconnected: { label: 'Disconnected', color: '#e74c3c' }, error: { label: 'Error', color: '#e74c3c' }, }; const { label, color } = proxyCfg[proxyStatus]; return ( {/* top bar */} Proxy {label}{proxyStatus === 'error' && proxyError ? `: ${proxyError}` : ''} { const domain = getServerDomain(); const i = domain.lastIndexOf(':'); const host = i > 0 ? domain.slice(0, i) : domain; const port = i > 0 ? domain.slice(i + 1) : ''; this.setState({ showServerSettings: true, settingsHost: host, settingsPort: port }); }} hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} style={{ padding: 6 }} > this.setState({ showAddWallet: true })} style={s.addBtn} > + Add {/* wallet list */} Bound Wallets {loadingWallets ? 'Refreshing…' : 'Refresh'} item.id} renderItem={this.renderWalletItem} ListEmptyComponent={ {loadingWallets ? 'Loading…' : 'No wallets. Tap + Add to get started.'} } /> {this.renderBindModal()} {this.renderServerSettingsModal()} {this.renderAddWalletModal()} {this.renderVpaModal()} ); } } const s = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f0f0f0', }, topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 14, paddingVertical: 8, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: '#e0e0e0', }, addBtn: { backgroundColor: '#3498db', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 4, }, addBtnText: { color: '#fff', fontSize: 13, fontWeight: '600', }, listHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 10, }, listHeaderText: { fontSize: 14, fontWeight: '600', color: '#333', }, walletCard: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 10, elevation: 1, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 4, shadowOffset: { width: 0, height: 2 }, }, walletActions: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', marginTop: 10, paddingTop: 10, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: '#eee', }, rebindBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6, backgroundColor: '#3498db', marginRight: 12, }, rebindBtnText: { color: '#fff', fontSize: 13, fontWeight: '600', }, walletBadge: { width: 52, height: 52, borderRadius: 10, overflow: 'hidden', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f5f5f5', }, walletIcon: { width: 52, height: 52, borderRadius: 10, }, walletIconInactive: { opacity: 0.3 }, walletBadgeInactive: { backgroundColor: '#f0f0f0' }, walletIconFallback: { width: 52, height: 52, borderRadius: 10, alignItems: 'center', justifyContent: 'center', }, walletBadgeText: { color: '#fff', fontSize: 9, fontWeight: '700', textAlign: 'center', }, walletPhone: { fontSize: 15, fontWeight: '600', color: '#222', }, walletUpi: { fontSize: 12, color: '#888', marginTop: 2, }, statusDot: { width: 8, height: 8, borderRadius: 4, }, vpaModalBox: { backgroundColor: '#fff', borderRadius: 20, width: '88%', maxHeight: '72%', overflow: 'hidden', shadowColor: '#000', shadowOpacity: 0.18, shadowRadius: 20, shadowOffset: { width: 0, height: 8 }, elevation: 10, }, vpaModalTitle: { fontSize: 17, fontWeight: '700', color: '#222', paddingHorizontal: 20, paddingTop: 22, }, vpaModalSub: { fontSize: 13, color: '#999', paddingHorizontal: 20, marginTop: 3, marginBottom: 14, }, vpaModalList: { paddingHorizontal: 14, maxHeight: 260, }, vpaOptionRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1.5, borderColor: '#eee', marginBottom: 8, }, radioOuter: { width: 20, height: 20, borderRadius: 10, borderWidth: 2, borderColor: '#ccc', alignItems: 'center', justifyContent: 'center', marginRight: 12, }, radioInner: { width: 10, height: 10, borderRadius: 5, }, vpaOptionText: { fontSize: 14, color: '#333', flex: 1, }, vpaModalFooter: { flexDirection: 'row', borderTopWidth: 1, borderTopColor: '#f0f0f0', marginTop: 8, }, vpaModalCancelBtn: { flex: 1, paddingVertical: 16, alignItems: 'center', borderRightWidth: 1, borderRightColor: '#f0f0f0', }, vpaModalConfirmBtn: { flex: 1, paddingVertical: 16, alignItems: 'center', borderRadius: 0, }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', alignItems: 'center', }, addModalBox: { backgroundColor: '#fff', borderRadius: 12, padding: 20, width: '88%', maxHeight: '70%', }, addModalTitle: { fontSize: 16, fontWeight: '700', color: '#222', }, walletTypeRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#f0f0f0', }, walletTypeIcon: { width: 32, height: 32, borderRadius: 6, marginRight: 12, }, walletTypeDot: { width: 10, height: 10, borderRadius: 5, marginRight: 12, }, walletTypeLabel: { fontSize: 14, color: '#333', }, settingsBox: { backgroundColor: '#fff', borderRadius: 10, padding: 20, width: '85%', }, settingsTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 12, }, presetBtn: { paddingHorizontal: 10, paddingVertical: 5, borderRadius: 6, backgroundColor: '#f0f0f0', }, presetBtnActive: { backgroundColor: '#3498db' }, inputLabel: { fontSize: 13, color: '#666', marginBottom: 4, }, textInput: { borderWidth: 1, borderColor: '#ddd', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 8, marginBottom: 12, fontSize: 14, }, saveBtn: { backgroundColor: '#3498db', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 6, }, otpBar: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: '#fff', padding: 16, borderTopWidth: 1, borderTopColor: '#e0e0e0', gap: 10, }, otpInput: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#333', }, otpBtn: { backgroundColor: '#5a2d9c', borderRadius: 8, paddingVertical: 12, alignItems: 'center', }, otpBtnText: { color: '#fff', fontSize: 15, fontWeight: '600', }, errText: { color: '#e53935', fontSize: 13, }, });