Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions veilend-mobile/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ export default function App() {
<StatusBar style="light" />

{anyLoading && (
<View style={styles.loadingOverlay} pointerEvents="none">
<ActivityIndicator size="large" color="#fff" />
<View
style={styles.loadingOverlay}
pointerEvents="none"
accessible={true}
accessibilityRole="progressbar"
accessibilityLabel="Loading, please wait"
accessibilityLiveRegion="polite"
>
<ActivityIndicator size="large" color="#fff" importantForAccessibility="no" />
</View>
)}

Expand Down
25 changes: 18 additions & 7 deletions veilend-mobile/src/components/ProtocolStatusBanners.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<View
Expand All @@ -47,22 +48,27 @@ export default function ProtocolStatusBanners({
styles.banner,
banner.severity === 'danger' ? styles.danger : styles.warning,
]}
// Screen readers announce this as an alert when it appears
accessibilityRole="alert"
accessibilityLiveRegion="polite"
accessibilityLabel={`${banner.title}. ${banner.message}`}
>
<Ionicons name={iconName} size={20} color="#FFFFFF" />
<Ionicons name={iconName} size={20} color="#FFFFFF" importantForAccessibility="no" />
<View style={styles.copy}>
<Text style={styles.title}>{banner.title}</Text>
<Text style={styles.message}>{banner.message}</Text>
{/* Text is included in the parent's accessibilityLabel; hide from individual focus */}
<Text style={styles.title} importantForAccessibility="no">{banner.title}</Text>
<Text style={styles.message} importantForAccessibility="no">{banner.message}</Text>
</View>
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={banner.actionLabel}
accessibilityLabel={actionLabel}
accessibilityHint={isWalletBanner ? 'Reconnect your wallet' : 'Retry syncing protocol status'}
disabled={isRefreshing && !isWalletBanner}
accessibilityState={{ disabled: isRefreshing && !isWalletBanner }}
onPress={onPress}
style={styles.action}
>
<Text style={styles.actionText}>
{isRefreshing && !isWalletBanner ? 'Checking...' : banner.actionLabel}
</Text>
<Text style={styles.actionText}>{actionLabel}</Text>
</TouchableOpacity>
</View>
);
Expand Down Expand Up @@ -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',
Expand Down
29 changes: 29 additions & 0 deletions veilend-mobile/src/hooks/useResponsive.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
44 changes: 37 additions & 7 deletions veilend-mobile/src/navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,53 @@ 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 <Ionicons name={iconName} size={size} color={color} />;
return <Ionicons name={iconName} size={size} color={color} importantForAccessibility="no" />;
},
})}
>
<Tab.Screen name="Dashboard" component={DashboardScreen} />
<Tab.Screen name="Deposit" component={DepositScreen} />
<Tab.Screen name="Borrow" component={BorrowScreen} />
<Tab.Screen name="Repay" component={RepayScreen} />
<Tab.Screen
name="Dashboard"
component={DashboardScreen}
options={{
tabBarAccessibilityLabel: 'Dashboard, home overview',
}}
/>
<Tab.Screen
name="Deposit"
component={DepositScreen}
options={{
tabBarAccessibilityLabel: 'Deposit, supply assets to the market',
}}
/>
<Tab.Screen
name="Borrow"
component={BorrowScreen}
options={{
tabBarAccessibilityLabel: 'Borrow, take out a loan from the market',
}}
/>
<Tab.Screen
name="Repay"
component={RepayScreen}
options={{
tabBarAccessibilityLabel: 'Repay, pay back borrowed assets',
}}
/>
</Tab.Navigator>
);
}

/** Splash shown while session is being restored from SecureStore */
function SessionRestoreSplash() {
return (
<View style={styles.splash}>
<ActivityIndicator size="large" color="#09cc71" />
<View
style={styles.splash}
accessible={true}
accessibilityRole="progressbar"
accessibilityLabel="Restoring your session, please wait"
accessibilityLiveRegion="polite"
>
<ActivityIndicator size="large" color="#09cc71" importantForAccessibility="no" />
</View>
);
}
Expand Down
Loading
Loading