diff --git a/mobile/src/components/AddressDisplay.tsx b/mobile/src/components/AddressDisplay.tsx new file mode 100644 index 00000000..08fd3349 --- /dev/null +++ b/mobile/src/components/AddressDisplay.tsx @@ -0,0 +1,94 @@ +import React, { useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + Clipboard, +} from 'react-native'; +import { KurdistanColors } from '../theme/colors'; + +interface AddressDisplayProps { + address: string; + label?: string; + copyable?: boolean; +} + +/** + * Format address for display (e.g., "5GrwV...xQjz") + */ +const formatAddress = (address: string): string => { + if (!address) return ''; + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +export const AddressDisplay: React.FC = ({ + address, + label, + copyable = true, +}) => { + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + if (!copyable) return; + + Clipboard.setString(address); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + {label && {label}} + + + {formatAddress(address)} + {copyable && ( + {copied ? '✅' : '📋'} + )} + + + {copied && Copied!} + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginVertical: 4, + }, + label: { + fontSize: 12, + color: '#666', + marginBottom: 4, + }, + addressContainer: { + flexDirection: 'row', + alignItems: 'center', + padding: 8, + backgroundColor: '#F5F5F5', + borderRadius: 8, + borderWidth: 1, + borderColor: '#E0E0E0', + }, + address: { + flex: 1, + fontSize: 14, + fontFamily: 'monospace', + color: '#000', + }, + copyIcon: { + fontSize: 18, + marginLeft: 8, + }, + copiedText: { + fontSize: 12, + color: KurdistanColors.kesk, + marginTop: 4, + textAlign: 'center', + }, +}); diff --git a/mobile/src/components/BalanceCard.tsx b/mobile/src/components/BalanceCard.tsx new file mode 100644 index 00000000..dfe97892 --- /dev/null +++ b/mobile/src/components/BalanceCard.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { TokenIcon } from './TokenIcon'; +import { KurdistanColors } from '../theme/colors'; + +interface BalanceCardProps { + symbol: string; + name: string; + balance: string; + value?: string; + change?: string; + onPress?: () => void; +} + +export const BalanceCard: React.FC = ({ + symbol, + name, + balance, + value, + change, + onPress, +}) => { + const changeValue = parseFloat(change || '0'); + const isPositive = changeValue >= 0; + + return ( + + + + + + {symbol} + {balance} + + + {name} + {value && {value}} + + + + {change && ( + + + {isPositive ? '+' : ''} + {change} + + + )} + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#FFFFFF', + padding: 16, + borderRadius: 12, + marginBottom: 12, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }, + row: { + flexDirection: 'row', + alignItems: 'center', + }, + info: { + flex: 1, + marginLeft: 12, + }, + nameRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 4, + }, + symbol: { + fontSize: 18, + fontWeight: '700', + color: '#000', + }, + balance: { + fontSize: 18, + fontWeight: '600', + color: '#000', + }, + detailsRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + name: { + fontSize: 14, + color: '#666', + }, + value: { + fontSize: 14, + color: '#666', + }, + changeContainer: { + marginTop: 8, + alignItems: 'flex-end', + }, + change: { + fontSize: 12, + fontWeight: '600', + }, +}); diff --git a/mobile/src/components/TokenIcon.tsx b/mobile/src/components/TokenIcon.tsx new file mode 100644 index 00000000..49b0ed7b --- /dev/null +++ b/mobile/src/components/TokenIcon.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; + +interface TokenIconProps { + symbol: string; + size?: number; +} + +// Token emoji mapping +const TOKEN_ICONS: { [key: string]: string } = { + HEZ: '🟡', + PEZ: '🟣', + wHEZ: '🟡', + USDT: '💵', + wUSDT: '💵', + BTC: '₿', + ETH: '⟠', + DOT: '●', +}; + +export const TokenIcon: React.FC = ({ symbol, size = 32 }) => { + const icon = TOKEN_ICONS[symbol] || '❓'; + + return ( + + {icon} + + ); +}; + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center', + borderRadius: 100, + backgroundColor: '#F5F5F5', + }, + icon: { + textAlign: 'center', + }, +}); diff --git a/mobile/src/components/TokenSelector.tsx b/mobile/src/components/TokenSelector.tsx new file mode 100644 index 00000000..47e7173d --- /dev/null +++ b/mobile/src/components/TokenSelector.tsx @@ -0,0 +1,236 @@ +import React, { useState } from 'react'; +import { + View, + Text, + TouchableOpacity, + Modal, + StyleSheet, + FlatList, + SafeAreaView, +} from 'react-native'; +import { TokenIcon } from './TokenIcon'; +import { KurdistanColors } from '../theme/colors'; + +export interface Token { + symbol: string; + name: string; + assetId?: number; // undefined for native HEZ + decimals: number; + balance?: string; +} + +interface TokenSelectorProps { + selectedToken: Token | null; + tokens: Token[]; + onSelectToken: (token: Token) => void; + label?: string; + disabled?: boolean; +} + +export const TokenSelector: React.FC = ({ + selectedToken, + tokens, + onSelectToken, + label, + disabled = false, +}) => { + const [modalVisible, setModalVisible] = useState(false); + + const handleSelect = (token: Token) => { + onSelectToken(token); + setModalVisible(false); + }; + + return ( + + {label && {label}} + + !disabled && setModalVisible(true)} + disabled={disabled} + activeOpacity={0.7} + > + {selectedToken ? ( + + + + {selectedToken.symbol} + {selectedToken.name} + + + + ) : ( + + Select Token + + + )} + + + setModalVisible(false)} + > + + + + Select Token + setModalVisible(false)}> + + + + + item.symbol} + renderItem={({ item }) => ( + handleSelect(item)} + > + + + {item.symbol} + {item.name} + + {item.balance && ( + {item.balance} + )} + {selectedToken?.symbol === item.symbol && ( + + )} + + )} + ItemSeparatorComponent={() => } + /> + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginBottom: 12, + }, + label: { + fontSize: 14, + fontWeight: '600', + color: '#666', + marginBottom: 8, + }, + selector: { + backgroundColor: '#FFFFFF', + borderRadius: 12, + borderWidth: 1, + borderColor: '#E0E0E0', + padding: 12, + }, + disabled: { + opacity: 0.5, + }, + selectedToken: { + flexDirection: 'row', + alignItems: 'center', + }, + tokenInfo: { + flex: 1, + marginLeft: 12, + }, + tokenSymbol: { + fontSize: 16, + fontWeight: '700', + color: '#000', + }, + tokenName: { + fontSize: 12, + color: '#666', + marginTop: 2, + }, + chevron: { + fontSize: 12, + color: '#999', + }, + placeholder: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + placeholderText: { + fontSize: 16, + color: '#999', + }, + modalContainer: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'flex-end', + }, + modalContent: { + backgroundColor: '#FFFFFF', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + maxHeight: '80%', + paddingBottom: 20, + }, + modalHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 20, + borderBottomWidth: 1, + borderBottomColor: '#E0E0E0', + }, + modalTitle: { + fontSize: 18, + fontWeight: '700', + color: '#000', + }, + closeButton: { + fontSize: 24, + color: '#999', + paddingHorizontal: 8, + }, + tokenItem: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + }, + selectedItem: { + backgroundColor: '#F0F9F4', + }, + tokenDetails: { + flex: 1, + marginLeft: 12, + }, + itemSymbol: { + fontSize: 16, + fontWeight: '700', + color: '#000', + }, + itemName: { + fontSize: 12, + color: '#666', + marginTop: 2, + }, + itemBalance: { + fontSize: 14, + color: '#666', + marginRight: 8, + }, + checkmark: { + fontSize: 20, + color: KurdistanColors.kesk, + }, + separator: { + height: 1, + backgroundColor: '#F0F0F0', + marginHorizontal: 16, + }, +}); diff --git a/mobile/src/components/index.ts b/mobile/src/components/index.ts index a21f6fe9..b1c311fd 100644 --- a/mobile/src/components/index.ts +++ b/mobile/src/components/index.ts @@ -9,3 +9,8 @@ export { Input } from './Input'; export { BottomSheet } from './BottomSheet'; export { Skeleton, CardSkeleton, ListItemSkeleton } from './LoadingSkeleton'; export { Badge } from './Badge'; +export { TokenIcon } from './TokenIcon'; +export { AddressDisplay } from './AddressDisplay'; +export { BalanceCard } from './BalanceCard'; +export { TokenSelector } from './TokenSelector'; +export type { Token } from './TokenSelector'; diff --git a/mobile/src/navigation/BottomTabNavigator.tsx b/mobile/src/navigation/BottomTabNavigator.tsx index f6c974e9..ebbfd822 100644 --- a/mobile/src/navigation/BottomTabNavigator.tsx +++ b/mobile/src/navigation/BottomTabNavigator.tsx @@ -6,6 +6,7 @@ import { KurdistanColors } from '../theme/colors'; // Screens import DashboardScreen from '../screens/DashboardScreen'; import WalletScreen from '../screens/WalletScreen'; +import SwapScreen from '../screens/SwapScreen'; import BeCitizenScreen from '../screens/BeCitizenScreen'; import ReferralScreen from '../screens/ReferralScreen'; import ProfileScreen from '../screens/ProfileScreen'; @@ -13,6 +14,7 @@ import ProfileScreen from '../screens/ProfileScreen'; export type BottomTabParamList = { Home: undefined; Wallet: undefined; + Swap: undefined; BeCitizen: undefined; Referral: undefined; Profile: undefined; @@ -70,6 +72,18 @@ const BottomTabNavigator: React.FC = () => { }} /> + ( + + {focused ? '🔄' : '↔️'} + + ), + }} + /> + { + const { t } = useTranslation(); + const { api, isApiReady, selectedAccount, getKeyPair } = usePolkadot(); + + const [state, setState] = useState({ + fromToken: null, + toToken: null, + fromAmount: '', + toAmount: '', + slippage: 1, // 1% default slippage + loading: false, + swapping: false, + }); + + const [balances, setBalances] = useState<{ [key: string]: string }>({}); + const [poolReserves, setPoolReserves] = useState<{ + reserve1: string; + reserve2: string; + } | null>(null); + const [priceImpact, setPriceImpact] = useState('0'); + const [settingsModalVisible, setSettingsModalVisible] = useState(false); + const [tempSlippage, setTempSlippage] = useState('1'); + + // Fetch user balances for all tokens + const fetchBalances = useCallback(async () => { + if (!api || !isApiReady || !selectedAccount) return; + + try { + const newBalances: { [key: string]: string } = {}; + + // Fetch HEZ (native) balance + const { data } = await api.query.system.account(selectedAccount.address); + newBalances.HEZ = formatTokenBalance(data.free.toString(), 12, 4); + + // Fetch asset balances + for (const token of AVAILABLE_TOKENS) { + if (token.assetId !== undefined) { + try { + const assetData = await api.query.assets.account( + token.assetId, + selectedAccount.address + ); + + if (assetData.isSome) { + const balance = assetData.unwrap().balance.toString(); + newBalances[token.symbol] = formatTokenBalance( + balance, + token.decimals, + 4 + ); + } else { + newBalances[token.symbol] = '0.0000'; + } + } catch (error) { + console.log(`No balance for ${token.symbol}`); + newBalances[token.symbol] = '0.0000'; + } + } + } + + setBalances(newBalances); + } catch (error) { + console.error('Failed to fetch balances:', error); + } + }, [api, isApiReady, selectedAccount]); + + // Fetch pool reserves + const fetchPoolReserves = useCallback(async () => { + if ( + !api || + !isApiReady || + !state.fromToken || + !state.toToken || + state.fromToken.assetId === undefined || + state.toToken.assetId === undefined + ) { + return; + } + + try { + setState((prev) => ({ ...prev, loading: true })); + + // Get pool account + const poolAccount = await api.query.assetConversion.pools([ + state.fromToken.assetId, + state.toToken.assetId, + ]); + + if (poolAccount.isNone) { + Alert.alert('Pool Not Found', 'No liquidity pool exists for this pair.'); + setPoolReserves(null); + setState((prev) => ({ ...prev, loading: false })); + return; + } + + // Get reserves + const reserve1Data = await api.query.assets.account( + state.fromToken.assetId, + poolAccount.unwrap() + ); + const reserve2Data = await api.query.assets.account( + state.toToken.assetId, + poolAccount.unwrap() + ); + + const reserve1 = reserve1Data.isSome + ? reserve1Data.unwrap().balance.toString() + : '0'; + const reserve2 = reserve2Data.isSome + ? reserve2Data.unwrap().balance.toString() + : '0'; + + setPoolReserves({ reserve1, reserve2 }); + setState((prev) => ({ ...prev, loading: false })); + } catch (error) { + console.error('Failed to fetch pool reserves:', error); + Alert.alert('Error', 'Failed to fetch pool information.'); + setState((prev) => ({ ...prev, loading: false })); + } + }, [api, isApiReady, state.fromToken, state.toToken]); + + // Calculate output amount when input changes + useEffect(() => { + if ( + !state.fromAmount || + !state.fromToken || + !state.toToken || + !poolReserves + ) { + setState((prev) => ({ ...prev, toAmount: '' })); + setPriceImpact('0'); + return; + } + + try { + const fromAmountRaw = parseTokenInput( + state.fromAmount, + state.fromToken.decimals + ); + + if (fromAmountRaw === '0') { + setState((prev) => ({ ...prev, toAmount: '' })); + setPriceImpact('0'); + return; + } + + // Calculate output amount + const toAmountRaw = getAmountOut( + fromAmountRaw, + poolReserves.reserve1, + poolReserves.reserve2, + 30 // 0.3% fee + ); + + const toAmountFormatted = formatTokenBalance( + toAmountRaw, + state.toToken.decimals, + 6 + ); + + // Calculate price impact + const impact = calculatePriceImpact( + poolReserves.reserve1, + poolReserves.reserve2, + fromAmountRaw + ); + + setState((prev) => ({ ...prev, toAmount: toAmountFormatted })); + setPriceImpact(impact); + } catch (error) { + console.error('Calculation error:', error); + setState((prev) => ({ ...prev, toAmount: '' })); + } + }, [state.fromAmount, state.fromToken, state.toToken, poolReserves]); + + // Load balances on mount + useEffect(() => { + fetchBalances(); + }, [fetchBalances]); + + // Load pool reserves when tokens change + useEffect(() => { + if (state.fromToken && state.toToken) { + fetchPoolReserves(); + } + }, [state.fromToken, state.toToken, fetchPoolReserves]); + + // Handle token selection + const handleFromTokenSelect = (token: Token) => { + // Prevent selecting same token + if (state.toToken && token.symbol === state.toToken.symbol) { + setState((prev) => ({ + ...prev, + fromToken: token, + toToken: null, + fromAmount: '', + toAmount: '', + })); + } else { + setState((prev) => ({ + ...prev, + fromToken: token, + fromAmount: '', + toAmount: '', + })); + } + }; + + const handleToTokenSelect = (token: Token) => { + // Prevent selecting same token + if (state.fromToken && token.symbol === state.fromToken.symbol) { + setState((prev) => ({ + ...prev, + toToken: token, + fromToken: null, + fromAmount: '', + toAmount: '', + })); + } else { + setState((prev) => ({ + ...prev, + toToken: token, + fromAmount: '', + toAmount: '', + })); + } + }; + + // Swap token positions + const handleSwapTokens = () => { + setState((prev) => ({ + ...prev, + fromToken: prev.toToken, + toToken: prev.fromToken, + fromAmount: prev.toAmount, + toAmount: prev.fromAmount, + })); + }; + + // Execute swap + const handleSwap = async () => { + if ( + !api || + !isApiReady || + !selectedAccount || + !state.fromToken || + !state.toToken || + !state.fromAmount || + !state.toAmount || + state.fromToken.assetId === undefined || + state.toToken.assetId === undefined + ) { + Alert.alert('Error', 'Please fill in all fields.'); + return; + } + + try { + setState((prev) => ({ ...prev, swapping: true })); + + // Get keypair for signing + const keyPair = await getKeyPair(selectedAccount.address); + if (!keyPair) { + throw new Error('Failed to load keypair'); + } + + // Parse amounts + const amountIn = parseTokenInput( + state.fromAmount, + state.fromToken.decimals + ); + const amountOutExpected = parseTokenInput( + state.toAmount, + state.toToken.decimals + ); + const amountOutMin = calculateMinAmount( + amountOutExpected, + state.slippage + ); + + // Create swap path + const path = [state.fromToken.assetId, state.toToken.assetId]; + + console.log('Swap params:', { + path, + amountIn, + amountOutMin, + slippage: state.slippage, + }); + + // Create transaction + const tx = api.tx.assetConversion.swapTokensForExactTokens( + path, + amountOutMin, + amountIn, + selectedAccount.address, + false // keep_alive + ); + + // Sign and send + await new Promise((resolve, reject) => { + let unsub: (() => void) | undefined; + + tx.signAndSend(keyPair, ({ status, events, dispatchError }) => { + console.log('Transaction status:', status.type); + + if (dispatchError) { + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule + ); + reject( + new Error(`${decoded.section}.${decoded.name}: ${decoded.docs}`) + ); + } else { + reject(new Error(dispatchError.toString())); + } + if (unsub) unsub(); + return; + } + + if (status.isInBlock || status.isFinalized) { + console.log('Transaction included in block'); + resolve(); + if (unsub) unsub(); + } + }) + .then((unsubscribe) => { + unsub = unsubscribe; + }) + .catch(reject); + }); + + // Success! + Alert.alert( + 'Swap Successful', + `Swapped ${state.fromAmount} ${state.fromToken.symbol} for ${state.toAmount} ${state.toToken.symbol}`, + [ + { + text: 'OK', + onPress: () => { + // Reset form + setState((prev) => ({ + ...prev, + fromAmount: '', + toAmount: '', + swapping: false, + })); + // Refresh balances + fetchBalances(); + }, + }, + ] + ); + } catch (error: any) { + console.error('Swap failed:', error); + Alert.alert('Swap Failed', error.message || 'An error occurred.'); + setState((prev) => ({ ...prev, swapping: false })); + } + }; + + // Save slippage settings + const handleSaveSettings = () => { + const slippageValue = parseFloat(tempSlippage); + if (isNaN(slippageValue) || slippageValue < 0.1 || slippageValue > 50) { + Alert.alert('Invalid Slippage', 'Please enter a value between 0.1% and 50%'); + return; + } + setState((prev) => ({ ...prev, slippage: slippageValue })); + setSettingsModalVisible(false); + }; + + const availableFromTokens = AVAILABLE_TOKENS.map((token) => ({ + ...token, + balance: balances[token.symbol] || '0.0000', + })); + + const availableToTokens = AVAILABLE_TOKENS.filter( + (token) => token.symbol !== state.fromToken?.symbol + ).map((token) => ({ + ...token, + balance: balances[token.symbol] || '0.0000', + })); + + const canSwap = + !state.swapping && + !state.loading && + state.fromToken && + state.toToken && + state.fromAmount && + state.toAmount && + parseFloat(state.fromAmount) > 0 && + selectedAccount; + + const impactLevel = + parseFloat(priceImpact) < 1 + ? 'low' + : parseFloat(priceImpact) < 3 + ? 'medium' + : 'high'; + + return ( + + + {/* Header */} + + Swap Tokens + { + setTempSlippage(state.slippage.toString()); + setSettingsModalVisible(true); + }} + > + ⚙️ + + + + {!isApiReady && ( + + Connecting to blockchain... + + )} + + {!selectedAccount && ( + + Please connect your wallet + + )} + + {/* Swap Card */} + + {/* From Token */} + + + + + setState((prev) => ({ ...prev, fromAmount: text })) + } + placeholder="0.00" + keyboardType="decimal-pad" + editable={!state.loading && !state.swapping} + /> + + {state.fromToken && ( + + Balance: {balances[state.fromToken.symbol] || '0.0000'}{' '} + {state.fromToken.symbol} + + )} + + + {/* Swap Button */} + + + + + + + {/* To Token */} + + + + + + {state.toToken && ( + + Balance: {balances[state.toToken.symbol] || '0.0000'}{' '} + {state.toToken.symbol} + + )} + + + + {/* Swap Details */} + {state.fromToken && state.toToken && state.toAmount && ( + + Swap Details + + + Price Impact + + {priceImpact}% + + + + + Slippage Tolerance + {state.slippage}% + + + + Minimum Received + + {formatTokenBalance( + calculateMinAmount( + parseTokenInput(state.toAmount, state.toToken.decimals), + state.slippage + ), + state.toToken.decimals, + 6 + )}{' '} + {state.toToken.symbol} + + + + + Fee (0.3%) + + {formatTokenBalance( + ( + BigInt( + parseTokenInput(state.fromAmount, state.fromToken.decimals) + ) * + BigInt(30) / + BigInt(10000) + ).toString(), + state.fromToken.decimals, + 6 + )}{' '} + {state.fromToken.symbol} + + + + )} + + {/* Swap Button */} + + + + {/* Settings Modal */} + setSettingsModalVisible(false)} + > + + + Swap Settings + + Slippage Tolerance (%) + + + + {['0.5', '1.0', '2.0', '5.0'].map((value) => ( + setTempSlippage(value)} + > + + {value}% + + + ))} + + + + setSettingsModalVisible(false)} + > + Cancel + + + + Save + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: AppColors.background, + }, + scrollView: { + flex: 1, + }, + scrollContent: { + padding: 16, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 20, + }, + title: { + fontSize: 28, + fontWeight: '700', + color: '#000', + }, + settingsButton: { + padding: 8, + }, + settingsIcon: { + fontSize: 24, + }, + warningCard: { + padding: 16, + marginBottom: 16, + backgroundColor: '#FFF3CD', + borderColor: '#FFE69C', + }, + warningText: { + fontSize: 14, + color: '#856404', + textAlign: 'center', + }, + swapCard: { + padding: 20, + marginBottom: 16, + }, + swapSection: { + marginBottom: 8, + }, + amountInput: { + fontSize: 32, + fontWeight: '700', + color: '#000', + padding: 16, + backgroundColor: '#F5F5F5', + borderRadius: 12, + marginTop: 8, + borderWidth: 1, + borderColor: '#E0E0E0', + }, + disabledInput: { + opacity: 0.6, + }, + balanceText: { + fontSize: 12, + color: '#666', + marginTop: 4, + textAlign: 'right', + }, + swapIconContainer: { + alignItems: 'center', + marginVertical: 8, + }, + swapIcon: { + width: 40, + height: 40, + borderRadius: 20, + backgroundColor: KurdistanColors.kesk, + justifyContent: 'center', + alignItems: 'center', + }, + swapIconText: { + fontSize: 24, + color: '#FFFFFF', + }, + detailsCard: { + padding: 16, + marginBottom: 16, + }, + detailsTitle: { + fontSize: 16, + fontWeight: '700', + color: '#000', + marginBottom: 12, + }, + detailRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 8, + borderBottomWidth: 1, + borderBottomColor: '#F0F0F0', + }, + detailLabel: { + fontSize: 14, + color: '#666', + }, + detailValue: { + fontSize: 14, + fontWeight: '600', + color: '#000', + }, + highImpact: { + color: KurdistanColors.sor, + }, + mediumImpact: { + color: KurdistanColors.zer, + }, + swapButton: { + marginTop: 8, + }, + modalContainer: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + padding: 20, + }, + modalContent: { + backgroundColor: '#FFFFFF', + borderRadius: 20, + padding: 24, + width: '100%', + maxWidth: 400, + }, + modalTitle: { + fontSize: 20, + fontWeight: '700', + color: '#000', + marginBottom: 20, + }, + inputLabel: { + fontSize: 14, + fontWeight: '600', + color: '#666', + marginBottom: 8, + }, + settingsInput: { + fontSize: 18, + padding: 16, + backgroundColor: '#F5F5F5', + borderRadius: 12, + borderWidth: 1, + borderColor: '#E0E0E0', + marginBottom: 16, + }, + presetContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 24, + }, + presetButton: { + flex: 1, + padding: 12, + backgroundColor: '#F5F5F5', + borderRadius: 8, + borderWidth: 1, + borderColor: '#E0E0E0', + marginHorizontal: 4, + }, + presetButtonActive: { + backgroundColor: KurdistanColors.kesk, + borderColor: KurdistanColors.kesk, + }, + presetText: { + fontSize: 14, + fontWeight: '600', + color: '#666', + textAlign: 'center', + }, + presetTextActive: { + color: '#FFFFFF', + }, + modalButtons: { + flexDirection: 'row', + gap: 12, + }, + modalButton: { + flex: 1, + padding: 16, + borderRadius: 12, + alignItems: 'center', + }, + cancelButton: { + backgroundColor: '#F5F5F5', + }, + cancelButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#666', + }, + saveButton: { + backgroundColor: KurdistanColors.kesk, + }, + saveButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#FFFFFF', + }, +}); + +export default SwapScreen;