Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail

echo "→ tsc --noEmit"
bunx tsc --noEmit

echo "→ biome check"
bun run lint
7 changes: 2 additions & 5 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/

import {ApolloProvider} from '@apollo/client';
import {NewAppScreen} from '@react-native/new-app-screen';
import {useEffect} from 'react';
import {
ActivityIndicator,
Expand All @@ -24,6 +23,7 @@ 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 {MapScreen} from './src/map/MapScreen';

function App() {
const isDarkMode = useColorScheme() === 'dark';
Expand Down Expand Up @@ -63,10 +63,7 @@ function AppContent() {

return (
<View style={styles.container}>
<NewAppScreen
templateFileName="App.tsx"
safeAreaInsets={safeAreaInsets}
/>
<MapScreen />
<View
style={[styles.logoutBar, {paddingBottom: safeAreaInsets.bottom + 12}]}>
<Text style={styles.signedInAs}>Signed in as {auth.user.email}</Text>
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application
android:name=".MainApplication"
Expand Down
32 changes: 31 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,24 @@ jest.mock('react-native-encrypted-storage', () => ({
clear: jest.fn(() => Promise.resolve()),
},
}));

jest.mock('@maplibre/maplibre-react-native', () => {
const React = require('react');
const passthrough = name => {
const Component = React.forwardRef(({children}, _ref) =>
React.createElement('View', {testID: name}, children),
);
Component.displayName = name;
return Component;
};
return {
__esModule: true,
Map: passthrough('Map'),
Camera: passthrough('Camera'),
UserLocation: passthrough('UserLocation'),
LocationManager: {
requestPermissions: jest.fn(() => Promise.resolve(false)),
},
useCurrentPosition: () => null,
};
});
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"lint": "biome check",
"lint:fix": "biome check --write",
"format": "biome format --write",
"codegen": "graphql-codegen --config codegen.ts"
"codegen": "graphql-codegen --config codegen.ts",
"postinstall": "git rev-parse --git-dir > /dev/null 2>&1 && git config core.hooksPath .githooks || true"
},
"dependencies": {
"@apollo/client": "^3.11.0",
"@maplibre/maplibre-react-native": "^11.2.1",
"@react-native/new-app-screen": "0.85.3",
"graphql": "^16.9.0",
"react": "19.2.3",
Expand Down
62 changes: 62 additions & 0 deletions src/map/MapScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
Camera,
type CameraRef,
LocationManager,
Map as MapLibreMap,
UserLocation,
useCurrentPosition,
} from '@maplibre/maplibre-react-native';
import {useEffect, useRef, useState} from 'react';
import {StyleSheet, View} from 'react-native';

const MAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty';
const USER_ZOOM = 14;

export function MapScreen() {
const cameraRef = useRef<CameraRef>(null);
const hasCenteredRef = useRef(false);
const [permissionGranted, setPermissionGranted] = useState(false);

useEffect(() => {
let cancelled = false;
(async () => {
const granted = await LocationManager.requestPermissions();
if (!cancelled && granted) {
setPermissionGranted(true);
}
})();
return () => {
cancelled = true;
};
}, []);

const position = useCurrentPosition({enabled: permissionGranted});

useEffect(() => {
if (hasCenteredRef.current || !position) return;
hasCenteredRef.current = true;
cameraRef.current?.flyTo({
center: [position.coords.longitude, position.coords.latitude],
zoom: USER_ZOOM,
duration: 1500,
});
}, [position]);

return (
<View style={styles.container}>
<MapLibreMap mapStyle={MAP_STYLE} style={styles.map}>
<Camera ref={cameraRef} initialViewState={{center: [0, 20], zoom: 1}} />
<UserLocation animated accuracy />
</MapLibreMap>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
},
map: {
flex: 1,
},
});
Loading