north-star / commit
removed extra list of friends
mobile/src/components/arrows_panel.tsx | 64 ++--------------- mobile/src/features/friends/sync.ts | 11 +-- mobile/src/screens/room_session_screen.tsx | 109 +++++++++++++++-------------- 3 files changed, 70 insertions(+), 114 deletions(-) diff --git a/mobile/src/components/arrows_panel.tsx b/mobile/src/components/arrows_panel.tsx index eec655c..872cbab 100644 --- a/mobile/src/components/arrows_panel.tsx +++ b/mobile/src/components/arrows_panel.tsx @@ -6,19 +6,17 @@ export type ArrowFriend = { name: string; color: string; arrowDeg: number | null; - dist: string | null; }; type ArrowsPanelProps = { friends: ArrowFriend[]; }; -// Arrow needle tip pointing up (north), centered at (0,0). -// Drawn in a ±1 unit coordinate space, scaled via SVG transform. -const NEEDLE_TIP = 'M 0 -36 L 8 -10 L 0 -16 L -8 -10 Z'; -const NEEDLE_TAIL = 'M 0 16 L 5 6 L 0 10 L -5 6 Z'; +// Needle pointing up (north), centered at (0,0), sized for the larger compass. +const NEEDLE_TIP = 'M 0 -105 L 22 -30 L 0 -50 L -22 -30 Z'; +const NEEDLE_TAIL = 'M 0 45 L 14 16 L 0 28 L -14 16 Z'; -const COMPASS_SIZE = 260; +const COMPASS_SIZE = 310; const CENTER = COMPASS_SIZE / 2; export function ArrowsPanel({ friends }: ArrowsPanelProps) { @@ -49,8 +47,8 @@ export function ArrowsPanel({ friends }: ArrowsPanelProps) { {/* Cardinal tick marks */} {[0, 90, 180, 270].map(deg => { const rad = (deg - 90) * (Math.PI / 180); - const inner = CENTER - 20; - const outer = CENTER - 6; + const inner = CENTER - 26; + const outer = CENTER - 8; return ( <Line key={deg} @@ -64,7 +62,7 @@ export function ArrowsPanel({ friends }: ArrowsPanelProps) { ); })} {/* Center dot */} - <Circle cx={CENTER} cy={CENTER} r={6} fill="rgba(0,0,0,0.15)" /> + <Circle cx={CENTER} cy={CENTER} r={8} fill="rgba(0,0,0,0.18)" /> {/* One needle per friend */} {friends.map((friend) => { @@ -92,22 +90,6 @@ export function ArrowsPanel({ friends }: ArrowsPanelProps) { )} </Svg> </View> - - {/* Legend: one row per friend */} - <View style={styles.legend}> - {friends.map((friend) => ( - <View key={friend.id} style={styles.legendRow}> - <View style={[styles.legendDot, { backgroundColor: friend.color }]} /> - <Text style={styles.legendName} numberOfLines={1}>{friend.name}</Text> - {friend.dist !== null && ( - <Text style={styles.legendDist}>{friend.dist}</Text> - )} - {friend.arrowDeg === null && ( - <Text style={styles.legendNoPos}>no position</Text> - )} - </View> - ))} - </View> </View> ); } @@ -115,44 +97,12 @@ export function ArrowsPanel({ friends }: ArrowsPanelProps) { const styles = StyleSheet.create({ wrap: { alignItems: 'center', - gap: 16, paddingVertical: 4 }, compassOuter: { borderRadius: COMPASS_SIZE / 2, overflow: 'hidden' }, - legend: { - alignSelf: 'stretch', - gap: 8, - paddingHorizontal: 4 - }, - legendRow: { - flexDirection: 'row', - alignItems: 'center', - gap: 8 - }, - legendDot: { - width: 12, - height: 12, - borderRadius: 6, - flexShrink: 0 - }, - legendName: { - flex: 1, - fontSize: 14, - fontWeight: '600', - color: '#2e2a23' - }, - legendDist: { - fontSize: 13, - color: '#7a7469' - }, - legendNoPos: { - fontSize: 12, - color: '#9a8f84', - fontStyle: 'italic' - }, emptyWrap: { paddingHorizontal: 8, paddingVertical: 12 diff --git a/mobile/src/features/friends/sync.ts b/mobile/src/features/friends/sync.ts index c27155a..eec2e00 100644 --- a/mobile/src/features/friends/sync.ts +++ b/mobile/src/features/friends/sync.ts @@ -79,9 +79,12 @@ export const subscribeToFriends = ({ async (snapshot) => { try { const value = snapshot.val() as Record<string, MobileFriendDbRecord> | null; - const raw = value ? Object.values(value) : []; + const raw = value ? Object.entries(value) : []; + + const resolved = await Promise.all(raw.map(async ([key, friend]): Promise<MobileFriendRecord | null> => { + // Filter self by Firebase key — most reliable, works for any write format + if (key === selfUid) return null; - const resolved = await Promise.all(raw.map(async (friend): Promise<MobileFriendRecord | null> => { if (friend.encryptedState) { if (!roomKey) return null; try { @@ -106,10 +109,10 @@ export const subscribeToFriends = ({ } } - return friend; + return { ...friend, id: key }; })); - const nextFriends = resolved.filter((item): item is MobileFriendRecord => item !== null && item.id !== selfUid); + const nextFriends = resolved.filter((item): item is MobileFriendRecord => item !== null); nextFriends.sort((a, b) => { const aName = (a.name || '').toLowerCase(); const bName = (b.name || '').toLowerCase(); diff --git a/mobile/src/screens/room_session_screen.tsx b/mobile/src/screens/room_session_screen.tsx index 31a0c9a..8f1a0c7 100644 --- a/mobile/src/screens/room_session_screen.tsx +++ b/mobile/src/screens/room_session_screen.tsx @@ -11,7 +11,7 @@ import { import AsyncStorage from '@react-native-async-storage/async-storage'; import QRCode from 'react-native-qrcode-svg'; import { ArrowsPanel, type ArrowFriend } from '../components/arrows_panel'; -import { formatDistance, getBearingDeg, getDistanceMeters, getRelativeBearingDeg } from '../core/bearing'; +import { getBearingDeg, getRelativeBearingDeg } from '../core/bearing'; import { parseRoomFromHash } from '../core/room'; import type { SymmetricKey } from '../core/crypto'; import { watchHeading } from '../platform/heading'; @@ -78,10 +78,7 @@ export function RoomSessionScreen({ const arrowDeg = bearingDeg !== null ? getRelativeBearingDeg(bearingDeg, headingDeg) : null; - const dist = position && f.position - ? formatDistance(getDistanceMeters(position, f.position)) - : null; - return { id: f.id, name: f.name || f.id, color: f.color ?? '#888888', arrowDeg, dist }; + return { id: f.id, name: f.name || f.id, color: f.color ?? '#888888', arrowDeg }; }); }, [orderedFriends, hiddenFriendIds, position, headingDeg]); @@ -264,54 +261,52 @@ export function RoomSessionScreen({ {locationStatus === 'watching' && !position && <Text style={styles.valueMuted}>waiting for GPS…</Text>} </View> <ArrowsPanel friends={arrowFriends} /> + {/* Friend rows with controls */} + <View style={styles.friendsList}> + {orderedFriends.map((friend, index) => { + const isHidden = hiddenFriendIds.has(friend.id); + const hasPos = !!friend.position; + return ( + <View key={friend.id} style={styles.friendRow}> + <View style={[styles.friendDot, { backgroundColor: friend.color ?? '#888888', opacity: isHidden ? 0.35 : 1 }]} /> + <Text style={[styles.value, styles.friendName, isHidden && styles.friendNameHidden]} numberOfLines={1}> + {friend.name || friend.id} + </Text> + {!hasPos && !isHidden && <Text style={styles.noPos}>no pos</Text>} + <Pressable + onPress={() => toggleHidden(friend.id)} + style={({ pressed }) => [styles.hideButton, pressed && styles.buttonPressed]} + > + <Text style={styles.hideButtonText}>{isHidden ? 'show' : 'hide'}</Text> + </Pressable> + {orderedFriends.length > 1 && ( + <View style={styles.moveButtons}> + <Pressable + onPress={() => moveUp(index)} + disabled={index === 0} + style={[styles.moveButton, index === 0 && styles.moveButtonDim]} + > + <Text style={styles.moveButtonText}>▲</Text> + </Pressable> + <Pressable + onPress={() => moveDown(index)} + disabled={index === orderedFriends.length - 1} + style={[styles.moveButton, index === orderedFriends.length - 1 && styles.moveButtonDim]} + > + <Text style={styles.moveButtonText}>▼</Text> + </Pressable> + </View> + )} + </View> + ); + })} + {orderedFriends.length === 0 && firebaseSession && ( + <Text style={styles.valueMuted}>No friends in room yet.</Text> + )} + </View> {!!syncError && <Text style={styles.errorText}>{syncError}</Text>} </View> - {/* ── Friends list ── */} - <View style={styles.card}> - <Text style={styles.label}> - {orderedFriends.length > 0 ? `Friends (${orderedFriends.length})` : 'Friends'} - </Text> - {orderedFriends.map((friend, index) => { - const isHidden = hiddenFriendIds.has(friend.id); - return ( - <View key={friend.id} style={styles.friendRow}> - <View style={[styles.friendDot, { backgroundColor: friend.color ?? '#888888', opacity: isHidden ? 0.35 : 1 }]} /> - <Text style={[styles.value, styles.friendName, isHidden && styles.friendNameHidden]}> - {friend.name || friend.id} - </Text> - <Pressable - onPress={() => toggleHidden(friend.id)} - style={({ pressed }) => [styles.hideButton, pressed && styles.buttonPressed]} - > - <Text style={styles.hideButtonText}>{isHidden ? 'show' : 'hide'}</Text> - </Pressable> - {orderedFriends.length > 1 && ( - <View style={styles.moveButtons}> - <Pressable - onPress={() => moveUp(index)} - disabled={index === 0} - style={[styles.moveButton, index === 0 && styles.moveButtonDim]} - > - <Text style={styles.moveButtonText}>▲</Text> - </Pressable> - <Pressable - onPress={() => moveDown(index)} - disabled={index === orderedFriends.length - 1} - style={[styles.moveButton, index === orderedFriends.length - 1 && styles.moveButtonDim]} - > - <Text style={styles.moveButtonText}>▼</Text> - </Pressable> - </View> - )} - </View> - ); - })} - {orderedFriends.length === 0 && firebaseSession && ( - <Text style={styles.valueMuted}>No friends in room yet.</Text> - )} - </View> - {/* ── Invite QR ── */} <View style={styles.card}> <Pressable @@ -393,6 +388,10 @@ const styles = StyleSheet.create({ gap: 10, paddingHorizontal: 4 }, + friendsList: { + gap: 10, + paddingHorizontal: 4 + }, connectingRowInline: { flexDirection: 'row', alignItems: 'center', @@ -418,12 +417,11 @@ const styles = StyleSheet.create({ color: '#2e2a23' }, secondaryButton: { - borderWidth: 1, - borderColor: '#7a4f2a', + backgroundColor: 'rgba(255,252,245,0.92)', borderRadius: 10, alignItems: 'center', paddingVertical: 8, - paddingHorizontal: 12 + paddingHorizontal: 14 }, secondaryButtonText: { color: '#7a4f2a', @@ -461,6 +459,11 @@ const styles = StyleSheet.create({ friendNameHidden: { opacity: 0.4 }, + noPos: { + fontSize: 11, + color: '#9a8f84', + fontStyle: 'italic' + }, hideButton: { paddingHorizontal: 10, paddingVertical: 4,