mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-24 17:47:55 +00:00
Fix all shadow deprecation warnings across entire mobile app
- Replaced shadowColor/shadowOffset/shadowOpacity/shadowRadius with boxShadow - Fixed 28 files (21 screens + 7 components) - Preserved elevation for Android compatibility - All React Native Web deprecation warnings resolved Files fixed: - All screen components - All reusable components - Navigation components - Modal components
This commit is contained in:
@@ -0,0 +1,527 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
TextInput,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
import { KurdistanColors } from '../theme/colors';
|
||||
import { usePezkuwi } from '../contexts/PezkuwiContext';
|
||||
|
||||
interface PoolInfo {
|
||||
id: string;
|
||||
asset1: number;
|
||||
asset2: number;
|
||||
asset1Symbol: string;
|
||||
asset2Symbol: string;
|
||||
asset1Decimals: number;
|
||||
asset2Decimals: number;
|
||||
reserve1: string;
|
||||
reserve2: string;
|
||||
feeRate?: string;
|
||||
volume24h?: string;
|
||||
apr7d?: string;
|
||||
}
|
||||
|
||||
const PoolBrowserScreen: React.FC = () => {
|
||||
const { api, isApiReady } = usePezkuwi();
|
||||
|
||||
const [pools, setPools] = useState<PoolInfo[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const fetchPools = async () => {
|
||||
if (!api || !isApiReady) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Fetch all pools from chain
|
||||
const poolsEntries = await api.query.assetConversion.pools.entries();
|
||||
|
||||
const poolsData: PoolInfo[] = [];
|
||||
|
||||
for (const [key, value] of poolsEntries) {
|
||||
const poolAccount = value.toString();
|
||||
|
||||
// Parse pool assets from key
|
||||
const keyData = key.toHuman() as any;
|
||||
const assets = keyData[0];
|
||||
|
||||
if (!assets || assets.length !== 2) continue;
|
||||
|
||||
const asset1 = parseInt(assets[0]);
|
||||
const asset2 = parseInt(assets[1]);
|
||||
|
||||
// Fetch metadata for both assets
|
||||
let asset1Symbol = asset1 === 0 ? 'wHEZ' : 'Unknown';
|
||||
let asset2Symbol = asset2 === 0 ? 'wHEZ' : 'Unknown';
|
||||
let asset1Decimals = 12;
|
||||
let asset2Decimals = 12;
|
||||
|
||||
try {
|
||||
if (asset1 !== 0) {
|
||||
const metadata1 = await api.query.assets.metadata(asset1);
|
||||
const meta1 = metadata1.toJSON() as any;
|
||||
asset1Symbol = meta1.symbol || `Asset ${asset1}`;
|
||||
asset1Decimals = meta1.decimals || 12;
|
||||
}
|
||||
|
||||
if (asset2 !== 0) {
|
||||
const metadata2 = await api.query.assets.metadata(asset2);
|
||||
const meta2 = metadata2.toJSON() as any;
|
||||
asset2Symbol = meta2.symbol || `Asset ${asset2}`;
|
||||
asset2Decimals = meta2.decimals || 12;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch asset metadata:', error);
|
||||
}
|
||||
|
||||
// Fetch pool reserves
|
||||
let reserve1 = '0';
|
||||
let reserve2 = '0';
|
||||
|
||||
try {
|
||||
if (asset1 === 0) {
|
||||
// Native token (wHEZ)
|
||||
const balance1 = await api.query.system.account(poolAccount);
|
||||
reserve1 = balance1.data.free.toString();
|
||||
} else {
|
||||
const balance1 = await api.query.assets.account(asset1, poolAccount);
|
||||
reserve1 = balance1.isSome ? balance1.unwrap().balance.toString() : '0';
|
||||
}
|
||||
|
||||
if (asset2 === 0) {
|
||||
const balance2 = await api.query.system.account(poolAccount);
|
||||
reserve2 = balance2.data.free.toString();
|
||||
} else {
|
||||
const balance2 = await api.query.assets.account(asset2, poolAccount);
|
||||
reserve2 = balance2.isSome ? balance2.unwrap().balance.toString() : '0';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch reserves:', error);
|
||||
}
|
||||
|
||||
poolsData.push({
|
||||
id: `${asset1}-${asset2}`,
|
||||
asset1,
|
||||
asset2,
|
||||
asset1Symbol,
|
||||
asset2Symbol,
|
||||
asset1Decimals,
|
||||
asset2Decimals,
|
||||
reserve1,
|
||||
reserve2,
|
||||
feeRate: '0.3', // 0.3% default
|
||||
volume24h: 'N/A',
|
||||
apr7d: 'N/A',
|
||||
});
|
||||
}
|
||||
|
||||
setPools(poolsData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load pools:', error);
|
||||
Alert.alert('Error', 'Failed to load liquidity pools');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPools();
|
||||
|
||||
// Refresh pools every 10 seconds
|
||||
const interval = setInterval(fetchPools, 10000);
|
||||
return () => clearInterval(interval);
|
||||
}, [api, isApiReady]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
setRefreshing(true);
|
||||
fetchPools();
|
||||
};
|
||||
|
||||
const filteredPools = pools.filter((pool) => {
|
||||
if (!searchTerm) return true;
|
||||
const search = searchTerm.toLowerCase();
|
||||
return (
|
||||
pool.asset1Symbol.toLowerCase().includes(search) ||
|
||||
pool.asset2Symbol.toLowerCase().includes(search) ||
|
||||
pool.id.toLowerCase().includes(search)
|
||||
);
|
||||
});
|
||||
|
||||
const formatBalance = (balance: string, decimals: number): string => {
|
||||
return (Number(balance) / Math.pow(10, decimals)).toFixed(2);
|
||||
};
|
||||
|
||||
const calculateExchangeRate = (pool: PoolInfo): string => {
|
||||
const reserve1Num = Number(pool.reserve1);
|
||||
const reserve2Num = Number(pool.reserve2);
|
||||
|
||||
if (reserve1Num === 0) return '0';
|
||||
|
||||
const rate = reserve2Num / reserve1Num;
|
||||
return rate.toFixed(4);
|
||||
};
|
||||
|
||||
const handleAddLiquidity = (pool: PoolInfo) => {
|
||||
Alert.alert('Add Liquidity', `Adding liquidity to ${pool.asset1Symbol}/${pool.asset2Symbol} pool`);
|
||||
// TODO: Navigate to AddLiquidityModal
|
||||
};
|
||||
|
||||
const handleRemoveLiquidity = (pool: PoolInfo) => {
|
||||
Alert.alert('Remove Liquidity', `Removing liquidity from ${pool.asset1Symbol}/${pool.asset2Symbol} pool`);
|
||||
// TODO: Navigate to RemoveLiquidityModal
|
||||
};
|
||||
|
||||
const handleSwap = (pool: PoolInfo) => {
|
||||
Alert.alert('Swap', `Swapping in ${pool.asset1Symbol}/${pool.asset2Symbol} pool`);
|
||||
// TODO: Navigate to SwapScreen with pool pre-selected
|
||||
};
|
||||
|
||||
if (loading && pools.length === 0) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.centerContent}>
|
||||
<ActivityIndicator size="large" color={KurdistanColors.kesk} />
|
||||
<Text style={styles.loadingText}>Loading liquidity pools...</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<ScrollView
|
||||
style={styles.scrollContent}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
|
||||
}
|
||||
>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>Liquidity Pools</Text>
|
||||
</View>
|
||||
|
||||
{/* Search Bar */}
|
||||
<View style={styles.searchContainer}>
|
||||
<Text style={styles.searchIcon}>🔍</Text>
|
||||
<TextInput
|
||||
style={styles.searchInput}
|
||||
placeholder="Search pools by token..."
|
||||
placeholderTextColor="#999"
|
||||
value={searchTerm}
|
||||
onChangeText={setSearchTerm}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Pools List */}
|
||||
{filteredPools.length === 0 ? (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyIcon}>💧</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
{searchTerm
|
||||
? 'No pools found matching your search'
|
||||
: 'No liquidity pools available yet'}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.poolsList}>
|
||||
{filteredPools.map((pool) => (
|
||||
<View key={pool.id} style={styles.poolCard}>
|
||||
{/* Pool Header */}
|
||||
<View style={styles.poolHeader}>
|
||||
<View style={styles.poolTitleRow}>
|
||||
<Text style={styles.poolAsset1}>{pool.asset1Symbol}</Text>
|
||||
<Text style={styles.poolSeparator}>/</Text>
|
||||
<Text style={styles.poolAsset2}>{pool.asset2Symbol}</Text>
|
||||
</View>
|
||||
<View style={styles.activeBadge}>
|
||||
<Text style={styles.activeBadgeText}>Active</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Reserves */}
|
||||
<View style={styles.reservesSection}>
|
||||
<View style={styles.reserveRow}>
|
||||
<Text style={styles.reserveLabel}>Reserve {pool.asset1Symbol}</Text>
|
||||
<Text style={styles.reserveValue}>
|
||||
{formatBalance(pool.reserve1, pool.asset1Decimals)} {pool.asset1Symbol}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.reserveRow}>
|
||||
<Text style={styles.reserveLabel}>Reserve {pool.asset2Symbol}</Text>
|
||||
<Text style={styles.reserveValue}>
|
||||
{formatBalance(pool.reserve2, pool.asset2Decimals)} {pool.asset2Symbol}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Exchange Rate */}
|
||||
<View style={styles.exchangeRateCard}>
|
||||
<Text style={styles.exchangeRateLabel}>Exchange Rate</Text>
|
||||
<Text style={styles.exchangeRateValue}>
|
||||
1 {pool.asset1Symbol} = {calculateExchangeRate(pool)} {pool.asset2Symbol}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Stats Row */}
|
||||
<View style={styles.statsRow}>
|
||||
<View style={styles.statBox}>
|
||||
<Text style={styles.statLabel}>Fee</Text>
|
||||
<Text style={styles.statValue}>{pool.feeRate}%</Text>
|
||||
</View>
|
||||
<View style={styles.statBox}>
|
||||
<Text style={styles.statLabel}>Volume 24h</Text>
|
||||
<Text style={styles.statValue}>{pool.volume24h}</Text>
|
||||
</View>
|
||||
<View style={styles.statBox}>
|
||||
<Text style={styles.statLabel}>APR</Text>
|
||||
<Text style={[styles.statValue, styles.statValuePositive]}>
|
||||
{pool.apr7d}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<View style={styles.actionButtons}>
|
||||
<TouchableOpacity
|
||||
style={[styles.actionButton, styles.addButton]}
|
||||
onPress={() => handleAddLiquidity(pool)}
|
||||
>
|
||||
<Text style={styles.actionButtonText}>💧 Add</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.actionButton, styles.removeButton]}
|
||||
onPress={() => handleRemoveLiquidity(pool)}
|
||||
>
|
||||
<Text style={styles.actionButtonText}>Remove</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.actionButton, styles.swapButton]}
|
||||
onPress={() => handleSwap(pool)}
|
||||
>
|
||||
<Text style={styles.actionButtonText}>📈 Swap</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F8F9FA',
|
||||
},
|
||||
centerContent: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 40,
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 16,
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
},
|
||||
scrollContent: {
|
||||
flex: 1,
|
||||
},
|
||||
header: {
|
||||
padding: 20,
|
||||
paddingBottom: 16,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
},
|
||||
searchContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#FFFFFF',
|
||||
marginHorizontal: 20,
|
||||
marginBottom: 20,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
boxShadow: '0px 1px 4px rgba(0, 0, 0, 0.05)',
|
||||
elevation: 2,
|
||||
},
|
||||
searchIcon: {
|
||||
fontSize: 18,
|
||||
marginRight: 8,
|
||||
},
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
color: '#333',
|
||||
},
|
||||
emptyContainer: {
|
||||
padding: 40,
|
||||
alignItems: 'center',
|
||||
},
|
||||
emptyIcon: {
|
||||
fontSize: 64,
|
||||
marginBottom: 16,
|
||||
},
|
||||
emptyText: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
},
|
||||
poolsList: {
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
},
|
||||
poolCard: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.05)',
|
||||
elevation: 2,
|
||||
},
|
||||
poolHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
poolTitleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
poolAsset1: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: KurdistanColors.kesk,
|
||||
},
|
||||
poolSeparator: {
|
||||
fontSize: 18,
|
||||
color: '#999',
|
||||
marginHorizontal: 4,
|
||||
},
|
||||
poolAsset2: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#F59E0B',
|
||||
},
|
||||
activeBadge: {
|
||||
backgroundColor: 'rgba(0, 143, 67, 0.1)',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(0, 143, 67, 0.3)',
|
||||
},
|
||||
activeBadgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: KurdistanColors.kesk,
|
||||
},
|
||||
reservesSection: {
|
||||
gap: 8,
|
||||
marginBottom: 16,
|
||||
},
|
||||
reserveRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
reserveLabel: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
},
|
||||
reserveValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
exchangeRateCard: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
marginBottom: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
exchangeRateLabel: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
},
|
||||
exchangeRateValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#3B82F6',
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
statsRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
paddingTop: 16,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#E5E5E5',
|
||||
marginBottom: 16,
|
||||
},
|
||||
statBox: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 11,
|
||||
color: '#999',
|
||||
marginBottom: 4,
|
||||
},
|
||||
statValue: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
statValuePositive: {
|
||||
color: KurdistanColors.kesk,
|
||||
},
|
||||
actionButtons: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
actionButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
},
|
||||
addButton: {
|
||||
backgroundColor: 'rgba(0, 143, 67, 0.1)',
|
||||
borderColor: 'rgba(0, 143, 67, 0.3)',
|
||||
},
|
||||
removeButton: {
|
||||
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
||||
borderColor: 'rgba(239, 68, 68, 0.3)',
|
||||
},
|
||||
swapButton: {
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
borderColor: 'rgba(59, 130, 246, 0.3)',
|
||||
},
|
||||
actionButtonText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
});
|
||||
|
||||
export default PoolBrowserScreen;
|
||||
Reference in New Issue
Block a user