diff --git a/App.tsx b/App.tsx
index eed2d1a..46e6dff 100644
--- a/App.tsx
+++ b/App.tsx
@@ -5,9 +5,10 @@
*/
import {ApolloProvider} from '@apollo/client';
-import {useEffect} from 'react';
+import {useEffect, useState} from 'react';
import {
ActivityIndicator,
+ Modal,
Pressable,
StatusBar,
StyleSheet,
@@ -22,7 +23,12 @@ import {
import {apolloClient, logout} from './src/auth/authClient';
import {useDeepLinkListener} from './src/auth/deepLinks';
import {LoginScreen} from './src/auth/LoginScreen';
-import {tokenStore, useAuth, useAuthHydrated} from './src/auth/tokenStore';
+import {
+ type AuthUser,
+ tokenStore,
+ useAuth,
+ useAuthHydrated,
+} from './src/auth/tokenStore';
import {MapScreen} from './src/map/MapScreen';
function App() {
@@ -41,7 +47,6 @@ function App() {
function AppContent() {
const hydrated = useAuthHydrated();
const auth = useAuth();
- const safeAreaInsets = useSafeAreaInsets();
useDeepLinkListener();
@@ -64,25 +69,63 @@ function AppContent() {
return (
-
- Signed in as {auth.user.email}
- {
- logout();
- }}
- style={({pressed}) => [
- styles.logoutButton,
- pressed && styles.logoutPressed,
- ]}>
- Log out
-
-
+
);
}
+function AccountMenu({user}: {user: AuthUser}) {
+ const safeAreaInsets = useSafeAreaInsets();
+ const [open, setOpen] = useState(false);
+
+ const initial = user.email.trim().charAt(0).toUpperCase() || '?';
+
+ return (
+ <>
+ setOpen(true)}
+ style={({pressed}) => [
+ styles.avatarButton,
+ {top: safeAreaInsets.top + 12},
+ pressed && styles.avatarPressed,
+ ]}>
+ {initial}
+
+ setOpen(false)}>
+ setOpen(false)}>
+ true}>
+
+
+ {user.email}
+
+ {
+ setOpen(false);
+ logout();
+ }}
+ style={({pressed}) => [
+ styles.sheetItem,
+ pressed && styles.sheetItemPressed,
+ ]}>
+ Log out
+
+
+
+
+ >
+ );
+}
+
const styles = StyleSheet.create({
container: {
flex: 1,
@@ -91,35 +134,63 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
- logoutBar: {
+ avatarButton: {
position: 'absolute',
- left: 0,
- right: 0,
- bottom: 0,
- paddingHorizontal: 16,
- paddingTop: 12,
- backgroundColor: 'rgba(255,255,255,0.95)',
- borderTopWidth: StyleSheet.hairlineWidth,
- borderTopColor: '#ccc',
- flexDirection: 'row',
+ right: 16,
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ backgroundColor: '#1d6fe0',
alignItems: 'center',
- justifyContent: 'space-between',
+ justifyContent: 'center',
+ shadowColor: '#000',
+ shadowOpacity: 0.2,
+ shadowRadius: 4,
+ shadowOffset: {width: 0, height: 2},
+ elevation: 4,
},
- signedInAs: {
- fontSize: 12,
- color: '#444',
+ avatarPressed: {opacity: 0.8},
+ avatarText: {
+ color: '#ffffff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ sheetBackdrop: {
flex: 1,
- marginRight: 12,
+ backgroundColor: 'rgba(0,0,0,0.35)',
+ justifyContent: 'flex-end',
+ },
+ sheet: {
+ backgroundColor: '#ffffff',
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ paddingTop: 8,
+ paddingHorizontal: 8,
},
- logoutButton: {
+ sheetHandle: {
+ alignSelf: 'center',
+ width: 36,
+ height: 4,
+ borderRadius: 2,
+ backgroundColor: '#d0d0d0',
+ marginBottom: 12,
+ },
+ sheetEmail: {
+ fontSize: 12,
+ color: '#666',
paddingHorizontal: 12,
- paddingVertical: 8,
- borderRadius: 6,
- backgroundColor: '#eee',
+ paddingBottom: 8,
+ },
+ sheetItem: {
+ paddingHorizontal: 12,
+ paddingVertical: 14,
+ borderRadius: 8,
+ },
+ sheetItemPressed: {
+ backgroundColor: '#f2f2f2',
},
- logoutPressed: {opacity: 0.7},
- logoutText: {
- fontSize: 14,
+ sheetItemText: {
+ fontSize: 16,
color: '#222',
},
});
diff --git a/src/map/MapScreen.tsx b/src/map/MapScreen.tsx
index c2a2094..f1d554f 100644
--- a/src/map/MapScreen.tsx
+++ b/src/map/MapScreen.tsx
@@ -29,10 +29,9 @@ const DEFAULT_INITIAL_VIEW = {center: [0, 20] as [number, number], zoom: 1};
// Show the recenter button once the viewport center drifts more than this
// fraction of the visible span away from the user in either axis.
const OFF_CENTER_THRESHOLD = 0.2;
-// Vertical clearance above the device safe area so the recenter button sits
-// above the app's bottom "signed in as…" bar in App.tsx. Keep in sync if that
-// bar's height changes.
-const BOTTOM_BAR_CLEARANCE = 64;
+// Margin above the device safe area for bottom-anchored overlays (recenter
+// button, preview card).
+const BOTTOM_MARGIN = 16;
export function MapScreen() {
const cameraRef = useRef(null);
@@ -233,7 +232,7 @@ export function MapScreen() {
onPress={flyToUser}
style={[
styles.recenterButton,
- {bottom: safeAreaInsets.bottom + BOTTOM_BAR_CLEARANCE},
+ {bottom: safeAreaInsets.bottom + BOTTOM_MARGIN},
]}>
◎
@@ -241,7 +240,7 @@ export function MapScreen() {
{selectedElementId ? (
setSelectedElementId(null)}
onExpand={() => setDetailExpanded(true)}
/>