|
5 | 5 | */ |
6 | 6 | import { useMemo, useState } from 'react'; |
7 | 7 | import { FlatList, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; |
| 8 | +import { gatewayQuality } from '@cumulusvpn/core'; |
| 9 | +import type { QualityTone } from '@cumulusvpn/core'; |
8 | 10 | import type { Country } from '../lib/gateways'; |
9 | | -import { latencyBand } from '../lib/gateways'; |
10 | | -import { LatencyDot } from '../components/LatencyDot'; |
11 | 11 | import { color, font, radius, space } from '../theme/tokens'; |
12 | 12 |
|
| 13 | +/** Quality-tone → accent colour (green best … red busiest). */ |
| 14 | +const TONE_COLOR: Record<QualityTone, string> = { |
| 15 | + excellent: color.green, |
| 16 | + good: color.cyan, |
| 17 | + fair: color.amber, |
| 18 | + busy: color.red, |
| 19 | +}; |
| 20 | + |
13 | 21 | interface Props { |
14 | 22 | readonly countries: readonly Country[]; |
15 | 23 | readonly selectedCode: string | null; |
@@ -61,30 +69,36 @@ export function CountryPickerScreen({ |
61 | 69 | ListEmptyComponent={ |
62 | 70 | <Text style={styles.empty}>No gateways reachable — pull to refresh.</Text> |
63 | 71 | } |
64 | | - renderItem={({ item }) => ( |
65 | | - <Pressable |
66 | | - style={[styles.row, item.code === selectedCode && styles.rowSelected]} |
67 | | - onPress={() => { |
68 | | - onSelect(item.code); |
69 | | - onClose(); |
70 | | - }} |
71 | | - accessibilityRole="button" |
72 | | - > |
73 | | - <Text style={styles.flag}>{item.flag}</Text> |
74 | | - <View style={styles.meta}> |
75 | | - <Text style={styles.name}>{item.name}</Text> |
76 | | - <Text style={styles.sub}> |
77 | | - {item.nodeCount} {item.nodeCount === 1 ? 'node' : 'nodes'} · {item.city} |
78 | | - </Text> |
79 | | - </View> |
80 | | - <View style={styles.ping}> |
81 | | - <LatencyDot band={latencyBand(item.latencyMs)} /> |
82 | | - <Text style={styles.pingText}> |
83 | | - {item.latencyMs === null ? '—' : `${item.latencyMs} ms`} |
84 | | - </Text> |
85 | | - </View> |
86 | | - </Pressable> |
87 | | - )} |
| 72 | + renderItem={({ item }) => { |
| 73 | + const q = gatewayQuality(item.latencyMs, item.best.load); |
| 74 | + return ( |
| 75 | + <Pressable |
| 76 | + style={[styles.row, item.code === selectedCode && styles.rowSelected]} |
| 77 | + onPress={() => { |
| 78 | + onSelect(item.code); |
| 79 | + onClose(); |
| 80 | + }} |
| 81 | + accessibilityRole="button" |
| 82 | + > |
| 83 | + <Text style={styles.flag}>{item.flag}</Text> |
| 84 | + <View style={styles.meta}> |
| 85 | + <Text style={styles.name}>{item.name}</Text> |
| 86 | + <Text style={styles.sub}> |
| 87 | + {item.nodeCount} {item.nodeCount === 1 ? 'node' : 'nodes'} · {item.city} |
| 88 | + </Text> |
| 89 | + </View> |
| 90 | + <View style={styles.qual}> |
| 91 | + <View style={styles.qualTop}> |
| 92 | + <View style={[styles.qualDot, { backgroundColor: TONE_COLOR[q.tone] }]} /> |
| 93 | + <Text style={[styles.qualLabel, { color: TONE_COLOR[q.tone] }]}>{q.label}</Text> |
| 94 | + </View> |
| 95 | + <Text style={styles.qualSub}> |
| 96 | + {item.latencyMs === null ? '— ms' : `${item.latencyMs} ms`} · {q.loadPct}% load |
| 97 | + </Text> |
| 98 | + </View> |
| 99 | + </Pressable> |
| 100 | + ); |
| 101 | + }} |
88 | 102 | /> |
89 | 103 | </View> |
90 | 104 | ); |
@@ -129,7 +143,10 @@ const styles = StyleSheet.create({ |
129 | 143 | meta: { flex: 1 }, |
130 | 144 | name: { color: color.ink, fontSize: 15, fontWeight: '600' }, |
131 | 145 | sub: { color: color.inkDim, fontSize: 12, marginTop: 2 }, |
132 | | - ping: { flexDirection: 'row', alignItems: 'center' }, |
133 | | - pingText: { fontFamily: font.mono, fontSize: 12, color: color.inkMuted }, |
| 146 | + qual: { alignItems: 'flex-end' }, |
| 147 | + qualTop: { flexDirection: 'row', alignItems: 'center', gap: 5 }, |
| 148 | + qualDot: { width: 7, height: 7, borderRadius: 4 }, |
| 149 | + qualLabel: { fontSize: 12.5, fontWeight: '600' }, |
| 150 | + qualSub: { fontFamily: font.mono, fontSize: 10.5, color: color.inkFaint, marginTop: 2 }, |
134 | 151 | empty: { color: color.inkDim, textAlign: 'center', marginTop: 40, fontSize: 14 }, |
135 | 152 | }); |
0 commit comments