diff --git a/veilend-mobile/App.tsx b/veilend-mobile/App.tsx index 8d938be..afbe5e7 100644 --- a/veilend-mobile/App.tsx +++ b/veilend-mobile/App.tsx @@ -25,8 +25,15 @@ export default function App() { {anyLoading && ( - - + + )} diff --git a/veilend-mobile/src/components/ProtocolStatusBanners.tsx b/veilend-mobile/src/components/ProtocolStatusBanners.tsx index b808ac9..85d3711 100644 --- a/veilend-mobile/src/components/ProtocolStatusBanners.tsx +++ b/veilend-mobile/src/components/ProtocolStatusBanners.tsx @@ -39,6 +39,7 @@ export default function ProtocolStatusBanners({ const isWalletBanner = banner.id === 'wallet-disconnected'; const onPress = isWalletBanner ? onReconnect : onRetrySync; const iconName = isWalletBanner ? 'wallet-outline' : 'warning-outline'; + const actionLabel = isRefreshing && !isWalletBanner ? 'Checking...' : banner.actionLabel; return ( - + - {banner.title} - {banner.message} + {/* Text is included in the parent's accessibilityLabel; hide from individual focus */} + {banner.title} + {banner.message} - - {isRefreshing && !isWalletBanner ? 'Checking...' : banner.actionLabel} - + {actionLabel} ); @@ -113,6 +119,11 @@ const styles = StyleSheet.create({ borderRadius: 999, paddingHorizontal: 10, paddingVertical: 7, + // Ensure minimum touch target + minWidth: 44, + minHeight: 44, + alignItems: 'center', + justifyContent: 'center', }, actionText: { color: '#FFFFFF', diff --git a/veilend-mobile/src/hooks/useResponsive.ts b/veilend-mobile/src/hooks/useResponsive.ts new file mode 100644 index 0000000..4e68f80 --- /dev/null +++ b/veilend-mobile/src/hooks/useResponsive.ts @@ -0,0 +1,29 @@ +/** + * useResponsive + * + * Returns responsive helpers derived from the current window dimensions. + * Re-renders when the device rotates or the window size changes (e.g. on + * foldables or when running in a split-screen context). + * + * Usage: + * const { isSmallScreen, width } = useResponsive(); + */ +import { useWindowDimensions } from 'react-native'; + +export type ResponsiveValues = { + /** Current window width in dp */ + width: number; + /** Current window height in dp */ + height: number; + /** True when the screen is narrower than 380 dp */ + isSmallScreen: boolean; +}; + +export function useResponsive(): ResponsiveValues { + const { width, height } = useWindowDimensions(); + return { + width, + height, + isSmallScreen: width < 380, + }; +} diff --git a/veilend-mobile/src/navigation/index.tsx b/veilend-mobile/src/navigation/index.tsx index f583173..b9834d1 100644 --- a/veilend-mobile/src/navigation/index.tsx +++ b/veilend-mobile/src/navigation/index.tsx @@ -31,14 +31,38 @@ function MainTabs() { else if (route.name === 'Deposit') iconName = focused ? 'arrow-down' : 'arrow-down-outline'; else if (route.name === 'Borrow') iconName = focused ? 'cash' : 'cash-outline'; else if (route.name === 'Repay') iconName = focused ? 'arrow-up' : 'arrow-up-outline'; - return ; + return ; }, })} > - - - - + + + + ); } @@ -46,8 +70,14 @@ function MainTabs() { /** Splash shown while session is being restored from SecureStore */ function SessionRestoreSplash() { return ( - - + + ); } diff --git a/veilend-mobile/src/screens/BorrowScreen.tsx b/veilend-mobile/src/screens/BorrowScreen.tsx index 4dcbf05..ad0a739 100644 --- a/veilend-mobile/src/screens/BorrowScreen.tsx +++ b/veilend-mobile/src/screens/BorrowScreen.tsx @@ -5,11 +5,14 @@ import { MOCK_ASSETS } from '../data/mockData'; import { useStore } from '../store/store'; import { ActivityIndicator } from 'react-native'; import Toast from '../utils/toast'; +import { useResponsive } from '../hooks/useResponsive'; type SelectedAsset = { id: string; name: string; symbol: string } | null; - export default function BorrowScreen() { + const { isSmallScreen } = useResponsive(); + const styles = makeStyles(isSmallScreen); + const [modalVisible, setModalVisible] = useState(false); const [selectedAsset, setSelectedAsset] = useState(null); const [amount, setAmount] = useState('1'); @@ -33,19 +36,25 @@ export default function BorrowScreen() { setModalVisible(false); } }; + return ( <> Borrow Market {/* Borrow Stats */} - - + + Total Borrowed $1,250 - - + + Borrow Limit $8,500 @@ -59,10 +68,13 @@ export default function BorrowScreen() { key={asset.id} style={styles.assetCard} onPress={() => openBorrowModal(asset)} + accessibilityRole="button" + accessibilityLabel={`Borrow ${asset.name} (${asset.symbol}), ${(asset.apy + 2).toFixed(2)}% APR, 10 million available`} + accessibilityHint="Double tap to open borrow amount dialog" > - + {asset.name} @@ -84,186 +96,210 @@ export default function BorrowScreen() { + {/* Amount Modal */} - setModalVisible(false)} - > - - - Borrow {selectedAsset?.symbol} - - - setModalVisible(false)} style={[styles.modalBtn, { backgroundColor: '#333' }]}> - Cancel - - - {useStore.getState().lendingLoading ? : Confirm} - - + setModalVisible(false)} + accessibilityViewIsModal={true} + > + + + Borrow {selectedAsset?.symbol} + + + setModalVisible(false)} + style={[styles.modalBtn, { backgroundColor: '#333' }]} + accessibilityRole="button" + accessibilityLabel="Cancel borrow" + > + Cancel + + + {useStore.getState().lendingLoading + ? + : Confirm} + - - - - ); + + + + + ); } -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#0A0A0A', - padding: 24, - paddingTop: 60, - }, - headerTitle: { - fontSize: 28, - fontWeight: 'bold', - color: '#FFFFFF', - marginBottom: 24, - }, - statsContainer: { - flexDirection: 'row', - backgroundColor: '#121212', - borderRadius: 16, - padding: 20, - marginBottom: 32, - borderWidth: 1, - borderColor: '#222', - }, - statBox: { - flex: 1, - alignItems: 'center', - }, - statDivider: { - width: 1, - backgroundColor: '#333', - marginHorizontal: 16, - }, - statLabel: { - color: '#A1A1A1', - marginBottom: 8, - fontSize: 14, - }, - statValue: { - color: '#FFFFFF', - fontSize: 24, - fontWeight: 'bold', - }, - sectionTitle: { - color: '#FFFFFF', - fontSize: 18, - fontWeight: 'bold', - marginBottom: 16, - }, - assetsList: { - gap: 16, - }, - assetCard: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - backgroundColor: '#121212', - padding: 16, - borderRadius: 16, - borderWidth: 1, - borderColor: '#222', - }, - assetLeft: { - flexDirection: 'row', - alignItems: 'center', - gap: 16, - }, - iconContainer: { - width: 48, - height: 48, - backgroundColor: '#1A1A1A', - borderRadius: 24, - justifyContent: 'center', - alignItems: 'center', - borderWidth: 1, - borderColor: '#333', - }, - assetName: { - color: '#FFFFFF', - fontSize: 16, - fontWeight: '600', - marginBottom: 4, - }, - assetSymbol: { - color: '#666', - fontSize: 14, - }, - assetRight: { - alignItems: 'flex-end', - }, - aprBadge: { - backgroundColor: 'rgba(255, 99, 99, 0.15)', - paddingHorizontal: 10, - paddingVertical: 4, - borderRadius: 8, - marginBottom: 4, - }, - aprText: { - color: '#FF6363', - fontWeight: 'bold', - fontSize: 12, - }, - available: { - color: '#A1A1A1', - fontSize: 14, - }, - modalOverlay: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.7)', - justifyContent: 'flex-end', - }, - modalContent: { - backgroundColor: '#121212', - padding: 24, - borderTopLeftRadius: 24, - borderTopRightRadius: 24, - borderWidth: 1, - borderColor: '#222', - gap: 16, - }, - modalTitle: { - color: '#FFFFFF', - fontSize: 22, - fontWeight: 'bold', - }, - amountInput: { - backgroundColor: '#1A1A1A', - borderWidth: 1, - borderColor: '#333', - borderRadius: 16, - paddingHorizontal: 16, - paddingVertical: 14, - color: '#FFFFFF', - fontSize: 16, - }, - modalButtons: { - flexDirection: 'row', - gap: 12, - }, - modalBtn: { - flex: 1, - paddingVertical: 16, - borderRadius: 16, - alignItems: 'center', - justifyContent: 'center', - }, - buttonText: { - color: '#FFFFFF', - fontWeight: 'bold', - fontSize: 16, - }, -}); +function makeStyles(isSmallScreen: boolean) { + return StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0A0A0A', + padding: isSmallScreen ? 16 : 24, + paddingTop: 60, + }, + headerTitle: { + fontSize: isSmallScreen ? 22 : 28, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 24, + }, + statsContainer: { + flexDirection: 'row', + backgroundColor: '#121212', + borderRadius: 16, + padding: isSmallScreen ? 14 : 20, + marginBottom: 32, + borderWidth: 1, + borderColor: '#222', + }, + statBox: { + flex: 1, + alignItems: 'center', + }, + statDivider: { + width: 1, + backgroundColor: '#333', + marginHorizontal: 16, + }, + statLabel: { + color: '#A1A1A1', + marginBottom: 8, + fontSize: isSmallScreen ? 12 : 14, + }, + statValue: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 18 : 24, + fontWeight: 'bold', + }, + sectionTitle: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 16 : 18, + fontWeight: 'bold', + marginBottom: 16, + }, + assetsList: { + gap: 16, + }, + assetCard: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: '#121212', + padding: isSmallScreen ? 12 : 16, + borderRadius: 16, + borderWidth: 1, + borderColor: '#222', + minHeight: 72, + }, + assetLeft: { + flexDirection: 'row', + alignItems: 'center', + gap: isSmallScreen ? 10 : 16, + flexShrink: 1, + }, + iconContainer: { + width: isSmallScreen ? 40 : 48, + height: isSmallScreen ? 40 : 48, + backgroundColor: '#1A1A1A', + borderRadius: 24, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 1, + borderColor: '#333', + }, + assetName: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 14 : 16, + fontWeight: '600', + marginBottom: 4, + }, + assetSymbol: { + color: '#666', + fontSize: isSmallScreen ? 12 : 14, + }, + assetRight: { + alignItems: 'flex-end', + flexShrink: 0, + }, + aprBadge: { + backgroundColor: 'rgba(255, 99, 99, 0.15)', + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 8, + marginBottom: 4, + }, + aprText: { + color: '#FF6363', + fontWeight: 'bold', + fontSize: 12, + }, + available: { + color: '#A1A1A1', + fontSize: isSmallScreen ? 12 : 14, + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + justifyContent: 'flex-end', + }, + modalContent: { + backgroundColor: '#121212', + padding: isSmallScreen ? 16 : 24, + borderTopLeftRadius: 24, + borderTopRightRadius: 24, + borderWidth: 1, + borderColor: '#222', + gap: 16, + }, + modalTitle: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 18 : 22, + fontWeight: 'bold', + }, + amountInput: { + backgroundColor: '#1A1A1A', + borderWidth: 1, + borderColor: '#333', + borderRadius: 16, + paddingHorizontal: 16, + paddingVertical: 14, + color: '#FFFFFF', + fontSize: 16, + }, + modalButtons: { + flexDirection: 'row', + gap: 12, + }, + modalBtn: { + flex: 1, + paddingVertical: isSmallScreen ? 14 : 16, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + minHeight: 48, + }, + buttonText: { + color: '#FFFFFF', + fontWeight: 'bold', + fontSize: isSmallScreen ? 14 : 16, + }, + }); +} diff --git a/veilend-mobile/src/screens/ConnectWalletScreen.tsx b/veilend-mobile/src/screens/ConnectWalletScreen.tsx index 31245e7..07249b8 100644 --- a/veilend-mobile/src/screens/ConnectWalletScreen.tsx +++ b/veilend-mobile/src/screens/ConnectWalletScreen.tsx @@ -112,6 +112,10 @@ export default function ConnectWalletScreen() { activeOpacity={0.8} onPress={generateWallet} disabled={loading} + accessibilityRole="button" + accessibilityLabel="Generate new wallet" + accessibilityHint="Creates a new Stellar wallet and connects to the app" + accessibilityState={{ disabled: loading }} > setMode('import')} disabled={loading} + accessibilityRole="button" + accessibilityLabel="Import existing wallet using secret key" + accessibilityHint="Switch to the import wallet form" + accessibilityState={{ disabled: loading }} > Already have a wallet?{' '} @@ -151,17 +159,27 @@ export default function ConnectWalletScreen() { autoCapitalize="characters" autoCorrect={false} secureTextEntry={false} + accessibilityLabel="Stellar secret key" + accessibilityHint="Enter your Stellar secret key starting with S to import your wallet" /> {error ? ( - {error} + {error} ) : null} importWallet(secretKey)} disabled={loading || !secretKey.trim()} + accessibilityRole="button" + accessibilityLabel="Connect wallet" + accessibilityHint="Imports and connects your Stellar wallet using the provided secret key" + accessibilityState={{ disabled: loading || !secretKey.trim() }} > { setMode('choose'); setSecretKey(''); }} disabled={loading} + accessibilityRole="button" + accessibilityLabel="Back to wallet options" + accessibilityHint="Returns to the generate or import wallet choice" + accessibilityState={{ disabled: loading }} > ← Back diff --git a/veilend-mobile/src/screens/DashboardScreen.tsx b/veilend-mobile/src/screens/DashboardScreen.tsx index 65e3828..5185ca7 100644 --- a/veilend-mobile/src/screens/DashboardScreen.tsx +++ b/veilend-mobile/src/screens/DashboardScreen.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState, useRef } from 'react'; -import { View, Text, ScrollView, StyleSheet, TouchableOpacity, Image, Modal, TextInput, TouchableWithoutFeedback, Keyboard, KeyboardAvoidingView, Platform, Dimensions, FlatList, ActivityIndicator } from 'react-native'; -import api from '../utils/api'; +import { View, Text, ScrollView, StyleSheet, TouchableOpacity, Image, Modal, TextInput, TouchableWithoutFeedback, Keyboard, FlatList, ActivityIndicator } from 'react-native'; import { useStore, TransactionRecord } from '../store/store'; import { LinearGradient } from 'expo-linear-gradient'; import { Ionicons } from '@expo/vector-icons'; @@ -17,6 +16,10 @@ const DEFAULT_PROFILE_IMAGE = 'https://i.pravatar.cc/100?img=5'; export default function DashboardScreen({ navigation }: any) { + const { width, isSmallScreen } = useResponsive(); + const CARD_WIDTH = width - 48; // Padding 24 * 2 + const styles = makeStyles(isSmallScreen); + const { address, authToken, @@ -68,8 +71,13 @@ export default function DashboardScreen({ navigation }: any) { if (portfolioError) { return ( - {portfolioError} - + {portfolioError} + Retry @@ -141,16 +149,35 @@ export default function DashboardScreen({ navigation }: any) { }; const ServiceButton = ({ icon, label, onPress }: any) => ( - + - + - {label} + {label} ); - const renderCard = ({ item }: { item: any }) => ( - + const renderCard = ({ item }: { item: any }) => { + const displayValue = isPrivacyMode + ? '****' + : `$${Number(item.value).toLocaleString('en-US', { minimumFractionDigits: 2 })}`; + const cardA11yLabel = isPrivacyMode + ? `${item.label}: hidden in privacy mode` + : `${item.label}: ${displayValue}`; + + return ( + - {item.label} + {item.label} - + {isPrivacyMode ? '****' : `$${item.value.toLocaleString('en-US', { minimumFractionDigits: 2 })}` } - {/* Masked number eye icon */} - + {/* Masked number eye icon - decorative */} + {/* Added privacy shield top-right */} - - - ZK Shielded + + + ZK Shielded - {/* Bottom dots styled as small privacy "veils" with teal glow */} - + {/* Bottom dots styled as small privacy "veils" with teal glow - decorative */} + @@ -199,7 +226,7 @@ export default function DashboardScreen({ navigation }: any) { - ); + );}; const getGreeting = () => { const hour = new Date().getHours(); @@ -212,15 +239,31 @@ export default function DashboardScreen({ navigation }: any) { {/* Header */} - - {getGreeting()} - {username} + + {getGreeting()} + {username} - - + + - setProfileVisible(true)}> + setProfileVisible(true)} + accessibilityRole="button" + accessibilityLabel={`Profile menu for ${username}`} + accessibilityHint="Opens profile and settings" + > setProfileVisible(false)} + accessibilityViewIsModal={true} > - setProfileVisible(false)}> + setProfileVisible(false)} accessibilityLabel="Close profile menu"> Profile - setProfileVisible(false)}> - + setProfileVisible(false)} + accessibilityRole="button" + accessibilityLabel="Close profile menu" + > + {/* Profile Setup / Username */} - + Username @@ -272,19 +320,32 @@ export default function DashboardScreen({ navigation }: any) { onChangeText={setTempName} autoFocus placeholderTextColor="#555" + accessibilityLabel="Edit username" + accessibilityHint="Enter your new display name" /> - - + + ) : ( {username} - { - setTempName(username); - setIsEditingName(true); - }}> - + { + setTempName(username); + setIsEditingName(true); + }} + accessibilityRole="button" + accessibilityLabel={`Edit username, current name: ${username}`} + accessibilityHint="Opens an editable field to change your display name" + > + )} @@ -292,46 +353,46 @@ export default function DashboardScreen({ navigation }: any) { {/* Change Profile Picture */} - + - + Change Profile Picture Update your avatar - + {/* Account Preference */} - + - + Account Preferences Currency, Notifications, Privacy - + {/* Profile Setup (Generic) */} - + - + Profile Setup Complete your KYC/Verification - + {/* Log Out */} - - + + Log Out @@ -352,18 +413,26 @@ export default function DashboardScreen({ navigation }: any) { onViewableItemsChanged={onViewableItemsChanged} viewabilityConfig={viewabilityConfig} extraData={isPrivacyMode} + accessibilityLabel="Balance cards carousel" + accessibilityHint={`Swipe left or right to view ${CARDS.length} balance cards`} /> {/* Pagination Dots */} - - {CARDS.map((_, index) => ( + + {CARDS.map((card, index) => ( ))} @@ -394,26 +463,54 @@ export default function DashboardScreen({ navigation }: any) { {/* Transactions List */} - Transactions + Transactions - {transactions.map((tx: TransactionRecord) => ( - - - - - - - {tx.type} — {tx.asset} - {tx.timestamp} + {transactions.length === 0 && ( + + No transactions yet. + + )} + {transactions.map((tx: TransactionRecord) => { + const txTypeLabel = + tx.type === 'deposit' ? 'Deposited' : + tx.type === 'withdraw' ? 'Withdrew' : + tx.type === 'borrow' ? 'Borrowed' : + tx.type === 'repay' ? 'Repaid' : tx.type; + const txA11yLabel = isPrivacyMode + ? `${txTypeLabel} ${tx.asset}, amount hidden in privacy mode, ${tx.timestamp}, status: ${tx.status}` + : `${txTypeLabel} ${tx.amount} ${tx.asset}, ${tx.timestamp}, status: ${tx.status}`; + + return ( + + + + + + + {tx.type} — {tx.asset} + {tx.timestamp} + + + {isPrivacyMode ? '****' : `${tx.amount} ${tx.asset}`} + - {tx.amount} {tx.asset} - - ))} + ); + })} {/* Spacer for bottom tab bar */} @@ -422,7 +519,8 @@ export default function DashboardScreen({ navigation }: any) { ); } -const styles = StyleSheet.create({ +function makeStyles(s: boolean) { + return StyleSheet.create({ container: { flex: 1, backgroundColor: '#0A0A0A', @@ -509,8 +607,8 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0, 0, 0, 0.2)', - paddingHorizontal: isSmallScreen ? 8 : 10, - paddingVertical: isSmallScreen ? 4 : 6, + paddingHorizontal: s ? 8 : 10, + paddingVertical: s ? 4 : 6, borderRadius: 20, borderWidth: 1, borderColor: 'rgba(0, 209, 255, 0.3)', // Teal border @@ -521,9 +619,9 @@ const styles = StyleSheet.create({ }, privacyText: { color: '#D1D1D1', - fontSize: isSmallScreen ? 10 : 12, + fontSize: s ? 10 : 12, fontWeight: '600', - marginLeft: isSmallScreen ? 4 : 6, + marginLeft: s ? 4 : 6, }, cardNumberContainer: { flexDirection: 'row', @@ -581,10 +679,12 @@ const styles = StyleSheet.create({ }, serviceBtn: { alignItems: 'center', + minWidth: 64, + padding: 4, }, serviceIconBox: { - width: 56, - height: 56, + width: s ? 52 : 56, + height: s ? 52 : 56, backgroundColor: '#1A1A1A', borderRadius: 16, justifyContent: 'center', diff --git a/veilend-mobile/src/screens/DepositScreen.tsx b/veilend-mobile/src/screens/DepositScreen.tsx index a34ef3e..d275e18 100644 --- a/veilend-mobile/src/screens/DepositScreen.tsx +++ b/veilend-mobile/src/screens/DepositScreen.tsx @@ -5,10 +5,14 @@ import { MOCK_ASSETS } from '../data/mockData'; import { useStore } from '../store/store'; import { ActivityIndicator } from 'react-native'; import Toast from '../utils/toast'; +import { useResponsive } from '../hooks/useResponsive'; type SelectedAsset = { id: string; name: string; symbol: string; balance?: number } | null; export default function DepositScreen() { + const { isSmallScreen } = useResponsive(); + const styles = makeStyles(isSmallScreen); + const [modalVisible, setModalVisible] = useState(false); const [selectedAsset, setSelectedAsset] = useState(null); const [amount, setAmount] = useState(''); @@ -33,19 +37,25 @@ export default function DepositScreen() { setModalVisible(false); } }; + return ( <> Supply Market {/* Stats Header */} - - + + Net APY 4.25% - - + + Supply Balance $12,450 @@ -59,10 +69,13 @@ export default function DepositScreen() { key={asset.id} style={styles.assetCard} onPress={() => openDepositModal(asset)} + accessibilityRole="button" + accessibilityLabel={`Deposit ${asset.name} (${asset.symbol}), ${asset.apy}% APY, wallet balance: ${asset.balance} ${asset.symbol}`} + accessibilityHint="Double tap to open deposit amount dialog" > - + {asset.name} @@ -84,186 +97,210 @@ export default function DepositScreen() { + {/* Amount Modal */} - setModalVisible(false)} - > - - - Deposit {selectedAsset?.symbol} - - - setModalVisible(false)} style={[styles.modalBtn, { backgroundColor: '#333' }]}> - Cancel - - - {useStore.getState().lendingLoading ? : Confirm} - - + setModalVisible(false)} + accessibilityViewIsModal={true} + > + + + Deposit {selectedAsset?.symbol} + + + setModalVisible(false)} + style={[styles.modalBtn, { backgroundColor: '#333' }]} + accessibilityRole="button" + accessibilityLabel="Cancel deposit" + > + Cancel + + + {useStore.getState().lendingLoading + ? + : Confirm} + - - - - ); + + + + + ); } -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#0A0A0A', - padding: 24, - paddingTop: 60, - }, - headerTitle: { - fontSize: 28, - fontWeight: 'bold', - color: '#FFFFFF', - marginBottom: 24, - }, - statsContainer: { - flexDirection: 'row', - backgroundColor: '#121212', - borderRadius: 16, - padding: 20, - marginBottom: 32, - borderWidth: 1, - borderColor: '#222', - }, - statBox: { - flex: 1, - alignItems: 'center', - }, - statDivider: { - width: 1, - backgroundColor: '#333', - marginHorizontal: 16, - }, - statLabel: { - color: '#A1A1A1', - marginBottom: 8, - fontSize: 14, - }, - statValue: { - color: '#FFFFFF', - fontSize: 24, - fontWeight: 'bold', - }, - sectionTitle: { - color: '#FFFFFF', - fontSize: 18, - fontWeight: 'bold', - marginBottom: 16, - }, - assetsList: { - gap: 16, - }, - assetCard: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - backgroundColor: '#121212', - padding: 16, - borderRadius: 16, - borderWidth: 1, - borderColor: '#222', - }, - assetLeft: { - flexDirection: 'row', - alignItems: 'center', - gap: 16, - }, - iconContainer: { - width: 48, - height: 48, - backgroundColor: '#1A1A1A', - borderRadius: 24, - justifyContent: 'center', - alignItems: 'center', - borderWidth: 1, - borderColor: '#333', - }, - assetName: { - color: '#FFFFFF', - fontSize: 16, - fontWeight: '600', - marginBottom: 4, - }, - assetSymbol: { - color: '#666', - fontSize: 14, - }, - assetRight: { - alignItems: 'flex-end', - }, - apyBadge: { - backgroundColor: 'rgba(168, 85, 247, 0.15)', - paddingHorizontal: 10, - paddingVertical: 4, - borderRadius: 8, - marginBottom: 4, - }, - apyText: { - color: '#A855F7', - fontWeight: 'bold', - fontSize: 12, - }, - walletBalance: { - color: '#A1A1A1', - fontSize: 14, - }, - modalOverlay: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.7)', - justifyContent: 'flex-end', - }, - modalContent: { - backgroundColor: '#121212', - padding: 24, - borderTopLeftRadius: 24, - borderTopRightRadius: 24, - borderWidth: 1, - borderColor: '#222', - gap: 16, - }, - modalTitle: { - color: '#FFFFFF', - fontSize: 22, - fontWeight: 'bold', - }, - amountInput: { - backgroundColor: '#1A1A1A', - borderWidth: 1, - borderColor: '#333', - borderRadius: 16, - paddingHorizontal: 16, - paddingVertical: 14, - color: '#FFFFFF', - fontSize: 16, - }, - modalButtons: { - flexDirection: 'row', - gap: 12, - }, - modalBtn: { - flex: 1, - paddingVertical: 16, - borderRadius: 16, - alignItems: 'center', - justifyContent: 'center', - }, - buttonText: { - color: '#FFFFFF', - fontWeight: 'bold', - fontSize: 16, - }, -}); +function makeStyles(isSmallScreen: boolean) { + return StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0A0A0A', + padding: isSmallScreen ? 16 : 24, + paddingTop: 60, + }, + headerTitle: { + fontSize: isSmallScreen ? 22 : 28, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 24, + }, + statsContainer: { + flexDirection: 'row', + backgroundColor: '#121212', + borderRadius: 16, + padding: isSmallScreen ? 14 : 20, + marginBottom: 32, + borderWidth: 1, + borderColor: '#222', + }, + statBox: { + flex: 1, + alignItems: 'center', + }, + statDivider: { + width: 1, + backgroundColor: '#333', + marginHorizontal: 16, + }, + statLabel: { + color: '#A1A1A1', + marginBottom: 8, + fontSize: isSmallScreen ? 12 : 14, + }, + statValue: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 18 : 24, + fontWeight: 'bold', + }, + sectionTitle: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 16 : 18, + fontWeight: 'bold', + marginBottom: 16, + }, + assetsList: { + gap: 16, + }, + assetCard: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: '#121212', + padding: isSmallScreen ? 12 : 16, + borderRadius: 16, + borderWidth: 1, + borderColor: '#222', + minHeight: 72, + }, + assetLeft: { + flexDirection: 'row', + alignItems: 'center', + gap: isSmallScreen ? 10 : 16, + flexShrink: 1, + }, + iconContainer: { + width: isSmallScreen ? 40 : 48, + height: isSmallScreen ? 40 : 48, + backgroundColor: '#1A1A1A', + borderRadius: 24, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 1, + borderColor: '#333', + }, + assetName: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 14 : 16, + fontWeight: '600', + marginBottom: 4, + }, + assetSymbol: { + color: '#666', + fontSize: isSmallScreen ? 12 : 14, + }, + assetRight: { + alignItems: 'flex-end', + flexShrink: 0, + }, + apyBadge: { + backgroundColor: 'rgba(168, 85, 247, 0.15)', + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 8, + marginBottom: 4, + }, + apyText: { + color: '#A855F7', + fontWeight: 'bold', + fontSize: 12, + }, + walletBalance: { + color: '#A1A1A1', + fontSize: isSmallScreen ? 12 : 14, + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + justifyContent: 'flex-end', + }, + modalContent: { + backgroundColor: '#121212', + padding: isSmallScreen ? 16 : 24, + borderTopLeftRadius: 24, + borderTopRightRadius: 24, + borderWidth: 1, + borderColor: '#222', + gap: 16, + }, + modalTitle: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 18 : 22, + fontWeight: 'bold', + }, + amountInput: { + backgroundColor: '#1A1A1A', + borderWidth: 1, + borderColor: '#333', + borderRadius: 16, + paddingHorizontal: 16, + paddingVertical: 14, + color: '#FFFFFF', + fontSize: 16, + }, + modalButtons: { + flexDirection: 'row', + gap: 12, + }, + modalBtn: { + flex: 1, + paddingVertical: isSmallScreen ? 14 : 16, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + minHeight: 48, + }, + buttonText: { + color: '#FFFFFF', + fontWeight: 'bold', + fontSize: isSmallScreen ? 14 : 16, + }, + }); +} diff --git a/veilend-mobile/src/screens/RepayScreen.tsx b/veilend-mobile/src/screens/RepayScreen.tsx index bc7359f..936ab68 100644 --- a/veilend-mobile/src/screens/RepayScreen.tsx +++ b/veilend-mobile/src/screens/RepayScreen.tsx @@ -5,8 +5,12 @@ import { MOCK_POSITIONS } from '../data/mockData'; import { useStore } from '../store/store'; import { ActivityIndicator } from 'react-native'; import Toast from '../utils/toast'; +import { useResponsive } from '../hooks/useResponsive'; export default function RepayScreen() { + const { isSmallScreen } = useResponsive(); + const styles = makeStyles(isSmallScreen); + const activeLoans = MOCK_POSITIONS.filter(p => p.type === 'Borrowed'); const [modalVisible, setModalVisible] = useState(false); const [selectedLoan, setSelectedLoan] = useState(null); @@ -44,30 +48,51 @@ export default function RepayScreen() { - + {loan.asset} Debt - - Health: {loan.healthFactor} + + + Health: {loan.healthFactor} + - - Amount Owed - {loan.amount} {loan.asset} + + Amount Owed + {loan.amount} {loan.asset} - - Value - ${loan.value.toLocaleString()} + + Value + ${loan.value.toLocaleString()} - - Interest Accrued - $12.50 + + Interest Accrued + $12.50 @@ -81,6 +106,9 @@ export default function RepayScreen() { } openRepayModal(loan); }} + accessibilityRole="button" + accessibilityLabel={`Repay ${loan.amount} ${loan.asset}`} + accessibilityHint="Double tap to open repayment amount dialog" > Repay Now @@ -88,194 +116,225 @@ export default function RepayScreen() { ))} ) : ( - - - No active loans - You don't have any borrowed assets to repay. + + + No active loans + + You don't have any borrowed assets to repay. + )} + {/* Amount Modal */} - setModalVisible(false)} - > - - - Repay {selectedLoan?.asset} - - - setModalVisible(false)} style={[styles.modalBtn, { backgroundColor: '#333' }]}> - Cancel - - - {useStore.getState().lendingLoading ? : Confirm} - - + setModalVisible(false)} + accessibilityViewIsModal={true} + > + + + Repay {selectedLoan?.asset} + + + setModalVisible(false)} + style={[styles.modalBtn, { backgroundColor: '#333' }]} + accessibilityRole="button" + accessibilityLabel="Cancel repayment" + > + Cancel + + + {useStore.getState().lendingLoading + ? + : Confirm} + - - - - ); + + + + + ); } -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#0A0A0A', - padding: 24, - paddingTop: 60, - }, - headerTitle: { - fontSize: 28, - fontWeight: 'bold', - color: '#FFFFFF', - marginBottom: 24, - }, - loansList: { - gap: 16, - }, - loanCard: { - backgroundColor: '#121212', - borderRadius: 24, - padding: 20, - borderWidth: 1, - borderColor: '#222', - }, - cardHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 24, - }, - assetInfo: { - flexDirection: 'row', - alignItems: 'center', - gap: 12, - }, - iconContainer: { - width: 48, - height: 48, - backgroundColor: '#1A1A1A', - borderRadius: 16, - justifyContent: 'center', - alignItems: 'center', - borderWidth: 1, - borderColor: '#333', - }, - assetName: { - color: '#FFFFFF', - fontSize: 18, - fontWeight: 'bold', - }, - loanLabel: { - color: '#666', - fontSize: 12, - }, - healthBadge: { - backgroundColor: 'rgba(74, 222, 128, 0.1)', - paddingHorizontal: 12, - paddingVertical: 6, - borderRadius: 20, - borderWidth: 1, - borderColor: 'rgba(74, 222, 128, 0.2)', - }, - healthText: { - color: '#4ade80', - fontSize: 12, - fontWeight: 'bold', - }, - loanDetails: { - gap: 12, - marginBottom: 24, - }, - detailRow: { - flexDirection: 'row', - justifyContent: 'space-between', - }, - detailLabel: { - color: '#A1A1A1', - fontSize: 14, - }, - detailValue: { - color: '#FFFFFF', - fontSize: 14, - fontWeight: '600', - }, - repayButton: { - backgroundColor: '#A855F7', - paddingVertical: 16, - borderRadius: 16, - alignItems: 'center', - }, - buttonText: { - color: '#FFFFFF', - fontWeight: 'bold', - fontSize: 16, - }, - emptyState: { - alignItems: 'center', - marginTop: 60, - }, - emptyText: { - color: '#FFFFFF', - fontSize: 20, - fontWeight: 'bold', - marginTop: 16, - }, - emptySubtext: { - color: '#666', - fontSize: 14, - marginTop: 8, - textAlign: 'center', - }, - modalOverlay: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.7)', - justifyContent: 'flex-end', - }, - modalContent: { - backgroundColor: '#121212', - padding: 24, - borderTopLeftRadius: 24, - borderTopRightRadius: 24, - borderWidth: 1, - borderColor: '#222', - gap: 16, - }, - modalTitle: { - color: '#FFFFFF', - fontSize: 22, - fontWeight: 'bold', - }, - amountInput: { - backgroundColor: '#1A1A1A', - borderWidth: 1, - borderColor: '#333', - borderRadius: 16, - paddingHorizontal: 16, - paddingVertical: 14, - color: '#FFFFFF', - fontSize: 16, - }, - modalButtons: { - flexDirection: 'row', - gap: 12, - }, - modalBtn: { - flex: 1, - paddingVertical: 16, - borderRadius: 16, - alignItems: 'center', - justifyContent: 'center', - }, -}); +function makeStyles(isSmallScreen: boolean) { + return StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0A0A0A', + padding: isSmallScreen ? 16 : 24, + paddingTop: 60, + }, + headerTitle: { + fontSize: isSmallScreen ? 22 : 28, + fontWeight: 'bold', + color: '#FFFFFF', + marginBottom: 24, + }, + loansList: { + gap: 16, + }, + loanCard: { + backgroundColor: '#121212', + borderRadius: 24, + padding: isSmallScreen ? 14 : 20, + borderWidth: 1, + borderColor: '#222', + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 24, + flexWrap: 'wrap', + gap: 8, + }, + assetInfo: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + iconContainer: { + width: isSmallScreen ? 40 : 48, + height: isSmallScreen ? 40 : 48, + backgroundColor: '#1A1A1A', + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 1, + borderColor: '#333', + }, + assetName: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 15 : 18, + fontWeight: 'bold', + }, + loanLabel: { + color: '#666', + fontSize: 12, + }, + healthBadge: { + backgroundColor: 'rgba(74, 222, 128, 0.1)', + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 20, + borderWidth: 1, + borderColor: 'rgba(74, 222, 128, 0.2)', + }, + healthText: { + color: '#4ade80', + fontSize: 12, + fontWeight: 'bold', + }, + loanDetails: { + gap: 12, + marginBottom: 24, + }, + detailRow: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + detailLabel: { + color: '#A1A1A1', + fontSize: isSmallScreen ? 12 : 14, + }, + detailValue: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 12 : 14, + fontWeight: '600', + }, + repayButton: { + backgroundColor: '#A855F7', + paddingVertical: isSmallScreen ? 14 : 16, + borderRadius: 16, + alignItems: 'center', + minHeight: 48, + }, + buttonText: { + color: '#FFFFFF', + fontWeight: 'bold', + fontSize: isSmallScreen ? 14 : 16, + }, + emptyState: { + alignItems: 'center', + marginTop: 60, + }, + emptyText: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 17 : 20, + fontWeight: 'bold', + marginTop: 16, + }, + emptySubtext: { + color: '#666', + fontSize: isSmallScreen ? 12 : 14, + marginTop: 8, + textAlign: 'center', + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + justifyContent: 'flex-end', + }, + modalContent: { + backgroundColor: '#121212', + padding: isSmallScreen ? 16 : 24, + borderTopLeftRadius: 24, + borderTopRightRadius: 24, + borderWidth: 1, + borderColor: '#222', + gap: 16, + }, + modalTitle: { + color: '#FFFFFF', + fontSize: isSmallScreen ? 18 : 22, + fontWeight: 'bold', + }, + amountInput: { + backgroundColor: '#1A1A1A', + borderWidth: 1, + borderColor: '#333', + borderRadius: 16, + paddingHorizontal: 16, + paddingVertical: 14, + color: '#FFFFFF', + fontSize: 16, + }, + modalButtons: { + flexDirection: 'row', + gap: 12, + }, + modalBtn: { + flex: 1, + paddingVertical: isSmallScreen ? 14 : 16, + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + minHeight: 48, + }, + }); +}